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

10 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 позволило:

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