2026-rff_mp/pogodinda/lab2/src/pathfinding.py

183 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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