[2] Добавлен паттерн Strategy с алгоритмами поиска пути (BFS, DFS, A*, Дейкстра)
This commit is contained in:
parent
4db9491cec
commit
0c93c3a3b0
183
pogodinda/lab2/src/pathfinding.py
Normal file
183
pogodinda/lab2/src/pathfinding.py
Normal 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
|
||||
34
pogodinda/lab2/tests/test_strategy.py
Normal file
34
pogodinda/lab2/tests/test_strategy.py
Normal 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("Путь не найден")
|
||||
Loading…
Reference in New Issue
Block a user