2026-rff_mp/soninrv/docs/report2.md
2026-05-25 03:26:25 +03:00

9.2 KiB
Raw Blame History

Отчёт по лабораторной работе «Поиск выхода из лабиринта»

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, но исследует на 2030% меньше клеток благодаря манхэттенской эвристике (на 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()).