diff --git a/shekurovaa/1/docs/data/zad1.py b/shekurovaa/1/docs/data/zad1.py new file mode 100644 index 0000000..ba01cb7 --- /dev/null +++ b/shekurovaa/1/docs/data/zad1.py @@ -0,0 +1,475 @@ +import time +import random +import csv +import os +import sys +from dataclasses import dataclass, field +from typing import Optional, List, Tuple, Any + +import matplotlib.pyplot as plt + +sys.setrecursionlimit(20000) + +BASE_PATH = r"C:\Users\andre\2026-rff_mp\smirnovad\lab1" +DOCS_PATH = os.path.join(BASE_PATH, "docs") +DATA_PATH = os.path.join(DOCS_PATH, "data") + +for path in (DOCS_PATH, DATA_PATH): + os.makedirs(path, exist_ok=True) + +N = 10_000 +REPEATS = 5 +FOUND_SAMPLE_SIZE = 100 +NOT_FOUND_SAMPLE_SIZE = 10 +DELETE_SAMPLE_SIZE = 50 + +@dataclass +class NodeLL: + """Узел односвязного списка.""" + key: str + value: Any + next: Optional["NodeLL"] = None + + +class LinkedList: + """Односвязный список с вставкой в начало.""" + + def __init__(self) -> None: + self.head: Optional[NodeLL] = None + + def insert(self, key: str, value: Any) -> None: + self.head = NodeLL(key, value, self.head) + + def find(self, key: str) -> Any: + cur = self.head + while cur is not None: + if cur.key == key: + return cur.value + cur = cur.next + return None + + def delete(self, key: str) -> None: + cur = self.head + prev: Optional[NodeLL] = None + + while cur is not None: + if cur.key == key: + if prev is None: + self.head = cur.next + else: + prev.next = cur.next + return + prev = cur + cur = cur.next + + def items(self) -> List[Tuple[str, Any]]: + res: List[Tuple[str, Any]] = [] + cur = self.head + while cur is not None: + res.append((cur.key, cur.value)) + cur = cur.next + return sorted(res) + + +@dataclass +class NodeBST: + """Узел бинарного дерева поиска.""" + key: str + value: Any + left: Optional["NodeBST"] = None + right: Optional["NodeBST"] = None + + +class BST: + """Бинарное дерево поиска (без балансировки).""" + + def __init__(self) -> None: + self.root: Optional[NodeBST] = None + + def insert(self, key: str, value: Any) -> None: + self.root = self._insert(self.root, key, value) + + def _insert(self, node: Optional[NodeBST], key: str, value: Any) -> NodeBST: + if node is None: + return NodeBST(key, value) + if key < node.key: + node.left = self._insert(node.left, key, value) + elif key > node.key: + node.right = self._insert(node.right, key, value) + else: + node.value = value + return node + + def find(self, key: str) -> Any: + return self._find(self.root, key) + + def _find(self, node: Optional[NodeBST], key: str) -> Any: + if node is None: + return None + if key == node.key: + return node.value + if key < node.key: + return self._find(node.left, key) + return self._find(node.right, key) + + def delete(self, key: str) -> None: + self.root = self._delete(self.root, key) + + def _delete(self, node: Optional[NodeBST], key: str) -> Optional[NodeBST]: + if node is None: + return None + if key < node.key: + node.left = self._delete(node.left, key) + elif key > node.key: + node.right = self._delete(node.right, key) + else: + if node.left is None: + return node.right + if node.right is None: + return node.left + succ = node.right + while succ.left is not None: + succ = succ.left + node.key, node.value = succ.key, succ.value + node.right = self._delete(node.right, succ.key) + return node + + def items(self) -> List[Tuple[str, Any]]: + res: List[Tuple[str, Any]] = [] + self._inorder(self.root, res) + return res + + def _inorder(self, node: Optional[NodeBST], out: List[Tuple[str, Any]]) -> None: + if node is None: + return + self._inorder(node.left, out) + out.append((node.key, node.value)) + self._inorder(node.right, out) + + +class HashTable: + """Хеш-таблица с цепочками (односвязные списки).""" + + def __init__(self, capacity: int = 1024) -> None: + self.capacity = capacity + self.buckets: List[Optional[LinkedList]] = [None] * capacity + + def _index(self, key: str) -> int: + return hash(key) % self.capacity + + def insert(self, key: str, value: Any) -> None: + idx = self._index(key) + bucket = self.buckets[idx] + if bucket is None: + bucket = LinkedList() + self.buckets[idx] = bucket + bucket.insert(key, value) + + def find(self, key: str) -> Any: + idx = self._index(key) + bucket = self.buckets[idx] + if bucket is None: + return None + return bucket.find(key) + + def delete(self, key: str) -> None: + idx = self._index(key) + bucket = self.buckets[idx] + if bucket is None: + return + bucket.delete(key) + + def items(self) -> List[Tuple[str, Any]]: + res: List[Tuple[str, Any]] = [] + for bucket in self.buckets: + if bucket is not None: + res.extend(bucket.items()) + return sorted(res) + +def generate_records(n: int) -> List[Tuple[str, str]]: + """Генерирует список (имя, телефон).""" + raw = [(f"user_{i:05d}", f"8-900-{random.randint(100, 999)}") for i in range(n)] + return raw + + +def prepare_datasets(n: int) -> dict: + """Подготавливает наборы данных: случайный и отсортированный.""" + raw = generate_records(n) + shuffled = raw[:] + random.shuffle(shuffled) + sorted_data = sorted(raw, key=lambda x: x[0]) + return { + "случайный": shuffled, + "сортированный": sorted_data, + } + +@dataclass +class RunResult: + struct_name: str + mode: str + run_label: str + insert_time: float + find_time: float + delete_time: float + + +class BenchmarkRunner: + def __init__(self, repeats: int = REPEATS) -> None: + self.repeats = repeats + self.results: List[RunResult] = [] + + def run_experiment(self, struct_name: str, mode: str, data: List[Tuple[str, str]]) -> None: + print(f"Запуск: {struct_name} ({mode})") + + insert_times: List[float] = [] + find_times: List[float] = [] + delete_times: List[float] = [] + + for rep in range(self.repeats): + if struct_name == "LinkedList": + container = LinkedList() + elif struct_name == "HashTable": + container = HashTable(capacity=1024) + elif struct_name == "BST": + container = BST() + else: + raise ValueError(f"Неизвестная структура: {struct_name}") + + t0 = time.perf_counter() + for key, val in data: + container.insert(key, val) + insert_times.append(time.perf_counter() - t0) + + found_keys = [d[0] for d in random.sample(data, FOUND_SAMPLE_SIZE)] + not_found_keys = [f"nonexistent_{j}" for j in range(NOT_FOUND_SAMPLE_SIZE)] + search_keys = found_keys + not_found_keys + + t0 = time.perf_counter() + for k in search_keys: + container.find(k) + find_times.append(time.perf_counter() - t0) + + delete_keys = [d[0] for d in random.sample(data, DELETE_SAMPLE_SIZE)] + t0 = time.perf_counter() + for k in delete_keys: + container.delete(k) + delete_times.append(time.perf_counter() - t0) + + self.results.append( + RunResult( + struct_name=struct_name, + mode=mode, + run_label=f"run_{rep+1}", + insert_time=insert_times[-1], + find_time=find_times[-1], + delete_time=delete_times[-1], + ) + ) + + avg_ins = sum(insert_times) / self.repeats + avg_find = sum(find_times) / self.repeats + avg_del = sum(delete_times) / self.repeats + + self.results.append( + RunResult( + struct_name=struct_name, + mode=mode, + run_label="AVG", + insert_time=avg_ins, + find_time=avg_find, + delete_time=avg_del, + ) + ) + + def save_csv(self, path: str) -> None: + with open(path, "w", newline="", encoding="utf-8") as f: + w = csv.writer(f) + w.writerow(["Структура", "Режим", "Итерация", "Вставка", "Поиск", "Удаление"]) + for r in self.results: + w.writerow([ + r.struct_name, + r.mode, + r.run_label, + r.insert_time, + r.find_time, + r.delete_time, + ]) + + def summary(self) -> List[dict]: + """Возвращает список словарей со средними по (структура, режим).""" + summary = [] + groups: dict = {} + for r in self.results: + if r.run_label != "AVG": + continue + key = (r.struct_name, r.mode) + groups[key] = { + "name": r.struct_name, + "mode": r.mode, + "ins": r.insert_time, + "find": r.find_time, + "del": r.delete_time, + } + summary.extend(groups.values()) + return summary + + +def build_plots(summary: List[dict], n: int, path_base: str) -> None: + structs = ["LinkedList", "HashTable", "BST"] + ops = ["insert", "find", "delete"] + op_keys = ["ins", "find", "del"] + colors_struct = { + "LinkedList": "#5dade2", + "HashTable": "#e67e22", + "BST": "#58d68d", + } + + fig1, axs = plt.subplots(1, 3, figsize=(18, 6)) + fig1.suptitle("Влияние порядка данных на время операций", fontsize=16, fontweight="bold") + + labels_ops = ["insert", "find", "delete"] + width = 0.35 + x = [0, 1, 2] + + for i, s_name in enumerate(structs): + rand_row = next( + (r for r in summary if r["name"] == s_name and r["mode"] == "случайный"), + None, + ) + sort_row = next( + (r for r in summary if r["name"] == s_name and r["mode"] == "сортированный"), + None, + ) + if rand_row is None or sort_row is None: + continue + + vals_rand = [rand_row["ins"], rand_row["find"], rand_row["del"]] + vals_sort = [sort_row["ins"], sort_row["find"], sort_row["del"]] + + axs[i].bar( + [p - width / 2 for p in x], + vals_rand, + width, + label="случайный", + color=colors_struct[s_name], + ) + axs[i].bar( + [p + width / 2 for p in x], + vals_sort, + width, + label="сортированный", + color="#e74c3c", + alpha=0.85, + ) + + axs[i].set_title(s_name, fontweight="bold") + axs[i].set_xticks(x) + axs[i].set_xticklabels(labels_ops) + axs[i].set_ylabel("Время (с)") + axs[i].legend() + axs[i].grid(axis="y", linestyle="--", alpha=0.3) + + plt.tight_layout(rect=[0, 0.03, 1, 0.95]) + plt.savefig(os.path.join(path_base, "order_impact.png")) + plt.close(fig1) + + fig2, axs2 = plt.subplots(1, 3, figsize=(18, 6)) + fig2.suptitle(f"Сравнение структур данных (N={n})", fontsize=16, fontweight="bold") + + for i, op_key in enumerate(op_keys): + plot_labels = [] + plot_values = [] + plot_colors = [] + + for r in summary: + plot_labels.append(f"{r['name']}\\n({r['mode'][:4]})") + plot_values.append(r[op_key]) + plot_colors.append(colors_struct[r["name"]]) + + bars = axs2[i].bar(plot_labels, plot_values, color=plot_colors) + axs2[i].set_title(f"Операция: {ops[i]}", fontweight="bold") + axs2[i].set_ylabel("Время (с)") + axs2[i].tick_params(axis="x", rotation=15) + + for bar in bars: + h = bar.get_height() + axs2[i].text( + bar.get_x() + bar.get_width() / 2, + h, + f"{h:.4f}", + ha="center", + va="bottom", + fontsize=8, + ) + + plt.tight_layout(rect=[0, 0.03, 1, 0.95]) + plt.savefig(os.path.join(path_base, "struct_comparison.png")) + plt.close(fig2) + + +def build_report(summary: List[dict], n: int, path: str) -> None: + lines = [] + lines.append("# Технический отчет: Сравнительный анализ структур данных\n") + lines.append("## 1. Вводные данные\n") + lines.append( + f"Цель — оценить производительность LinkedList, HashTable и BST на массиве из {n} элементов. " + "Рассмотрены два сценария: случайный порядок ключей и заранее отсортированный по возрастанию.\n" + ) + + lines.append("## 2. Результаты измерений (среднее)\n") + lines.append("| Структура | Режим | Вставка (с) | Поиск (с) | Удаление (с) |\n") + lines.append("| :--- | :--- | :---: | :---: | :---: |\n") + for r in summary: + lines.append( + f"| {r['name']} | {r['mode']} | {r['ins']:.6f} | {r['find']:.6f} | {r['del']:.6f} |\n" + ) + + lines.append("\n## 3. Визуализация\n") + lines.append("### Сравнение структур по операциям\n") + lines.append("![Сравнение структур](data/struct_comparison.png)\n") + lines.append("### Влияние порядка данных\n") + lines.append("![Влияние порядка](data/order_impact.png)\n") + + lines.append("## 4. Выводы\n") + lines.append( + "- **BST без балансировки** на отсортированных ключах вырождается в линейную цепочку, " + "что приводит к резкому росту времени операций (практическая сложность приближается к $O(N)$).\n" + ) + lines.append( + "- **HashTable** показывает стабильную производительность, практически не зависящую от порядка входных данных. " + "Это делает её предпочтительной для задач с интенсивным поиском и вставкой.\n" + ) + lines.append( + "- **LinkedList**ónico предсказуемо медленен при поиске и удалении, так как эти операции требуют линейного прохода по списку.\n" + ) + lines.append( + "- **Итог:** для систем с высокой нагрузкой на поиск/вставку оптимальным выбором является хеш-таблица; " + "BST имеет смысл использовать только при дополнительной балансировке (AVL, красно-черное дерево и т.п.).\n" + ) + + with open(path, "w", encoding="utf-8") as f: + f.writelines(lines) + + + +def main() -> None: + datasets = prepare_datasets(N) + runner = BenchmarkRunner(repeats=REPEATS) + + for mode_name, data in datasets.items(): + for struct_name in ["LinkedList", "HashTable", "BST"]: + runner.run_experiment(struct_name, mode_name, data) + + csv_path = os.path.join(DATA_PATH, "results.csv") + runner.save_csv(csv_path) + + summary = runner.summary() + + build_plots(summary, N, DATA_PATH) + build_report(summary, N, os.path.join(DOCS_PATH, "report.md")) + + print("Готово.") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/shekurovaa/2/docs/Report.docx b/shekurovaa/2/docs/Report.docx new file mode 100644 index 0000000..1d5ac2a Binary files /dev/null and b/shekurovaa/2/docs/Report.docx differ diff --git a/shekurovaa/2/docs/data/builder.py b/shekurovaa/2/docs/data/builder.py new file mode 100644 index 0000000..321b6be --- /dev/null +++ b/shekurovaa/2/docs/data/builder.py @@ -0,0 +1,46 @@ +from model import Cell, Maze + +class MazeBuilder: + def buildFromFile(self, filename: str) -> Maze: + raise NotImplementedError + + +class TextFileMazeBuilder(MazeBuilder): + def buildFromFile(self, filename: str) -> Maze: + with open(filename, "r", encoding="utf-8") as f: + raw_lines = [line.rstrip("\n") for line in f if line.strip("\n") != ""] + + width = max(len(line) for line in raw_lines) + grid = [] + + start_count = 0 + exit_count = 0 + + for y, line in enumerate(raw_lines): + row = [] + padded = line.ljust(width) + for x, ch in enumerate(padded): + if ch == "#": + row.append(Cell(x, y, isWall=True)) + elif ch == "S": + row.append(Cell(x, y, isStart=True)) + start_count += 1 + elif ch == "E": + row.append(Cell(x, y, isExit=True)) + exit_count += 1 + elif ch == "1": + row.append(Cell(x, y, weight=1)) + elif ch == "2": + row.append(Cell(x, y, weight=2)) + elif ch == "3": + row.append(Cell(x, y, weight=3)) + else: + row.append(Cell(x, y)) + grid.append(row) + + maze = Maze(grid) + + if start_count != 1 or exit_count != 1: + raise ValueError("В лабиринте должен быть ровно один S и один E") + + return maze \ No newline at end of file diff --git a/shekurovaa/2/docs/data/command.py b/shekurovaa/2/docs/data/command.py new file mode 100644 index 0000000..d5ed005 --- /dev/null +++ b/shekurovaa/2/docs/data/command.py @@ -0,0 +1,44 @@ +class Command: + def execute(self): + raise NotImplementedError + + def undo(self): + raise NotImplementedError + + +class Player: + def __init__(self, position): + self.position = position + + +class MoveCommand(Command): + DIRS = { + "W": (0, -1), + "S": (0, 1), + "A": (-1, 0), + "D": (1, 0), + } + + def __init__(self, maze, player, direction): + self.maze = maze + self.player = player + self.direction = direction.upper() + self.prev_position = None + + def execute(self): + if self.direction not in self.DIRS: + return False + dx, dy = self.DIRS[self.direction] + current = self.player.position + nxt = self.maze.getCell(current.x + dx, current.y + dy) + if nxt is None or not nxt.isPassable(): + return False + self.prev_position = current + self.player.position = nxt + return True + + def undo(self): + if self.prev_position is not None: + self.player.position = self.prev_position + return True + return False \ No newline at end of file diff --git a/shekurovaa/2/docs/data/experiments.py b/shekurovaa/2/docs/data/experiments.py new file mode 100644 index 0000000..41df502 --- /dev/null +++ b/shekurovaa/2/docs/data/experiments.py @@ -0,0 +1,30 @@ +import csv +from statistics import mean + +def run_experiments(maze_files, strategies, runs=5, out_csv="output/results.csv"): + rows = [] + for maze_name, maze in maze_files.items(): + for strat_name, strat_cls in strategies.items(): + times = [] + visiteds = [] + lengths = [] + for _ in range(runs): + solver = maze["solver_factory"](strat_cls()) + stats = solver.solve() + times.append(stats.timeMs) + visiteds.append(stats.visitedCells) + lengths.append(stats.pathLength) + rows.append({ + "maze": maze_name, + "strategy": strat_name, + "time_ms": round(mean(times), 3), + "visited_cells": round(mean(visiteds), 1), + "path_length": round(mean(lengths), 1) + }) + + with open(out_csv, "w", newline="", encoding="utf-8") as f: + writer = csv.DictWriter(f, fieldnames=["maze", "strategy", "time_ms", "visited_cells", "path_length"]) + writer.writeheader() + writer.writerows(rows) + + return rows \ No newline at end of file diff --git a/shekurovaa/2/docs/data/main.py b/shekurovaa/2/docs/data/main.py new file mode 100644 index 0000000..f8e59fe --- /dev/null +++ b/shekurovaa/2/docs/data/main.py @@ -0,0 +1,33 @@ +from builder import TextFileMazeBuilder +from strategies import BFSStrategy, DFSStrategy, AStarStrategy +from solver import MazeSolver +from observer import ConsoleView +from command import Player, MoveCommand + +def main(): + builder = TextFileMazeBuilder() + maze = builder.buildFromFile("mazes/small.txt") + + console = ConsoleView() + console.update({"type": "message", "text": "Лабиринт загружен:"}) + console.update({"type": "render", "maze": maze}) + + strategies = { + "BFS": BFSStrategy(), + "DFS": DFSStrategy(), + "A*": AStarStrategy() + } + + for name, strat in strategies.items(): + solver = MazeSolver(maze, strat) + stats = solver.solve() + print(f"{name}: time={stats.timeMs:.3f} ms, visited={stats.visitedCells}, path={stats.pathLength}") + console.update({"type": "render", "maze": maze, "path": stats.path}) + + player = Player(maze.start) + cmd = MoveCommand(maze, player, "D") + cmd.execute() + console.update({"type": "render", "maze": maze, "player": player.position}) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/shekurovaa/2/docs/data/mazes/small.txt b/shekurovaa/2/docs/data/mazes/small.txt new file mode 100644 index 0000000..f3d092c --- /dev/null +++ b/shekurovaa/2/docs/data/mazes/small.txt @@ -0,0 +1,10 @@ +########## +#S # # +# ## # # # +# ## # # +# ### # +### ## # +# # # +# # ###E # +# # +########## \ No newline at end of file diff --git a/shekurovaa/2/docs/data/model.py b/shekurovaa/2/docs/data/model.py new file mode 100644 index 0000000..587691b --- /dev/null +++ b/shekurovaa/2/docs/data/model.py @@ -0,0 +1,67 @@ +from dataclasses import dataclass +from typing import List, Optional + +@dataclass(frozen=True) +class Cell: + x: int + y: int + isWall: bool = False + isStart: bool = False + isExit: bool = False + weight: int = 1 + + def isPassable(self) -> bool: + return not self.isWall + + +class Maze: + def __init__(self, grid: List[List[Cell]]): + self.grid = grid + self.height = len(grid) + self.width = len(grid[0]) if self.height else 0 + self.start: Optional[Cell] = None + self.exit: Optional[Cell] = None + + for row in grid: + for cell in row: + if cell.isStart: + self.start = cell + if cell.isExit: + self.exit = cell + + def getCell(self, x: int, y: int) -> Optional[Cell]: + if 0 <= y < self.height and 0 <= x < self.width: + return self.grid[y][x] + return None + + def getNeighbors(self, cell: Cell) -> List[Cell]: + result = [] + for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]: + nxt = self.getCell(cell.x + dx, cell.y + dy) + if nxt is not None and nxt.isPassable(): + result.append(nxt) + return result + + def render(self, path=None, player_position=None) -> str: + path_set = {(c.x, c.y) for c in path} if path else set() + player_xy = (player_position.x, player_position.y) if player_position else None + + lines = [] + for y in range(self.height): + row = [] + for x in range(self.width): + c = self.grid[y][x] + if player_xy == (x, y): + row.append("P") + elif c.isStart: + row.append("S") + elif c.isExit: + row.append("E") + elif (x, y) in path_set: + row.append(".") + elif c.isWall: + row.append("#") + else: + row.append(" ") + lines.append("".join(row)) + return "\n".join(lines) \ No newline at end of file diff --git a/shekurovaa/2/docs/data/observer.py b/shekurovaa/2/docs/data/observer.py new file mode 100644 index 0000000..43628e8 --- /dev/null +++ b/shekurovaa/2/docs/data/observer.py @@ -0,0 +1,15 @@ +class Observer: + def update(self, event): + raise NotImplementedError + + +class ConsoleView(Observer): + def update(self, event): + if isinstance(event, dict) and event.get("type") == "message": + print(event["text"]) + elif isinstance(event, dict) and event.get("type") == "render": + maze = event["maze"] + path = event.get("path") + player = event.get("player") + print(maze.render(path=path, player_position=player)) + print() \ No newline at end of file diff --git a/shekurovaa/2/docs/data/solver.py b/shekurovaa/2/docs/data/solver.py new file mode 100644 index 0000000..57d22c5 --- /dev/null +++ b/shekurovaa/2/docs/data/solver.py @@ -0,0 +1,38 @@ +import time +from dataclasses import dataclass + +@dataclass +class SearchStats: + timeMs: float + visitedCells: int + pathLength: int + path: list + + +class MazeSolver: + def __init__(self, maze, strategy): + self.maze = maze + self.strategy = strategy + + def setStrategy(self, strategy): + self.strategy = strategy + + def solve(self) -> SearchStats: + if self.maze.start is None or self.maze.exit is None: + raise ValueError("Лабиринт должен содержать start и exit") + + t0 = time.perf_counter() + result = self.strategy.findPath(self.maze, self.maze.start, self.maze.exit) + t1 = time.perf_counter() + + if isinstance(result, tuple): + path, visited = result + else: + path, visited = result, 0 + + return SearchStats( + timeMs=(t1 - t0) * 1000, + visitedCells=visited, + pathLength=len(path), + path=path + ) \ No newline at end of file diff --git a/shekurovaa/2/docs/data/strategies.py b/shekurovaa/2/docs/data/strategies.py new file mode 100644 index 0000000..0274d9a --- /dev/null +++ b/shekurovaa/2/docs/data/strategies.py @@ -0,0 +1,103 @@ +from collections import deque +import heapq +from math import inf + +class PathFindingStrategy: + def findPath(self, maze, start, exit): + raise NotImplementedError + + +def reconstruct_path(parent, start, goal): + if goal not in parent and goal != start: + return [] + path = [] + cur = goal + while cur != start: + path.append(cur) + cur = parent[cur] + path.append(start) + path.reverse() + return path + + +class BFSStrategy(PathFindingStrategy): + def findPath(self, maze, start, exit): + queue = deque([start]) + visited = {start} + parent = {} + visited_count = 0 + + while queue: + current = queue.popleft() + visited_count += 1 + if current == exit: + path = reconstruct_path(parent, start, exit) + return path, visited_count + + for nxt in maze.getNeighbors(current): + if nxt not in visited: + visited.add(nxt) + parent[nxt] = current + queue.append(nxt) + + return [], visited_count + + +class DFSStrategy(PathFindingStrategy): + def findPath(self, maze, start, exit): + stack = [start] + visited = {start} + parent = {} + visited_count = 0 + + while stack: + current = stack.pop() + visited_count += 1 + if current == exit: + path = reconstruct_path(parent, start, exit) + return path, visited_count + + for nxt in maze.getNeighbors(current): + if nxt not in visited: + visited.add(nxt) + parent[nxt] = current + stack.append(nxt) + + return [], visited_count + + +class AStarStrategy(PathFindingStrategy): + def h(self, a, b): + return abs(a.x - b.x) + abs(a.y - b.y) + + def findPath(self, maze, start, exit): + open_heap = [] + heapq.heappush(open_heap, (0, 0, start)) + parent = {} + g = {start: 0} + visited = set() + visited_count = 0 + counter = 1 + + while open_heap: + _, _, current = heapq.heappop(open_heap) + if current in visited: + continue + + visited.add(current) + visited_count += 1 + + if current == exit: + path = reconstruct_path(parent, start, exit) + return path, visited_count + + for nxt in maze.getNeighbors(current): + tentative_g = g[current] + nxt.weight + if tentative_g < g.get(nxt, inf): + g[nxt] = tentative_g + parent[nxt] = current + f = tentative_g + self.h(nxt, exit) + heapq.heappush(open_heap, (f, counter, nxt)) + counter += 1 + + return [], visited_count \ No newline at end of file diff --git a/shekurovaa/2/docs/~$Report.docx b/shekurovaa/2/docs/~$Report.docx new file mode 100644 index 0000000..6288f33 Binary files /dev/null and b/shekurovaa/2/docs/~$Report.docx differ