# Отчёт по лабораторной работе «Поиск выхода из лабиринта» ## 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 | Графическое представление результатов приведено на рисунке ниже. ![Сравнение производительности](performance_comparison.png) ## 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())`.