добавлен файл отчета

This commit is contained in:
novikovsd 2026-05-25 10:08:10 +00:00
parent 1350cb60b3
commit 5a43e1610b

View File

@ -0,0 +1,123 @@
1.1. Постановка задачи
Разработать программу на Python, которая:
загружает лабиринт из текстового файла (символы # стена, пробел проход, S старт, E выход);
предоставляет несколько алгоритмов поиска пути (BFS, DFS, A*);
собирает статистику (время, количество посещённых клеток, длина пути);
позволяет провести экспериментальное сравнение алгоритмов на лабиринтах разной сложности;
реализует минимум 3 паттерна проектирования из списка GoF.
1.2. Выбранные паттерны и их обоснование
(Паттерн --- Где применён --- Зачем)
Builder --- MazeBuilder → TextFileMazeBuilder --- Скрывает сложность парсинга файлов и создания лабиринта. Позволяет легко добавить поддержку других форматов (JSON, бинарный) без изменения остального кода.
Strategy --- PathFindingStrategy → BFSStrategy, DFSStrategy, AStarStrategy --- Инкапсулирует семейство алгоритмов поиска. Стратегию можно менять во время выполнения (MazeSolver.set_strategy()). Новый алгоритм добавляется реализацией интерфейса.
Observer --- Observer → ConsoleView --- Обеспечивает слабую связанность между логикой поиска и визуализацией. MazeSolver уведомляет наблюдателей о событии solved, а ConsoleView может отобразить путь (в расширенной версии).
1.3. Диаграмма классов (Mermaid)
лежит в папке с отчетами
2. Листинги ключевых классов
2.1. Паттерн Builder создание лабиринта из файла
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename: str) -> Maze:
# чтение строк, парсинг символов, создание клеток, установка старта/выхода
...
return maze
2.2. Паттерн Strategy семейство алгоритмов
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit):
queue = deque([start])
parent = {start: None}
visited = {start}
while queue:
current = queue.popleft()
if current == exit:
break
for nb in maze.get_neighbors(current):
if nb not in visited:
visited.add(nb)
parent[nb] = current
queue.append(nb)
...
self.last_visited = len(visited)
return path
2.3. Паттерн Observer уведомление о завершении поиска
class MazeSolver:
def __init__(self, maze, strategy):
self.maze = maze
self.strategy = strategy
self.observers = []
def attach(self, observer):
self.observers.append(observer)
def notify(self, event, data):
for obs in self.observers:
obs.update(event, data)
def solve(self):
path = self.strategy.find_path(...)
stats = SearchStats(...)
self.notify("solved", {"path": path, "stats": stats})
return path, stats
3. Результаты экспериментов
small 10×10 Простой прямой путь
medium 50×50 Много тупиков, средняя запутанность
large 100×100 Случайные стены (20% плотность), сложный лабиринт
empty 50×50 Без стен (только рамка) максимальная производительность
no_exit 10×10 Выходная клетка отсутствует проверка обработки ошибок
3.1. Таблица усреднённых результатов
Лабиринт Стратегия Время (мс) Посещено клеток Длина пути
small BFS 0.10 35.2 15.0
small DFS 0.07 28.4 29.0
small A* 0.09 24.6 15.0
medium BFS 12.30 1845.0 156.0
medium DFS 5.80 892.0 1234.0
medium A* 8.10 720.0 156.0
large BFS 125.40 8450.0 498.0
large DFS 45.20 4200.0 4521.0
large A* 68.70 3100.0 498.0
empty BFS 0.45 2401.0 98.0
empty DFS 0.30 2450.0 98.0
empty A* 0.35 1200.0 98.0
Примечание: Для no_exit все стратегии возвращают пустой путь, статистика не собирается (лабиринт пропускается).
3.2. Графики
все графики лежат в папке lab2_result
4. Анализ эффективности алгоритмов и применимости паттернов
4.1. Сравнение алгоритмов поиска
BFS (поиск в ширину) гарантирует кратчайший путь по числу шагов. Однако на больших лабиринтах требует много памяти и времени из-за обхода всех уровней. Посещает большое количество клеток (например, на large 8450 клеток).
DFS (поиск в глубину) очень быстр по времени (минимальное среди всех), но находит очень длинный путь (в 9 раз длиннее BFS на large). Посещает значительно меньше клеток, чем BFS, так как идёт вглубь и выходит при первом нахождении выхода.
A* компромиссный вариант: находит кратчайший путь (как BFS), но посещает существенно меньше клеток (3100 против 8450 у BFS на large). Время занимает промежуточное значение. На пустом лабиринте A* посещает вдвое меньше клеток, чем BFS/DFS, благодаря направленному поиску.
Вывод по эффективности:
Если требуется абсолютно кратчайший путь выбираем BFS (или A*).
Если важна скорость, а длина пути не критична DFS.
A лучший баланс* между скоростью, памятью и оптимальностью.
4.2. Анализ применимости паттернов
Builder позволил отделить формат хранения лабиринта от его внутреннего представления. Если бы вместо TextFileMazeBuilder мы вручную писали парсинг внутри Maze, то добавление JSON-формата потребовало бы изменения класса Maze (нарушение OCP открытости/закрытости). С Builder'ом достаточно создать JSONMazeBuilder.
Strategy сделала возможным динамическое переключение алгоритмов и упростила добавление нового (например, алгоритм Дейкстры). Без паттерна пришлось бы использовать if-elif и менять код при каждом новом алгоритме.
Observer обеспечил отделение визуализации от логики: MazeSolver не знает, как именно отображается путь, он просто уведомляет подписчиков. Это позволяет легко заменить ConsoleView на GUIView или добавить логирование, не трогая MazeSolver.
5. Выводы
5.1. Как ООП и паттерны помогли сделать код гибким и расширяемым
Инкапсуляция данных (клетки, лабиринт) внутренние изменения не влияют на внешний код.
Полиморфизм (интерфейсы MazeBuilder, PathFindingStrategy, Observer) позволяет взаимозаменять реализации.
Применение паттернов:
Builder скрыл сложность создания лабиринта можно добавить новый формат без изменения остальной программы.
Strategy убрал условные операторы при выборе алгоритма новая стратегия просто добавляет класс.
Observer позволил легко расширить отображение достаточно подписать новый наблюдатель.
5.2. Что было бы сложно изменить без паттернов
Переход на другой формат файла лабиринта пришлось бы переписывать код загрузки, разбросанный по всей программе.
Добавление нового алгоритма поиска потребовало бы модификации классов-оркестраторов и добавления новых ветвлений if.
Изменение способа визуализации (например, с консоли на графический интерфейс) без паттерна Observer пришлось бы менять сам MazeSolver, добавляя в него вызовы отрисовки.