[2] Добавлен паттерн Strategy с алгоритмами поиска пути (BFS, DFS, A*, Дейкстра)

This commit is contained in:
pogodinda 2026-05-23 22:47:48 +03:00
parent 4db9491cec
commit 0c93c3a3b0
2 changed files with 217 additions and 0 deletions

View File

@ -0,0 +1,183 @@
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

View File

@ -0,0 +1,34 @@
from src.maze_builder import TextFileMazeBuilder
from src.pathfinding import BFSStrategy, DFSStrategy, AStarStrategy, DijkstraStrategy
# Загружаем лабиринт
builder = TextFileMazeBuilder()
maze = builder.build_from_file("data/demo_maze.txt")
print("Лабиринт:")
print(maze)
print(f"\nСтарт: ({maze.start.x}, {maze.start.y})")
print(f"Выход: ({maze.exit.x}, {maze.exit.y})")
# Тестируем все алгоритмы
strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy(), DijkstraStrategy()]
for strategy in strategies:
print(f"\n{'='*40}")
print(f"Алгоритм: {strategy.get_name()}")
print('='*40)
path, visited = strategy.find_path(maze, maze.start, maze.exit)
print(f"Посещено клеток: {visited}")
print(f"Длина пути: {len(path)}")
if path:
print("Путь найден!") # ← убрал f, или добавь переменную
if len(path) > 10:
print(f"Начало: {[(c.x, c.y) for c in path[:5]]}...")
print(f"Конец: ...{[(c.x, c.y) for c in path[-5:]]}")
else:
print(f"Путь: {[(c.x, c.y) for c in path]}")
else:
print("Путь не найден")