2026-rff_mp/krasnovia/lab2/docs/data/strategies.py
2026-05-20 23:04:18 +03:00

176 lines
6.9 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.

"""
Этап 3: Паттерн Strategy — алгоритмы поиска пути.
Зачем Strategy?
Позволяет менять алгоритм поиска во время выполнения без изменения
остального кода. Добавить новый алгоритм = написать новый класс.
"""
from abc import ABC, abstractmethod
from collections import deque
import heapq
from maze_model import Cell, Maze
class PathFindingStrategy(ABC):
"""Интерфейс стратегии поиска пути."""
@abstractmethod
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> list[Cell]:
"""
Возвращает список клеток от старта до выхода (включительно).
Пустой список — если путь не найден.
Счётчик посещённых клеток сохраняется в self.visited_count.
"""
...
# Вспомогательный метод восстановления пути по словарю предшественников
@staticmethod
def _reconstruct_path(came_from: dict, start: Cell, goal: Cell) -> list[Cell]:
path = []
current = goal
while current != start:
path.append(current)
current = came_from[current]
path.append(start)
path.reverse()
return path
# ── BFS ──────────────────────────────────────────────────────────────────────
class BFSStrategy(PathFindingStrategy):
"""
Поиск в ширину (BFS).
Гарантирует кратчайший путь по числу шагов.
Использует очередь (deque).
"""
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> list[Cell]:
queue = deque([start])
came_from: dict[Cell, Cell | None] = {start: None}
self.visited_count = 0
while queue:
current = queue.popleft()
self.visited_count += 1
if current == exit_cell:
return self._reconstruct_path(came_from, start, exit_cell)
for neighbor in maze.get_neighbors(current):
if neighbor not in came_from:
came_from[neighbor] = current
queue.append(neighbor)
return [] # путь не найден
# ── DFS ──────────────────────────────────────────────────────────────────────
class DFSStrategy(PathFindingStrategy):
"""
Поиск в глубину (DFS).
Быстр, но не гарантирует кратчайший путь.
Использует стек (list).
"""
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> list[Cell]:
stack = [start]
came_from: dict[Cell, Cell | None] = {start: None}
self.visited_count = 0
while stack:
current = stack.pop()
self.visited_count += 1
if current == exit_cell:
return self._reconstruct_path(came_from, start, exit_cell)
for neighbor in maze.get_neighbors(current):
if neighbor not in came_from:
came_from[neighbor] = current
stack.append(neighbor)
return []
# ── A* ───────────────────────────────────────────────────────────────────────
class AStarStrategy(PathFindingStrategy):
"""
Алгоритм A* с манхэттенской эвристикой.
Компромисс между BFS (оптимальность) и скоростью.
Использует приоритетную очередь (heapq).
"""
@staticmethod
def _heuristic(a: Cell, b: Cell) -> int:
return abs(a.x - b.x) + abs(a.y - b.y)
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> list[Cell]:
# (f_score, уникальный_счётчик, клетка) — счётчик нужен для стабильного сравнения
counter = 0
open_heap = [(0, counter, start)]
came_from: dict[Cell, Cell | None] = {start: None}
g_score: dict[Cell, int] = {start: 0}
self.visited_count = 0
while open_heap:
_, _, current = heapq.heappop(open_heap)
self.visited_count += 1
if current == exit_cell:
return self._reconstruct_path(came_from, start, exit_cell)
for neighbor in maze.get_neighbors(current):
tentative_g = g_score[current] + 1
if tentative_g < g_score.get(neighbor, float("inf")):
g_score[neighbor] = tentative_g
came_from[neighbor] = current
f = tentative_g + self._heuristic(neighbor, exit_cell)
counter += 1
heapq.heappush(open_heap, (f, counter, neighbor))
return []
# ── Dijkstra ─────────────────────────────────────────────────────────────────
class DijkstraStrategy(PathFindingStrategy):
"""
Алгоритм Дейкстры.
В базовом лабиринте (все веса = 1) совпадает с BFS,
но полезен при взвешенных клетках (болото, песок и т.д.).
"""
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> list[Cell]:
counter = 0
open_heap = [(0, counter, start)]
came_from: dict[Cell, Cell | None] = {start: None}
dist: dict[Cell, int] = {start: 0}
self.visited_count = 0
while open_heap:
cost, _, current = heapq.heappop(open_heap)
self.visited_count += 1
if current == exit_cell:
return self._reconstruct_path(came_from, start, exit_cell)
if cost > dist.get(current, float("inf")):
continue # устаревшая запись
for neighbor in maze.get_neighbors(current):
# Вес клетки: можно расширить через cell.weight
weight = getattr(neighbor, "weight", 1)
new_cost = dist[current] + weight
if new_cost < dist.get(neighbor, float("inf")):
dist[neighbor] = new_cost
came_from[neighbor] = current
counter += 1
heapq.heappush(open_heap, (new_cost, counter, neighbor))
return []