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
|