import sys import os import time import csv from collections import deque import heapq import matplotlib.pyplot as plt import numpy as np # ========== Модель данных ========== class Tile: def __init__(self, x, y): self._x = x self._y = y self._wall = False self._entry = False self._goal = False @property def x(self): return self._x @property def y(self): return self._y @property def is_wall(self): return self._wall @is_wall.setter def is_wall(self, value): self._wall = value @property def is_entry(self): return self._entry @is_entry.setter def is_entry(self, value): self._entry = value @property def is_goal(self): return self._goal @is_goal.setter def is_goal(self, value): self._goal = value def can_walk(self): return not self._wall class Labyrinth: def __init__(self, width, height): self._width = width self._height = height self._grid = [[Tile(x, y) for x in range(width)] for y in range(height)] self._start = None self._exit = None @property def width(self): return self._width @property def height(self): return self._height @property def start(self): return self._start @property def exit(self): return self._exit def tile_at(self, x, y): if 0 <= x < self._width and 0 <= y < self._height: return self._grid[y][x] return None def configure_tile(self, x, y, kind): tile = self.tile_at(x, y) if tile is None: return if kind == 'wall': tile.is_wall = True elif kind == 'entry': if self._start: self._start.is_entry = False tile.is_entry = True tile.is_wall = False self._start = tile elif kind == 'goal': if self._exit: self._exit.is_goal = False tile.is_goal = True tile.is_wall = False self._exit = tile elif kind == 'floor': tile.is_wall = False def neighbours(self, tile): res = [] for dx, dy in ((0, -1), (0, 1), (-1, 0), (1, 0)): nb = self.tile_at(tile.x + dx, tile.y + dy) if nb and nb.can_walk(): res.append(nb) return res # ========== Загрузка из файла ========== class TextLabyrinthBuilder: def build(self, filename): with open(filename, 'r', encoding='utf-8') as f: lines = [line.rstrip('\n') for line in f] h = len(lines) w = max(len(l) for l in lines) if h else 0 lab = Labyrinth(w, h) for y, row in enumerate(lines): for x, ch in enumerate(row): if ch == '#': lab.configure_tile(x, y, 'wall') elif ch == 'S': lab.configure_tile(x, y, 'entry') elif ch == 'E': lab.configure_tile(x, y, 'goal') elif ch == ' ': lab.configure_tile(x, y, 'floor') return lab # ========== Алгоритмы поиска ========== class BFS_Pathfinder: def find_path(self, lab, start, goal): if goal is None: self._visited = 0 return [] q = deque([start]) preds = {start: None} seen = {start} while q: cur = q.popleft() if cur == goal: self._visited = len(seen) return self._build_path(preds, start, goal) for nb in lab.neighbours(cur): if nb not in seen: seen.add(nb) preds[nb] = cur q.append(nb) self._visited = len(seen) return [] def _build_path(self, preds, start, goal): path = [] cur = goal while cur is not None: path.append(cur) cur = preds.get(cur) path.reverse() return path @property def visited_count(self): return getattr(self, '_visited', 0) class DFS_Pathfinder: def find_path(self, lab, start, goal): if goal is None: self._visited = 0 return [] stack = [start] preds = {start: None} seen = {start} while stack: cur = stack.pop() if cur == goal: self._visited = len(seen) return self._build_path(preds, start, goal) for nb in lab.neighbours(cur): if nb not in seen: seen.add(nb) preds[nb] = cur stack.append(nb) self._visited = len(seen) return [] def _build_path(self, preds, start, goal): path = [] cur = goal while cur is not None: path.append(cur) cur = preds.get(cur) path.reverse() return path @property def visited_count(self): return getattr(self, '_visited', 0) class AStar_Pathfinder: def _heuristic(self, a, b): return abs(a.x - b.x) + abs(a.y - b.y) def find_path(self, lab, start, goal): if goal is None: self._visited = 0 return [] heap = [] cnt = 0 f_start = self._heuristic(start, goal) heapq.heappush(heap, (f_start, cnt, start)) cnt += 1 preds = {} g = {start: 0} f = {start: f_start} seen = set() while heap: cur_f, _, cur = heapq.heappop(heap) seen.add(cur) if cur == goal: self._visited = len(seen) return self._build_path(preds, start, goal) if cur_f > f.get(cur, float('inf')): continue for nb in lab.neighbours(cur): tent_g = g[cur] + 1 if tent_g < g.get(nb, float('inf')): preds[nb] = cur g[nb] = tent_g new_f = tent_g + self._heuristic(nb, goal) f[nb] = new_f heapq.heappush(heap, (new_f, cnt, nb)) cnt += 1 self._visited = len(seen) return [] def _build_path(self, preds, start, goal): path = [] cur = goal while cur is not None: path.append(cur) cur = preds.get(cur) path.reverse() return path @property def visited_count(self): return getattr(self, '_visited', 0) class LabyrinthSolver: def __init__(self, lab): self._lab = lab self._strategy = None def set_strategy(self, strategy): self._strategy = strategy def solve(self): start_t = time.perf_counter() path = self._strategy.find_path(self._lab, self._lab.start, self._lab.exit) elapsed = (time.perf_counter() - start_t) * 1000 return { 'time_ms': elapsed, 'visited': self._strategy.visited_count, 'length': len(path) } # ========== Графики ========== def create_charts(results, save_filename='maze_performance.png'): mazes = list(set([r['maze'] for r in results])) strategies = ['BFS', 'DFS', 'AStar'] data = {s: {m: None for m in mazes} for s in strategies} for r in results: data[r['strategy']][r['maze']] = r times = {s: [data[s][m]['time_ms'] for m in mazes] for s in strategies} visited = {s: [data[s][m]['visited_cells'] for m in mazes] for s in strategies} lengths = {s: [data[s][m]['path_length'] for m in mazes] for s in strategies} fig, axes = plt.subplots(1, 3, figsize=(15, 5)) x = np.arange(len(mazes)) width = 0.25 # Время axes[0].bar(x - width, times['BFS'], width, label='BFS', color='blue', alpha=0.7) axes[0].bar(x, times['DFS'], width, label='DFS', color='green', alpha=0.7) axes[0].bar(x + width, times['AStar'], width, label='A*', color='red', alpha=0.7) axes[0].set_xlabel('Лабиринты') axes[0].set_ylabel('Время (мс)') axes[0].set_title('Время выполнения алгоритмов') axes[0].set_xticks(x) axes[0].set_xticklabels(mazes, rotation=15, ha='right', fontsize=8) axes[0].legend() axes[0].grid(True, alpha=0.3) # Посещённые клетки axes[1].bar(x - width, visited['BFS'], width, label='BFS', color='blue', alpha=0.7) axes[1].bar(x, visited['DFS'], width, label='DFS', color='green', alpha=0.7) axes[1].bar(x + width, visited['AStar'], width, label='A*', color='red', alpha=0.7) axes[1].set_xlabel('Лабиринты') axes[1].set_ylabel('Количество клеток') axes[1].set_title('Посещённые клетки') axes[1].set_xticks(x) axes[1].set_xticklabels(mazes, rotation=15, ha='right', fontsize=8) axes[1].legend() axes[1].grid(True, alpha=0.3) # Длина пути axes[2].bar(x - width, lengths['BFS'], width, label='BFS', color='blue', alpha=0.7) axes[2].bar(x, lengths['DFS'], width, label='DFS', color='green', alpha=0.7) axes[2].bar(x + width, lengths['AStar'], width, label='A*', color='red', alpha=0.7) axes[2].set_xlabel('Лабиринты') axes[2].set_ylabel('Длина пути') axes[2].set_title('Длина найденного пути') axes[2].set_xticks(x) axes[2].set_xticklabels(mazes, rotation=15, ha='right', fontsize=8) axes[2].legend() axes[2].grid(True, alpha=0.3) plt.suptitle('Сравнение алгоритмов поиска пути', fontsize=14, fontweight='bold') plt.tight_layout() plt.savefig(save_filename, dpi=300, bbox_inches='tight') print(f"\nГрафик сохранён в файл: {save_filename}") plt.show() # ========== Основная программа ========== def main(): print('\n' + '=' * 60) print('ПОИСК ВЫХОДА ИЗ ЛАБИРИНТА') print('Сравнение алгоритмов BFS, DFS и A*') print('=' * 60) # Находим все txt файлы в текущей директории maze_files = [f for f in os.listdir('.') if f.endswith('.txt') and f.startswith('maze_')] if not maze_files: print("\nНет файлов с лабиринтами (maze_*.txt)") return print(f"\nНайдено лабиринтов: {len(maze_files)}") for f in maze_files: print(f" - {f}") strategies = [ ('BFS', BFS_Pathfinder()), ('DFS', DFS_Pathfinder()), ('AStar', AStar_Pathfinder()) ] all_results = [] builder = TextLabyrinthBuilder() for maze_file in sorted(maze_files): maze_name = maze_file.replace('.txt', '').replace('maze_', '').replace('_', ' ').title() print(f"\nОбработка лабиринта: {maze_name}") lab = builder.build(maze_file) for sname, strat in strategies: solver = LabyrinthSolver(lab) solver.set_strategy(strat) stats = solver.solve() all_results.append({ 'maze': maze_name, 'strategy': sname, 'time_ms': stats['time_ms'], 'visited_cells': stats['visited'], 'path_length': stats['length'] }) status = "Найден" if stats['length'] > 0 else "Не найден" print(f" {sname}: {status} (время: {stats['time_ms']:.3f} мс, посещено: {stats['visited']}, длина: {stats['length']})") # Сохранение в CSV print('\n' + '=' * 60) print('СОХРАНЕНИЕ РЕЗУЛЬТАТОВ В CSV') print('=' * 60) csv_filename = 'experiment_results.csv' with open(csv_filename, 'w', newline='', encoding='utf-8') as csvfile: fieldnames = ['лабиринт', 'стратегия', 'время_мс', 'посещено_клеток', 'длина_пути'] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() for result in all_results: writer.writerow({ 'лабиринт': result['maze'], 'стратегия': result['strategy'], 'время_мс': f"{result['time_ms']:.3f}", 'посещено_клеток': result['visited_cells'], 'длина_пути': result['path_length'] }) print(f"\nРезультаты сохранены в файл: {csv_filename}") # Вывод таблицы в консоль print('\n' + '=' * 100) print('ТАБЛИЦА РЕЗУЛЬТАТОВ') print('=' * 100) print(f"{'Лабиринт':<20} {'Стратегия':<10} {'Время (мс)':<12} {'Посещено':<12} {'Длина пути':<12}") print('-' * 100) for r in all_results: print(f"{r['maze']:<20} {r['strategy']:<10} {r['time_ms']:<12.3f} {r['visited_cells']:<12.0f} {r['path_length']:<12.0f}") print('=' * 100) # Создание графика print('\nСОЗДАНИЕ ГРАФИКА...') create_charts(all_results, 'maze_performance.png') # Статистика print('\n' + '=' * 60) print('СТАТИСТИКА') print('=' * 60) for strategy in ['BFS', 'DFS', 'AStar']: strategy_results = [r for r in all_results if r['strategy'] == strategy and r['path_length'] > 0] if strategy_results: avg_time = sum(r['time_ms'] for r in strategy_results) / len(strategy_results) avg_visited = sum(r['visited_cells'] for r in strategy_results) / len(strategy_results) avg_length = sum(r['path_length'] for r in strategy_results) / len(strategy_results) print(f'\n{strategy}:') print(f' Среднее время: {avg_time:.3f} мс') print(f' Среднее посещено: {avg_visited:.0f} клеток') print(f' Средняя длина пути: {avg_length:.1f}') print('\n' + '=' * 60) print('ГОТОВО') print(f'Результаты сохранены в:') print(f' - {csv_filename} (таблица)') print(f' - maze_performance.png (график)') print('=' * 60) if __name__ == '__main__': main()