9.2 KiB
Отчёт по лабораторной работе «Поиск выхода из лабиринта»
1. Цель работы
Разработать гибкую расширяемую программу для загрузки лабиринта из текстового файла, поиска пути от старта до выхода с возможностью выбора алгоритма и экспериментального сравнения алгоритмов. В ходе работы применены паттерны проектирования GoF: Builder, Strategy, Observer, Command.
Реализованные алгоритмы поиска пути:
- BFS (поиск в ширину) — гарантирует кратчайший путь по числу шагов.
- DFS (поиск в глубину) — не гарантирует кратчайший путь, но быстрее при удачном порядке соседей.
- A* (с манхэттенской эвристикой) — направленный поиск, учитывает веса клеток.
- Dijkstra — оптимален для взвешенных графов, без эвристики.
Тестовые лабиринты:
- Маленький 10×10 — простой путь.
- Средний 50×50 — с тупиками (алгоритм Прима).
- Большой 100×100 — запутанная структура.
- Пустой 30×30 — без стен, демонстрирует максимальную нагрузку.
- Без выхода 20×20 — старт и выход разделены глухой стеной.
- Взвешенный 40×40 — клетки с разным весом: асфальт (1), песок (2), болото (3).
Каждый эксперимент повторялся 7 раз, результаты усреднены.
2. Описание паттернов
| Паттерн | Классы | Назначение |
|---|---|---|
| Builder | MazeBuilder, TextFileMazeBuilder |
Скрывает парсинг файла; новый формат = новый класс |
| Strategy | PathFindingStrategy, BFS/DFS/A*/Dijkstra |
Смена алгоритма одной строкой без изменения остального кода |
| Observer | Observer, ConsoleView |
Визуализация отделена от логики поиска |
| Command | Command, MoveCommand, CommandHistory |
Пошаговое управление игроком с поддержкой undo |
3. Результаты экспериментов
Усреднённые значения (7 повторений) представлены в таблице:
| Лабиринт | Алгоритм | Время, мс | Посещено клеток | Длина пути |
|---|---|---|---|---|
| 10×10 | BFS | 0.081 | 28 | 21 |
| 10×10 | DFS | 0.053 | 22 | 21 |
| 10×10 | A* | 0.088 | 24 | 21 |
| 10×10 | Dijkstra | 0.672 | 28 | 21 |
| 50×50 | BFS | 1.150 | 493 | 257 |
| 50×50 | DFS | 0.614 | 263 | 257 |
| 50×50 | A* | 1.220 | 357 | 257 |
| 50×50 | Dijkstra | 1.685 | 493 | 257 |
| 100×100 | BFS | 11.378 | 4783 | 1953 |
| 100×100 | DFS | 5.141 | 2161 | 1953 |
| 100×100 | A* | 18.019 | 4741 | 1953 |
| 100×100 | Dijkstra | 17.489 | 4783 | 1953 |
| 30×30 пустой | BFS | 1.832 | 784 | 55 |
| 30×30 пустой | DFS | 1.151 | 433 | 379 |
| 30×30 пустой | A* | 3.748 | 784 | 55 |
| 30×30 пустой | Dijkstra | 3.945 | 784 | 55 |
| 20×20 без выхода | BFS | 0.370 | 162 | — |
| 20×20 без выхода | DFS | 0.373 | 162 | — |
| 20×20 без выхода | A* | 0.708 | 162 | — |
| 20×20 без выхода | Dijkstra | 0.677 | 162 | — |
| 40×40 взвешенный | BFS | 1.104 | 533 | 321 |
| 40×40 взвешенный | DFS | 0.774 | 361 | 321 |
| 40×40 взвешенный | A* | 1.516 | 452 | 321 |
| 40×40 взвешенный | Dijkstra | 1.725 | 533 | 321 |
Графическое представление результатов приведено на рисунке ниже.
4. Анализ результатов
4.1. BFS
Гарантирует кратчайший путь по числу шагов. Исследует все клетки на расстоянии d перед переходом к d+1, поэтому число посещённых клеток максимально среди всех алгоритмов — на лабиринте 100×100 это 4783 клетки. На пустом лабиринте 30×30 BFS находит оптимальный путь длиной 55 клеток, тогда как DFS даёт 379. Время растёт линейно с размером: от 0.08 мс (10×10) до 11.4 мс (100×100).
4.2. DFS
Самый быстрый алгоритм по времени: на лабиринте 100×100 — 5.1 мс против 11.4 мс у BFS. Посещает вдвое меньше клеток (2161 против 4783), так как уходит глубоко в одном направлении. Однако на пустом лабиринте DFS даёт путь в 7 раз длиннее оптимального (379 против 55) — алгоритм уходит в угол и обходит весь лабиринт по периметру. Оптимальная длина пути при этом совпадает с BFS только в лабиринтах-лабиринтах (50×50 и 100×100), где единственный путь — сам.
4.3. A*
На невзвешенных лабиринтах находит тот же кратчайший путь, что BFS, но исследует на 20–30% меньше клеток благодаря манхэттенской эвристике (на 50×50: 357 против 493). Однако на большом лабиринте 100×100 оказывается медленнее BFS (18 мс против 11 мс) из-за накладных расходов на heapq. На взвешенном лабиринте A* корректно учитывает стоимость клеток, в отличие от BFS.
4.4. Dijkstra
На невзвешенных лабиринтах полностью совпадает с BFS по посещённым клеткам и длине пути, но медленнее из-за heapq вместо deque. На взвешенном лабиринте 40×40 корректно минимизирует суммарный вес пути. Практически вытесняется A* везде, где цель заранее известна.
4.5. Лабиринт «без выхода»
Все алгоритмы обходят все 162 доступные клетки левой секции и возвращают пустой путь. Реализация корректно обрабатывает этот случай через событие no_path для Observer. A* и Dijkstra работают медленнее (0.7 мс против 0.37 мс у BFS/DFS) из-за накладных расходов heapq при полном обходе без нахождения цели.
5. Выводы и рекомендации
На основе полученных результатов можно сформулировать следующие рекомендации:
-
Кратчайший путь в невзвешенном лабиринте → BFS. Гарантированный результат, простая реализация на
deque, линейное масштабирование. -
Максимальная скорость, длина пути не критична → DFS. В 2 раза быстрее BFS на больших лабиринтах, посещает вдвое меньше клеток. Не использовать на открытых пространствах — путь может быть многократно длиннее оптимального.
-
Взвешенный граф, цель известна → A*. Направленный поиск + учёт весов. На небольших и средних лабиринтах быстрее и экономнее BFS. На очень больших (100×100+) накладные расходы
heapqмогут перевесить выигрыш от эвристики. -
Взвешенный граф, нужны все кратчайшие расстояния → Dijkstra. Оптимален без целевой точки. С целевой точкой предпочтительнее A*.
Итог: для навигации в лабиринте с одним выходом оптимален BFS (гарантия) или DFS (скорость). A* предпочтителен при взвешенных клетках. Паттерн Strategy позволяет переключать алгоритмы без изменения остального кода — solver.set_strategy(AStarStrategy()).
