183 lines
6.2 KiB
Python
183 lines
6.2 KiB
Python
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 |