2026-rff_mp/SobolevNS/docs/data/task2_maze/maze_solver/strategies.py
2026-05-22 13:42:42 +03:00

180 lines
5.7 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.

"""
maze_solver/strategies.py - паттерн Strategy.
Каждая стратегия реализует один и тот же интерфейс PathFindingStrategy
с методом find_path(maze, start, exit_), возвращающим:
{'path': [Cell, ...], 'visited': int}
Стратегии не модифицируют сам лабиринт.
"""
from abc import ABC, abstractmethod
from collections import deque
import heapq
# ---------- интерфейс стратегии ----------
class PathFindingStrategy(ABC):
name = "Strategy"
@abstractmethod
def find_path(self, maze, start, exit_):
"""Возвращает dict с ключами 'path' (list[Cell]) и 'visited' (int).
Если пути нет - path = []."""
# ---------- общая утилита: восстановление пути ----------
def _reconstruct(parents, start, end):
"""Восстанавливает путь по словарю предшественников {(x,y): Cell|None}."""
path = []
cur = end
while cur is not None:
path.append(cur)
cur = parents.get((cur.x, cur.y))
path.reverse()
if path and path[0] is start:
return path
return []
# ---------- BFS ----------
class BFSStrategy(PathFindingStrategy):
"""Поиск в ширину. Гарантирует кратчайший путь по числу шагов
(когда веса всех клеток равны)."""
name = "BFS"
def find_path(self, maze, start, exit_):
queue = deque([start])
parents = {(start.x, start.y): None}
visited = 1
while queue:
cell = queue.popleft()
if cell is exit_:
return {"path": _reconstruct(parents, start, exit_),
"visited": visited}
for nb in maze.get_neighbors(cell):
key = (nb.x, nb.y)
if key not in parents:
parents[key] = cell
visited += 1
queue.append(nb)
return {"path": [], "visited": visited}
# ---------- DFS ----------
class DFSStrategy(PathFindingStrategy):
"""Поиск в глубину. Не гарантирует кратчайший путь, но прост и быстр."""
name = "DFS"
def find_path(self, maze, start, exit_):
stack = [start]
parents = {(start.x, start.y): None}
visited = 1
while stack:
cell = stack.pop()
if cell is exit_:
return {"path": _reconstruct(parents, start, exit_),
"visited": visited}
for nb in maze.get_neighbors(cell):
key = (nb.x, nb.y)
if key not in parents:
parents[key] = cell
visited += 1
stack.append(nb)
return {"path": [], "visited": visited}
# ---------- A* ----------
def _manhattan(a, b):
return abs(a.x - b.x) + abs(a.y - b.y)
class AStarStrategy(PathFindingStrategy):
"""A*-поиск с манхэттенской эвристикой. Учитывает вес клеток (weight)."""
name = "A*"
def find_path(self, maze, start, exit_):
# f = g + h; в куче храним (f, tie, cell)
g_score = {(start.x, start.y): 0}
parents = {(start.x, start.y): None}
tie = 0
heap = [(_manhattan(start, exit_), tie, start)]
visited = 0
closed = set()
while heap:
f, _, cell = heapq.heappop(heap)
key = (cell.x, cell.y)
if key in closed:
continue
closed.add(key)
visited += 1
if cell is exit_:
return {"path": _reconstruct(parents, start, exit_),
"visited": visited}
for nb in maze.get_neighbors(cell):
nb_key = (nb.x, nb.y)
tentative_g = g_score[key] + nb.weight
if tentative_g < g_score.get(nb_key, float("inf")):
g_score[nb_key] = tentative_g
parents[nb_key] = cell
tie += 1
heapq.heappush(heap,
(tentative_g + _manhattan(nb, exit_), tie, nb))
return {"path": [], "visited": visited}
# ---------- Дейкстра ----------
class DijkstraStrategy(PathFindingStrategy):
"""Дейкстра - оптимальный путь с учётом веса клеток.
На немодифицированном лабиринте (все веса = 1) совпадает с BFS."""
name = "Dijkstra"
def find_path(self, maze, start, exit_):
dist = {(start.x, start.y): 0}
parents = {(start.x, start.y): None}
tie = 0
heap = [(0, tie, start)]
visited = 0
closed = set()
while heap:
d, _, cell = heapq.heappop(heap)
key = (cell.x, cell.y)
if key in closed:
continue
closed.add(key)
visited += 1
if cell is exit_:
return {"path": _reconstruct(parents, start, exit_),
"visited": visited}
for nb in maze.get_neighbors(cell):
nb_key = (nb.x, nb.y)
nd = d + nb.weight
if nd < dist.get(nb_key, float("inf")):
dist[nb_key] = nd
parents[nb_key] = cell
tie += 1
heapq.heappush(heap, (nd, tie, nb))
return {"path": [], "visited": visited}
STRATEGIES = {
"BFS": BFSStrategy,
"DFS": DFSStrategy,
"A*": AStarStrategy,
"Dijkstra": DijkstraStrategy,
}