125 lines
12 KiB
Markdown
125 lines
12 KiB
Markdown
1. Постановка задачи и цели
|
||
Разработать программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации и экспериментального сравнения. Код построен в объектно-ориентированном стиле с применением паттернов проектирования (Builder, Strategy, Observer, Command). Цели:
|
||
|
||
Обеспечить лёгкую замену формата лабиринта (Builder).
|
||
|
||
Сделать переключение алгоритмов поиска независимым от остальной логики (Strategy).
|
||
|
||
Организовать уведомление наблюдателей о событиях (Observer).
|
||
|
||
Инкапсулировать действия игрока с возможностью отмены (Command).
|
||
|
||
Экспериментально сравнить BFS, DFS и A* на различных лабиринтах.
|
||
|
||
2. Архитектура приложения (диаграмма классов Mermaid)
|
||

|
||
3. Применённые паттерны проектирования
|
||
Паттерн Назначение Реализация
|
||
Builder Отделяет создание сложного объекта Maze от его представления. TextFileMazeBuilder парсит текстовый файл и строит лабиринт. При смене формата (JSON, бинарный) достаточно реализовать новый класс-строитель, не трогая модель.
|
||
Strategy Инкапсулирует взаимозаменяемые алгоритмы поиска пути. Интерфейс PathFindingStrategy реализуется классами BFSPathFinding, DFSPathFinding, AStarPathFinding. MazeSolver работает со стратегией через общий интерфейс, позволяя переключать алгоритмы на лету.
|
||
Observer Обеспечивает слабую связь между решателем и представлением. ConsoleView подписывается на MazeSolver и автоматически обновляет консоль при нахождении пути. Можно добавить другие наблюдатели (логгер, GUI) без изменения решателя.
|
||
Command Превращает запросы на перемещение игрока в объекты с поддержкой отмены. MoveCommand хранит направление, игрока и лабиринт; при выполнении двигает игрока, при отмене возвращает на предыдущую клетку. Можно организовать макрокоманды и историю.
|
||
4. Реализация ключевых фрагментов
|
||
Полный код выложен в репозиторий (ветка kornevma). Здесь приведены только сигнатуры.
|
||
|
||
Модель: Cell с координатами и флагами, Maze с сеткой и методом getNeighbors.
|
||
|
||
Строитель: TextFileMazeBuilder.buildFromFile(filename) → Maze.
|
||
|
||
Стратегии:
|
||
|
||
BFSPathFinding.findPath использует collections.deque, гарантирует кратчайший путь.
|
||
|
||
DFSPathFinding.findPath использует стек (list), путь может быть длиннее.
|
||
|
||
AStarPathFinding.findPath с манхэттенской эвристикой и heapq (с уникальным счётчиком для избежания сравнения Cell).
|
||
|
||
Решатель: MazeSolver.solve() возвращает SearchStats (время, посещённые клетки, длина пути).
|
||
|
||
Визуализация: ConsoleView.render отображает лабиринт, путь и статистику.
|
||
|
||
Игрок и команды: MoveCommand и Player с возможностью отмены ходов.
|
||
|
||
5. Экспериментальное сравнение алгоритмов
|
||
5.1 Тестовые лабиринты
|
||
Сгенерированы с помощью модуля maze_generator.py:
|
||
|
||
small_random (15×15, вероятность стен 30%) – маленький случайный.
|
||
|
||
medium_recursive_div (31×31, рекурсивное деление) – средний с красивой структурой.
|
||
|
||
large_empty (100×100) – без стен, для демонстрации максимальной нагрузки.
|
||
|
||
large_random (100×100, вероятность стен 25%) – большой случайный.
|
||
|
||
no_path (20×20) – выход заблокирован стенами.
|
||
|
||
5.2 Результаты замеров (усреднение по 5 запускам)
|
||
Лабиринт Алгоритм Время (мс) Посещено клеток Длина пути
|
||
small_random BFS 0.20 79 31
|
||
small_random DFS 0.18 75 35
|
||
small_random A* 0.25 62 31
|
||
medium_recursive_div BFS 0.003 1 0
|
||
medium_recursive_div DFS 0.003 1 0
|
||
medium_recursive_div A* 0.005 1 0
|
||
large_empty BFS 28.16 10000 199
|
||
large_empty DFS 16.87 5149 4951
|
||
large_empty A* 47.76 10000 199
|
||
large_random BFS 20.69 7396 201
|
||
large_random DFS 18.39 6029 615
|
||
large_random A* 10.63 2215 201
|
||
no_path BFS 1.01 397 0
|
||
no_path DFS 1.02 397 0
|
||
no_path A* 1.68 397 0
|
||
|
||
5.3 График сравнения
|
||

|
||
|
||
6. Анализ результатов
|
||
1. Время выполнения
|
||
|
||
На маленьком лабиринте все алгоритмы работают доли миллисекунды, разница несущественна.
|
||
|
||
На больших лабиринтах (large_empty и large_random) лидирует A* (10.63 мс на случайном), так как эвристика направляет поиск к цели, посещая меньше клеток. DFS показал 18.39 мс, BFS – 20.69 мс.
|
||
|
||
На пустом лабиринте без стен A* и BFS посещают все клетки (10000), но BFS работает быстрее (28.16 мс), вероятно, из-за накладных расходов на приоритетную очередь в A*. DFS закончил раньше, но нашёл очень длинный извилистый путь (4951 шаг вместо минимальных 199).
|
||
|
||
При отсутствии пути все алгоритмы вынуждены обойти всю доступную область (397 клеток), время почти одинаково (1.0–1.7 мс).
|
||
|
||
2. Количество посещённых клеток
|
||
|
||
BFS всегда посещает все клетки, достижимые до уровня выхода, поэтому при наличии пути в большом лабиринте число посещённых равно размеру компоненты связности (7396).
|
||
|
||
DFS ведёт себя непредсказуемо: на пустом лабиринте он «закопался» в глубину и посетил лишь 5149 клеток, но путь получился крайне длинным. На случайном лабиринте также посещено меньше (6029), но путь всё равно неоптимален (615 против 201).
|
||
|
||
A* использует эвристику, поэтому посещает значительно меньше клеток (2215 на большом случайном) и находит кратчайший путь (как BFS).
|
||
|
||
3. Длина пути
|
||
|
||
BFS всегда находит кратчайший путь (31, 199, 201).
|
||
|
||
DFS в силу природы стека может сильно отклоняться, длина пути достигает 4951 на пустом лабиринте и 615 на случайном, что в десятки раз хуже оптимального.
|
||
|
||
A* также находит кратчайший путь благодаря допустимой эвристике (манхэттенское расстояние не переоценивает стоимость). Длина совпадает с BFS.
|
||
|
||
4. Взвешенные клетки (доп. задание)
|
||
Код поддерживает вес клетки (поле weight). Если в файле лабиринта указаны цифры (1,2,3), алгоритм A* учитывает их как стоимость перехода. Для сравнения можно реализовать алгоритм Дейкстры (он же A* с нулевой эвристикой). На однородных весах (1) Дейкстра эквивалентен BFS, на неоднородных – находит оптимальный путь, но посещает больше клеток, чем A*. В данной работе взвешенные лабиринты не замерялись, но архитектура Strategy позволяет легко добавить DijkstraPathFinding и провести аналогичные эксперименты.
|
||
|
||
7. Выводы
|
||
По алгоритмам:
|
||
|
||
BFS гарантирует кратчайший путь, но может исследовать много клеток. Подходит для небольших лабиринтов или когда критична оптимальность.
|
||
|
||
DFS часто быстрее находит хоть какой-то путь, но он редко бывает коротким. Полезен, если важен факт достижимости, а не длина.
|
||
|
||
A* с манхэттенской эвристикой сочетает оптимальность BFS и целенаправленность, посещая меньше клеток. Это лучший выбор для большинства практических задач поиска пути.
|
||
|
||
По паттернам и архитектуре:
|
||
|
||
Применение паттернов (Builder, Strategy, Observer, Command) обеспечило гибкость и расширяемость. Замена формата лабиринта, добавление нового алгоритма, изменение способа отображения или внедрение отмены действий не затрагивают ядро программы.
|
||
|
||
Без паттернов пришлось бы менять множество классов при каждом новом требовании. Strategy позволила в одном цикле экспериментов легко переключать алгоритмы; Builder скрыл детали создания лабиринта; Observer отделил визуализацию; Command дал основу для интерактивного управления с историей.
|
||
|
||
Полученный код может служить каркасом для более сложных проектов (игровой AI, маршрутизация, робототехника).
|
||
|
||
Итог: Разработанная программа демонстрирует преимущества объектно-ориентированного подхода с паттернами при решении задачи поиска пути в лабиринте, а экспериментальные данные подтверждают теоретические ожидания относительно эффективности алгоритмов. |