From 0c93c3a3b0790985fe8e30fe49d629427177f353 Mon Sep 17 00:00:00 2001 From: pogodinda Date: Sat, 23 May 2026 22:47:48 +0300 Subject: [PATCH] =?UTF-8?q?[2]=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BF=D0=B0=D1=82=D1=82=D0=B5=D1=80=D0=BD=20Str?= =?UTF-8?q?ategy=20=D1=81=20=D0=B0=D0=BB=D0=B3=D0=BE=D1=80=D0=B8=D1=82?= =?UTF-8?q?=D0=BC=D0=B0=D0=BC=D0=B8=20=D0=BF=D0=BE=D0=B8=D1=81=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=BF=D1=83=D1=82=D0=B8=20(BFS,=20DFS,=20A*,=20=D0=94=D0=B5?= =?UTF-8?q?=D0=B9=D0=BA=D1=81=D1=82=D1=80=D0=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pogodinda/lab2/src/pathfinding.py | 183 ++++++++++++++++++++++++++ pogodinda/lab2/tests/test_strategy.py | 34 +++++ 2 files changed, 217 insertions(+) create mode 100644 pogodinda/lab2/src/pathfinding.py create mode 100644 pogodinda/lab2/tests/test_strategy.py diff --git a/pogodinda/lab2/src/pathfinding.py b/pogodinda/lab2/src/pathfinding.py new file mode 100644 index 0000000..44d3070 --- /dev/null +++ b/pogodinda/lab2/src/pathfinding.py @@ -0,0 +1,183 @@ +from abc import ABC, abstractmethod +from typing import List, Tuple +from collections import deque +import heapq +from maze import Maze, Cell + + +class PathFindingStrategy(ABC): + """Интерфейс стратегии поиска пути (Strategy pattern).""" + + @abstractmethod + def find_path(self, maze: Maze, start: Cell, exit: Cell) -> Tuple[List[Cell], int]: + """ + Возвращает: + - путь (список клеток от старта до выхода) + - количество посещённых клеток + Если пути нет — возвращает ([], 0). + """ + pass + + @abstractmethod + def get_name(self) -> str: + """Название алгоритма для отчёта.""" + pass + + +class BFSStrategy(PathFindingStrategy): + """BFS — поиск в ширину. Гарантирует кратчайший путь по числу шагов.""" + + def get_name(self) -> str: + return "BFS" + + def find_path(self, maze: Maze, start: Cell, exit: Cell) -> Tuple[List[Cell], int]: + if not start or not exit: + return [], 0 + + # Очередь: (текущая_клетка, путь_до_неё) + queue = deque([(start, [start])]) + visited = {start} + visited_count = 1 + + while queue: + current, path = queue.popleft() + + if current == exit: + return path, visited_count + + # Проверяем всех соседей + for neighbor in maze.get_neighbors(current): + if neighbor not in visited: + visited.add(neighbor) + visited_count += 1 + queue.append((neighbor, path + [neighbor])) + + return [], visited_count + + +class DFSStrategy(PathFindingStrategy): + """DFS — поиск в глубину. Быстрый, но путь не обязательно кратчайший.""" + + def get_name(self) -> str: + return "DFS" + + def find_path(self, maze: Maze, start: Cell, exit: Cell) -> Tuple[List[Cell], int]: + if not start or not exit: + return [], 0 + + # Стек: (текущая_клетка, путь_до_неё) + stack = [(start, [start])] + visited = {start} + visited_count = 1 + + while stack: + current, path = stack.pop() + + if current == exit: + return path, visited_count + + for neighbor in maze.get_neighbors(current): + if neighbor not in visited: + visited.add(neighbor) + visited_count += 1 + stack.append((neighbor, path + [neighbor])) + + return [], visited_count + + +class AStarStrategy(PathFindingStrategy): + """ + A* — поиск с эвристикой. + Использует приоритетную очередь (кучу) и манхэттенское расстояние. + """ + + def get_name(self) -> str: + return "A*" + + def _heuristic(self, cell: Cell, exit: Cell) -> int: + """Манхэттенское расстояние: |x1-x2| + |y1-y2|.""" + if not cell or not exit: + return 0 + return abs(cell.x - exit.x) + abs(cell.y - exit.y) + + def find_path(self, maze: Maze, start: Cell, exit: Cell) -> Tuple[List[Cell], int]: + if not start or not exit: + return [], 0 + + # Куча: (f_score, счётчик, клетка, путь, g_score) + # f = g + h, где g — пройденный путь, h — эвристика + counter = 0 + open_set = [(self._heuristic(start, exit), counter, start, [start], 0)] + visited = set() + visited_count = 0 + g_scores = {start: 0} + + while open_set: + _, _, current, path, g_score = heapq.heappop(open_set) + + if current in visited: + continue + + visited.add(current) + visited_count += 1 + + if current == exit: + return path, visited_count + + for neighbor in maze.get_neighbors(current): + if neighbor in visited: + continue + + tentative_g = g_score + 1 + + if neighbor not in g_scores or tentative_g < g_scores[neighbor]: + g_scores[neighbor] = tentative_g + f_score = tentative_g + self._heuristic(neighbor, exit) + counter += 1 + heapq.heappush(open_set, (f_score, counter, neighbor, path + [neighbor], tentative_g)) + + return [], visited_count + + +class DijkstraStrategy(PathFindingStrategy): + """ + Дейкстра — для взвешенных графов. + В базовом варианте (вес=1) работает как BFS, но с приоритетной очередью. + """ + + def get_name(self) -> str: + return "Dijkstra" + + def find_path(self, maze: Maze, start: Cell, exit: Cell) -> Tuple[List[Cell], int]: + if not start or not exit: + return [], 0 + + # Куча: (расстояние, счётчик, клетка, путь) + counter = 0 + pq = [(0, counter, start, [start])] + distances = {start: 0} + visited = set() + visited_count = 0 + + while pq: + dist, _, current, path = heapq.heappop(pq) + + if current in visited: + continue + + visited.add(current) + visited_count += 1 + + if current == exit: + return path, visited_count + + for neighbor in maze.get_neighbors(current): + weight = neighbor.weight + new_dist = dist + weight + + if neighbor not in distances or new_dist < distances[neighbor]: + distances[neighbor] = new_dist + counter += 1 + heapq.heappush(pq, (new_dist, counter, neighbor, path + [neighbor])) + + return [], visited_count \ No newline at end of file diff --git a/pogodinda/lab2/tests/test_strategy.py b/pogodinda/lab2/tests/test_strategy.py new file mode 100644 index 0000000..2698d45 --- /dev/null +++ b/pogodinda/lab2/tests/test_strategy.py @@ -0,0 +1,34 @@ +from src.maze_builder import TextFileMazeBuilder +from src.pathfinding import BFSStrategy, DFSStrategy, AStarStrategy, DijkstraStrategy + +# Загружаем лабиринт +builder = TextFileMazeBuilder() +maze = builder.build_from_file("data/demo_maze.txt") + +print("Лабиринт:") +print(maze) +print(f"\nСтарт: ({maze.start.x}, {maze.start.y})") +print(f"Выход: ({maze.exit.x}, {maze.exit.y})") + +# Тестируем все алгоритмы +strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy(), DijkstraStrategy()] + +for strategy in strategies: + print(f"\n{'='*40}") + print(f"Алгоритм: {strategy.get_name()}") + print('='*40) + + path, visited = strategy.find_path(maze, maze.start, maze.exit) + + print(f"Посещено клеток: {visited}") + print(f"Длина пути: {len(path)}") + + if path: + print("Путь найден!") # ← убрал f, или добавь переменную + if len(path) > 10: + print(f"Начало: {[(c.x, c.y) for c in path[:5]]}...") + print(f"Конец: ...{[(c.x, c.y) for c in path[-5:]]}") + else: + print(f"Путь: {[(c.x, c.y) for c in path]}") + else: + print("Путь не найден") \ No newline at end of file