2026-rff_mp/BorisovMI/lab_2/docs/data/report2.md

9.9 KiB
Raw Blame History

Отчёт: Задание 2 — Поиск выхода из лабиринта

Цель работы

Разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов

Выбранные паттерны и их обоснование

Builder

Для загрузки лабиринта из файла был использован паттерн Builder. Создан интерфейс: class MazeBuilder(): и его реализация: class TextFileMazeBuilder(MazeBuilder): Преимущества использования Builder: пользоватеь не знает деталей создания лабиринта; можно добавить новые форматы (JSON, XML, бинарный); код загрузки изолирован от остальной программы.

Strategy

Для алгоритмов поиска пути использован паттерн Strategy Создан общий интерфейс: class PathFindingStrategy(): Реализованы стратегии: BFSStrategy; DFSStrategy; AStrategy; Каждая стратегия реализует собственный алгоритм поиска пути по правилам. Преимущества паттерна: алгоритмы можно менять во время выполнения; код MazeSolver не зависит от конкретного алгоритма; новые алгоритмы можно добавлять без изменения существующего кода.

Observer

Для уведомления интерфейса о событиях использован паттерн Observer Создан интерфейс: class Observer(): и реализация: class ConsoleView(Observer): MazeSolver хранит список наблюдателей и уведомляет их о событиях: начало поиска; окончание поиска; перемещение игрока. Преимущества: логика интерфейса отделена от логики поиска; можно легко добавить графический интерфейс;

Command

Для пошагового перемещения игрока использован паттерн Command. Создан интерфейс: class Command(): и реализация: class MoveCommand(Command): Каждая команда умеет: execute() — выполнить действие; undo() — отменить действие Преимущества: поддержка undo; возможность расширения системы команд

Листинги ключевых классов

Паттерн Strategy

class PathFindingStrategy: def findPath(self, maze, start, exit): pass

class BFSStrategy(PathFindingStrategy): def findPath(self, maze, start, exit): if exit is None: return [] queue = deque([start]) visited = {start} parent = {start: None}

    while queue:
        current = queue.popleft()
        
        if current == exit:
            return self._reconstruct_path(parent, start, exit)
        
        for neighbor in maze.getNeighbors(current):
            if neighbor not in visited:
                visited.add(neighbor)
                parent[neighbor] = current
                queue.append(neighbor)
    
    return []  

class AStrategy(PathFindingStrategy): def _heuristic(self, cell, exit): if exit is None: return 0 return abs(cell.x - exit.x) + abs(cell.y - exit.y)

def findPath(self, maze, start, exit):
    if exit is None:
        return []
    open_set = []
    heapq.heappush(open_set, (0, start))
    
    came_from = {start: None}
    g_score = {start: 0}
    
    while open_set:
        current = heapq.heappop(open_set)[1]
        
        if current == exit:
            return self._reconstruct_path(came_from, start, exit)
        
        for neighbor in maze.getNeighbors(current):
            tentative_g = g_score[current] + 1
            
            if neighbor not in g_score or tentative_g < g_score[neighbor]:
                came_from[neighbor] = current
                g_score[neighbor] = tentative_g
                f_score = tentative_g + self._heuristic(neighbor, exit)
                heapq.heappush(open_set, (f_score, neighbor))
    
    return []

Паттерн Command

class Command: def execute(self): pass def undo(self): pass

class MoveCommand(Command): def init(self, player, direction, maze): self.player = player self.dx, self.dy = direction self.maze = maze self.executed = False

def execute(self):
    new_x = self.player.currentCell.x + self.dx
    new_y = self.player.currentCell.y + self.dy
    new_cell = self.maze.getCell(new_x, new_y)
    if new_cell and new_cell.isPassable():
        self.player.moveTo(new_cell)
        self.executed = True
        return True
    return False

def undo(self):
    if self.executed:
        self.player.undoMove()
        self.executed = False
        return True
    return False

Результаты

| Лабиринт | Стратегия | Время (с) | Посещено | Длина пути | Путь найден | |---|---|---|---|---| | маленький (10x10) | BFS | 0.9148200158961117 | 19.0 | 19.0 | True | | маленький (10x10) | DFS | 0.717819994315505 | 39.0 | 39.0 | True | | маленький (10x10) | A | 1.577159995213151 | 19.0 | 19.0 | True | | средний (50x50) | BFS | 14.496059995144606 | 99.0 | 99.0 | True | | средний (50x50) | DFS | 8.470179990399629 | 393.0 | 393.0 |True | | средний (50x50) | A | 9.11291999509558 | 99.0 |99.0 | True | | большой (100x100) | BFS | 0.013179995585232973 | 0.0 | 0.0 | False | | большой (100x100) | A | 0.013079994823783636 | 0.0 | 0.0 | False | | пустой (50x50) | BFS | 29.2012800113298 | 99.0 | 99.0 | True | | пустой (50x50) | DFS | 13.176999986171722 | 1275.0 | 1275.0 | True | | пустой (50x50) | A | 50.366899999789894 | 99.0 | 99.0 | True | | без выхода (20x20) | BFS | 0.004239997360855341 | 0.0 | 0.0 | False | | без выхода (20x20) | DFS | 0.006399990525096655 | 0.0 | 0.0 | False | | без выхода (20x20) | A | 0.008680007886141539 | 0.0 | 0.0 | False |

Графики

Сравнение длины

Сравнение времён

Анализ эффективности алгоритмов

В ходе экспериментов были получены следующие результаты.

BFS

Преимущества: всегда находит кратчайший путь; простая реализация. Недостатки: посещает большое количество клеток; требует много памяти. Выходит, что наиболее эффективен в небольших невзвешенных лабиринтах.

DFS

Преимущества: простая реализация; самым быстрым находит произвольный путь. Недостатки: не гарантирует кратчайший путь; может уходить в тупики. Подходит для быстрого поиска любого решения.

A

Преимущества: высокая скорость; посещает меньше клеток; Недостатки: требует выбора хорошей эвристики. Показал хорошие результаты на больших лабиринтах.

Анализ применимости паттернов

Builder

Без Builder код загрузки лабиринта был бы жёстко связан с классом Maze, а добавление нового формата потребовало бы изменения существующего кода. Strategy Без Strategy пришлось бы: хранить все алгоритмы внутри одного класса; использовать большое количество условных операторов; изменять код MazeSolver при добавлении новых алгоритмов Strategy помог полностью отделить алгоритмы друг от друга.

Observer

Без Observer логика интерфейса смешивалась бы с логикой поиска. Это усложнило бы: добавление GUI; логирование; визуализацию.

Command

Без Command было бы сложно реализовать: undo; историю действий; расширяемую систему управления.

Выводы

В проекте были успешно реализованы:

загрузка лабиринта из файла; несколько алгоритмов поиска пути; визуализация; система наблюдателей; система команд; экспериментальное сравнение алгоритмов.

Использование паттернов GoF позволило:

сделать архитектуру гибкой; уменьшить связанность компонентов; упростить расширение программы; облегчить сопровождение кода.