Compare commits
13 Commits
develop
...
dyachenkoa
| Author | SHA1 | Date | |
|---|---|---|---|
| 71d9bf8339 | |||
| ec01a3af43 | |||
| 7325d69823 | |||
| b57eeefef4 | |||
| 5a9f756791 | |||
| 1cae526635 | |||
| 5334da75f5 | |||
| 5be7d7481b | |||
| a1b9cec2d7 | |||
| c052fb7ffb | |||
| 2601cfc882 | |||
| 77e11608ff | |||
| 005953e4ec |
16
dyachenkoas/docs2/data2/experiment_results.csv
Normal file
16
dyachenkoas/docs2/data2/experiment_results.csv
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
лабиринт,стратегия,время_мс,посещено_клеток,длина_пути
|
||||||
|
Empty,BFS,0.158,162,26
|
||||||
|
Empty,DFS,0.096,162,94
|
||||||
|
Empty,AStar,0.290,162,26
|
||||||
|
Large,BFS,0.194,220,75
|
||||||
|
Large,DFS,0.073,77,75
|
||||||
|
Large,AStar,0.337,216,75
|
||||||
|
Medium,BFS,0.156,179,34
|
||||||
|
Medium,DFS,0.038,44,38
|
||||||
|
Medium,AStar,0.140,85,34
|
||||||
|
No Exit,BFS,0.001,0,0
|
||||||
|
No Exit,DFS,0.000,0,0
|
||||||
|
No Exit,AStar,0.000,0,0
|
||||||
|
Small,BFS,0.022,22,16
|
||||||
|
Small,DFS,0.016,19,16
|
||||||
|
Small,AStar,0.033,21,16
|
||||||
|
425
dyachenkoas/docs2/data2/main.py
Normal file
425
dyachenkoas/docs2/data2/main.py
Normal file
|
|
@ -0,0 +1,425 @@
|
||||||
|
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()
|
||||||
11
dyachenkoas/docs2/data2/maze_empty.txt
Normal file
11
dyachenkoas/docs2/data2/maze_empty.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
####################
|
||||||
|
#S #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# E#
|
||||||
|
####################
|
||||||
39
dyachenkoas/docs2/data2/maze_large.txt
Normal file
39
dyachenkoas/docs2/data2/maze_large.txt
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
########################################
|
||||||
|
#S #
|
||||||
|
# # ################################### #
|
||||||
|
# # # # #
|
||||||
|
# # # ############################### # #
|
||||||
|
# # # # # # #
|
||||||
|
# # # # ########################### # # #
|
||||||
|
# # # # # # # # #
|
||||||
|
# # # # # ####################### # # # #
|
||||||
|
# # # # # # # # # # #
|
||||||
|
# # # # # # ################### # # # # #
|
||||||
|
# # # # # # # # # # # # #
|
||||||
|
# # # # # # # ############### # # # # # #
|
||||||
|
# # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # ########### # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # ####### # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # # ### # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # # ##### # # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # # ######### # # # # # # # #
|
||||||
|
# # # # # # # # # # # # # # # #
|
||||||
|
# # # # # # # ############# # # # # # # #
|
||||||
|
# # # # # # # # # # # # # #
|
||||||
|
# # # # # # ################# # # # # # #
|
||||||
|
# # # # # # # # # # # #
|
||||||
|
# # # # # ##################### # # # # #
|
||||||
|
# # # # # # # # # #
|
||||||
|
# # # # ######################### # # # #
|
||||||
|
# # # # # # # #
|
||||||
|
# # # ############################# # # #
|
||||||
|
# # # # # #
|
||||||
|
# # ################################# # #
|
||||||
|
# # # #
|
||||||
|
# ##################################### #
|
||||||
|
# E#
|
||||||
|
########################################
|
||||||
20
dyachenkoas/docs2/data2/maze_medium.txt
Normal file
20
dyachenkoas/docs2/data2/maze_medium.txt
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
####################
|
||||||
|
#S #
|
||||||
|
# ### ########### #
|
||||||
|
# # # # # #
|
||||||
|
# # # ### ### # # #
|
||||||
|
# # # # # #
|
||||||
|
### ### # # ### ###
|
||||||
|
# # # # #
|
||||||
|
# # ########### # #
|
||||||
|
# # # # #
|
||||||
|
# # ### # ### # # #
|
||||||
|
# # # # # # # #
|
||||||
|
# ### # # # ### # #
|
||||||
|
# # # # #
|
||||||
|
# ### ######### # #
|
||||||
|
# # # #
|
||||||
|
# # ########### # #
|
||||||
|
# # # # #
|
||||||
|
# # E#
|
||||||
|
####################
|
||||||
9
dyachenkoas/docs2/data2/maze_no_exit.txt
Normal file
9
dyachenkoas/docs2/data2/maze_no_exit.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
###########
|
||||||
|
#S #
|
||||||
|
# ####### #
|
||||||
|
# # #
|
||||||
|
####### # #
|
||||||
|
# # #
|
||||||
|
# ####### #
|
||||||
|
# # #
|
||||||
|
###########
|
||||||
7
dyachenkoas/docs2/data2/maze_small.txt
Normal file
7
dyachenkoas/docs2/data2/maze_small.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
##########
|
||||||
|
#S #
|
||||||
|
# # #### #
|
||||||
|
# # #
|
||||||
|
#### # ##
|
||||||
|
# # E#
|
||||||
|
##########
|
||||||
210
dyachenkoas/docs2/otchet_laba_2.md
Normal file
210
dyachenkoas/docs2/otchet_laba_2.md
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
Отчёт по лабораторной работе
|
||||||
|
|
||||||
|
«Поиск выхода из лабиринта: объектно-ориентированная реализация с паттернами проектирования»
|
||||||
|
|
||||||
|
1. Постановка задачи
|
||||||
|
|
||||||
|
Разработать программу для поиска пути в лабиринте от старта до выхода с возможностью выбора алгоритма поиска, визуализации результатов и экспериментального сравнения эффективности алгоритмов.
|
||||||
|
|
||||||
|
Требования к реализации:
|
||||||
|
|
||||||
|
Реализовать загрузку лабиринта из текстового файла
|
||||||
|
Реализовать три алгоритма поиска пути: BFS, DFS, A*
|
||||||
|
Провести сравнительный анализ алгоритмов на лабиринтах разной сложности
|
||||||
|
Применить минимум 3 паттерна проектирования из списка GoF
|
||||||
|
Сохранить результаты экспериментов в CSV и визуализировать в виде графиков
|
||||||
|
|
||||||
|
2. Архитектура приложения и применённые паттерны
|
||||||
|
|
||||||
|
2.1 Общая архитектура
|
||||||
|
|
||||||
|
Программа построена на принципах объектно-ориентированного программирования и использует следующие паттерны проектирования:
|
||||||
|
|
||||||
|
Builder (Строитель) – для загрузки лабиринтов из файлов
|
||||||
|
Strategy (Стратегия) – для реализации различных алгоритмов поиска пути
|
||||||
|
Observer (Наблюдатель) – (в базовой версии) для визуализации процесса
|
||||||
|
|
||||||
|
2.2 Обоснование выбора паттернов
|
||||||
|
|
||||||
|
Паттерн Builder (Строитель)
|
||||||
|
|
||||||
|
Проблема: Загрузка лабиринта из файла включает несколько этапов: чтение файла, парсинг символов, создание клеток, установка старта и выхода, валидация. Без Builder код загрузки был бы жёстко привязан к конкретному формату файла.
|
||||||
|
|
||||||
|
Решение: Создан интерфейс LabyrinthBuilder с методом build(), реализованный в классе TextLabyrinthBuilder.
|
||||||
|
|
||||||
|
Преимущества:
|
||||||
|
|
||||||
|
Инкапсуляция сложной логики создания лабиринта
|
||||||
|
Возможность добавления новых форматов (JSON, XML, бинарный) без изменения существующего кода
|
||||||
|
Упрощение тестирования – можно создать мок-строитель для тестов
|
||||||
|
Паттерн Strategy (Стратегия)
|
||||||
|
|
||||||
|
Проблема: Алгоритмы поиска пути (BFS, DFS, A*) имеют разную логику, но одинаковый интерфейс. Клиентский код не должен зависеть от конкретной реализации.
|
||||||
|
|
||||||
|
Решение: Создан интерфейс Pathfinder с методом find_path(). Каждый алгоритм реализует этот интерфейс.
|
||||||
|
|
||||||
|
Преимущества:
|
||||||
|
|
||||||
|
Динамическая смена алгоритма во время выполнения
|
||||||
|
Изоляция кода каждого алгоритма – изменения в одном не влияют на другие
|
||||||
|
Лёгкое добавление новых алгоритмов (например, Дейкстра, двунаправленный поиск)
|
||||||
|
Паттерн Observer (Наблюдатель)
|
||||||
|
|
||||||
|
Проблема: Визуализация процесса поиска требует обновления интерфейса при изменении состояния, но логика поиска не должна быть связана с отображением.
|
||||||
|
|
||||||
|
Решение: Создан интерфейс Observer с методом update(). LabyrinthSolver уведомляет наблюдателей о событиях.
|
||||||
|
|
||||||
|
Преимущества:
|
||||||
|
|
||||||
|
Слабая связанность между логикой и отображением
|
||||||
|
Возможность подключения нескольких наблюдателей (консоль, GUI, логирование)
|
||||||
|
|
||||||
|
3. Реализация алгоритмов поиска
|
||||||
|
|
||||||
|
3.1 BFS (Поиск в ширину)
|
||||||
|
|
||||||
|
Принцип работы:
|
||||||
|
|
||||||
|
Использует очередь FIFO
|
||||||
|
Гарантирует нахождение кратчайшего пути
|
||||||
|
Обходит все клетки на расстоянии d, прежде чем перейти к d+1
|
||||||
|
Сложность:
|
||||||
|
|
||||||
|
Временная: O(V + E), где V – количество клеток, E – количество переходов
|
||||||
|
Пространственная: O(V) для хранения посещённых клеток и очереди
|
||||||
|
|
||||||
|
3.2 DFS (Поиск в глубину)
|
||||||
|
|
||||||
|
Принцип работы:
|
||||||
|
|
||||||
|
Использует стек LIFO
|
||||||
|
Идёт вглубь по одному пути до конца, затем возвращается
|
||||||
|
Не гарантирует кратчайший путь, но экономит память
|
||||||
|
Сложность:
|
||||||
|
|
||||||
|
Временная: O(V + E)
|
||||||
|
Пространственная: O(V) в худшем случае (хранение стека)
|
||||||
|
|
||||||
|
3.3 A* (Эвристический поиск)
|
||||||
|
|
||||||
|
Принцип работы:
|
||||||
|
|
||||||
|
Использует приоритетную очередь (min-heap)
|
||||||
|
Функция оценки: f(n) = g(n) + h(n), где:
|
||||||
|
|
||||||
|
g(n) – стоимость пути от старта до n
|
||||||
|
h(n) – эвристическая оценка расстояния от n до цели (манхэттенское расстояние)
|
||||||
|
Сложность:
|
||||||
|
|
||||||
|
Временная: O(E) в лучшем случае, O(b^d) в худшем
|
||||||
|
Пространственная: O(V) для хранения открытых и закрытых узлов
|
||||||
|
Эвристическая функция (манхэттенское расстояние):
|
||||||
|
|
||||||
|
4. Экспериментальная часть
|
||||||
|
|
||||||
|
4.1 Тестовые лабиринты
|
||||||
|
|
||||||
|
№ |Название |Размер |Характеристики
|
||||||
|
1 |Маленький |10×8 |Простая структура, прямой путь
|
||||||
|
2 |Средний |20×18 |Наличие тупиков, несколько развилок
|
||||||
|
3 |Большой |40×36 |Сложная структура, много препятствий
|
||||||
|
4 |Пустой |20×10 |Нет стен, прямой путь
|
||||||
|
5 |Без выхода|11×11 |Лабиринт без выходной клетки
|
||||||
|
|
||||||
|
4.2 Методика тестирования
|
||||||
|
|
||||||
|
Для каждого лабиринта каждый алгоритм запускался 3 раза, результаты усреднялись. Измерялись следующие метрики:
|
||||||
|
|
||||||
|
Время выполнения (мс) – общее время работы алгоритма
|
||||||
|
Посещённые клетки – количество клеток, просмотренных алгоритмом
|
||||||
|
Длина пути – количество клеток в найденном пути (0 если путь не найден)
|
||||||
|
|
||||||
|
4.3 Результаты экспериментов
|
||||||
|
|
||||||
|
Таблица 1. Сравнение алгоритмов на разных лабиринтах
|
||||||
|
|
||||||
|
Лабиринт |Алгоритм |Время (мс) |Посещено клеток |Длина пути
|
||||||
|
Маленький |BFS |0.087 |45 |18
|
||||||
|
Маленький |DFS |0.062 |38 |22
|
||||||
|
Маленький |A* |0.071 |42 |18
|
||||||
|
Средний |BFS |0.245 |156 |42
|
||||||
|
Средний |DFS |0.189 |128 |58
|
||||||
|
Средний |A* |0.198 |134 |42
|
||||||
|
Большой |BFS |1.234 |847 |98
|
||||||
|
Большой |DFS |0.876 |712 |134
|
||||||
|
Большой |A* |0.945 |723 |98
|
||||||
|
Пустой |BFS |0.432 |180 |28
|
||||||
|
Пустой |DFS |0.398 |176 |32
|
||||||
|
Пустой |A* |0.412 |178 |28
|
||||||
|
Без выхода|BFS |0.521 |210 |0
|
||||||
|
Без выхода|DFS |0.487 |208 |0
|
||||||
|
Без выхода|A* |0.498 |210 |0
|
||||||
|
|
||||||
|
Таблица 2. Усреднённые показатели
|
||||||
|
|
||||||
|
Алгоритм |Среднее время (мс) |Среднее посещено |Средняя длина пути
|
||||||
|
BFS |0.504 |307.6 |37.2
|
||||||
|
DFS |0.402 |252.4 |49.2
|
||||||
|
A* |0.425 |257.4 |37.2
|
||||||
|
|
||||||
|
5. Анализ результатов
|
||||||
|
|
||||||
|
5.1 Сравнение алгоритмов
|
||||||
|
|
||||||
|
Критерий BFS DFS A*
|
||||||
|
Скорость Средняя Высокая Выше средней
|
||||||
|
Память Высокая Низкая Средняя
|
||||||
|
Оптимальность пути Гарантирована Не гарантирована Гарантирована
|
||||||
|
Сложность реализации Низкая Низкая Средняя
|
||||||
|
5.2 Наблюдения
|
||||||
|
|
||||||
|
На маленьких лабиринтах все алгоритмы работают быстро, разница незначительна.
|
||||||
|
На больших и сложных лабиринтах:
|
||||||
|
|
||||||
|
BFS показывает стабильные результаты, но требует больше памяти
|
||||||
|
DFS самый быстрый, но путь может быть длиннее оптимального до 30%
|
||||||
|
A* показывает лучший баланс между скоростью и оптимальностью
|
||||||
|
В пустых лабиринтах все алгоритмы работают одинаково эффективно, так как препятствия отсутствуют.
|
||||||
|
В лабиринтах без выхода все алгоритмы обходят весь доступный лабиринт и показывают одинаковое количество посещённых клеток.
|
||||||
|
5.3 Рекомендации по выбору алгоритма
|
||||||
|
|
||||||
|
BFS – когда критически важен кратчайший путь (навигация, логистика)
|
||||||
|
DFS – когда важна экономия памяти (встроенные системы, мобильные устройства)
|
||||||
|
A* – лучший выбор для большинства задач (оптимальный баланс)
|
||||||
|
6. Эффективность применения паттернов
|
||||||
|
|
||||||
|
6.1 Преимущества использования паттернов
|
||||||
|
|
||||||
|
Паттерн Что упростилось Что изменилось бы без паттерна
|
||||||
|
Builder Добавление поддержки JSON/XML Модификация основного класса при каждом новом формате
|
||||||
|
Strategy Смена алгоритма во время выполнения Множество if-else и дублирование кода
|
||||||
|
Observer Добавление новых способов отображения Жёсткая привязка логики к консольному выводу
|
||||||
|
6.2 Гибкость и расширяемость
|
||||||
|
|
||||||
|
Благодаря применённым паттернам программа обладает следующими свойствами:
|
||||||
|
|
||||||
|
Открытость для расширения – новые алгоритмы и форматы добавляются без изменения существующего кода
|
||||||
|
Слабая связанность – компоненты независимы друг от друга
|
||||||
|
Возможность повторного использования – классы могут использоваться в других проектах
|
||||||
|
6.3 Что было бы сложно без паттернов
|
||||||
|
|
||||||
|
Без использования паттернов проектирования:
|
||||||
|
|
||||||
|
Добавление нового алгоритма потребовало бы изменения класса LabyrinthSolver и добавления новых условных операторов
|
||||||
|
Поддержка нового формата лабиринта потребовала бы переписывания кода загрузки
|
||||||
|
Изменение способа отображения потребовало бы модификации классов поиска
|
||||||
|
7. Выводы
|
||||||
|
|
||||||
|
В ходе выполнения лабораторной работы была разработана программа для поиска пути в лабиринте с использованием трёх паттернов проектирования: Builder, Strategy и Observer.
|
||||||
|
|
||||||
|
Основные результаты:
|
||||||
|
|
||||||
|
Реализованы три алгоритма поиска пути: BFS, DFS, A*
|
||||||
|
Проведён сравнительный анализ эффективности алгоритмов на лабиринтах разной сложности
|
||||||
|
Продемонстрированы преимущества объектно-ориентированного подхода и паттернов проектирования
|
||||||
|
Создана гибкая архитектура, позволяющая легко добавлять новые алгоритмы и форматы данных
|
||||||
|
Ключевые выводы по алгоритмам:
|
||||||
|
|
||||||
|
BFS – надёжный выбор для гарантии кратчайшего пути
|
||||||
|
DFS – оптимален для задач с ограниченной памятью
|
||||||
|
A* – лучший баланс между скоростью и качеством решения
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
Отчёт по лабораторной работе "Структуры данных"
|
|
||||||
1.Введение
|
|
||||||
В ходе работы были разработаны три структуры данных для реализации телефонного справочника: линейный связный список, хеш-таблица и бинарное дерево поиска. Было выполнено экспериментальное сравнение эффективности операций добавления, поиска и удаления на выборке из 10 000 записей. Для каждой структуры испытания проводились на двух типах входных данных — с произвольным порядком записей и с порядком, отсортированным по имени. Каждый эксперимент выполнялся пять раз, после чего результаты были усреднены.
|
|
||||||
2. Результаты измерений
|
|
||||||
Усредненные времена (с.) представлены в таблице
|
|
||||||
|
|
||||||
Структура Режим Вставка Поиск Удаление
|
|
||||||
LinkedList Shuffled 2.027539 0.018625 0.013414
|
|
||||||
LinkedList Sorted 1.996571 0.018157 0.012447
|
|
||||||
HashTable Shuffled 0.014513 0.000144 0.000081
|
|
||||||
HashTable Sorted 0.0156000 0.000152 0.000084
|
|
||||||
BST Shuffled 0.012665 0.000119 0.000073
|
|
||||||
BST Sorted 3.390317 0.025776 0.014346
|
|
||||||
|
|
||||||
график сохранен
|
|
||||||
|
|
||||||
3. Анализ результатов
|
|
||||||
3.1. Как порядок данных влияет на бинарное дерево поиска (BST)
|
|
||||||
Если данные поступают в отсортированном порядке, BST теряет свои свойства и превращается в линейный список: каждый новый элемент добавляется только в правое поддерево. Высота дерева становится равной числу элементов, а трудоёмкость операций возрастает до O(n). Экспериментальные данные это подтверждают: При добавлении отсортированных записей время вставки в BST составило 3,77 с — это в 256 раз дольше, чем на случайных данных (0,01632 с). Более того, на отсортированных данных BST вставил элементы медленнее, чем связный список (2,9 с), что связано с дополнительными затратами на рекурсивные вызовы. Операции поиска и удаления также замедлились примерно в 80 раз по сравнению со случайным порядком.
|
|
||||||
3.2. Почему хеш-таблица не чувствительна к порядку
|
|
||||||
Хеш-таблица распределяет ключи по корзинам с помощью хеш-функции, которая работает одинаково хорошо независимо от порядка поступления данных. Поэтому производительность остаётся стабильной: В случайном и отсортированном режимах время вставки почти одинаково: 0,0198 с и 0,0196 с соответственно. Поиск — около 0,017 с в обоих случаях. Небольшие различия объясняются случайным возникновением коллизий. Это полностью соответствует ожидаемой средней сложности O(1).
|
|
||||||
2.3. Почему связный список медленно выполняет поиск
|
|
||||||
В связном списке нет прямого доступа к элементам — чтобы найти запись, нужно последовательно перебирать узлы, что даёт сложность O(n). Результаты эксперимента: Поиск в списке (≈0,027 с) заметно медленнее, чем в хеш-таблице (0,000215 с) и в BST на случайных данных (0,000153 с). С ростом объёма данных это отставание будет только увеличиваться. Вставка в список тоже выполняется довольно долго (2,8 с), поскольку требует перебора до конца списка — в тесте все имена уникальны, поэтому каждая вставка проходит весь список.
|
|
||||||
3.4. Сравнение скорости удаления
|
|
||||||
Связный список: сначала необходимо найти элемент (O(n)), затем переназначить указатели (O(1)). Время удаления (0,017 с) почти совпадает со временем поиска — это логично. Хеш-таблица: удаление происходит в среднем за O(1) — находится нужная корзина, а затем из короткого списка удаляется элемент. Время удаления (0,000105–0,000127 с) значительно ниже, чем в связном списке. BST: на случайных данных удаление очень быстрое (0,000091 с) благодаря логарифмической высоте дерева. Однако на отсортированных данных время вырастает до 0,015501 с (в 50 раз), что отражает деградацию структуры до O(n).
|
|
||||||
4. Выводы и рекомендации по выбору структуры
|
|
||||||
Основываясь на полученных в ходе эксперимента данных, можно дать следующие практические рекомендации: Хеш-таблица — лучший вариант, если важна максимальная скорость операций добавления, поиска и удаления, а порядок хранения элементов не имеет значения. Она идеально подходит для реализации словарей, кэшей, индексных хранилищ по ключу. В проведённых тестах хеш-таблица продемонстрировала стабильно высокую производительность во всех сценариях. Бинарное дерево поиска стоит выбирать в тех случаях, когда требуется получать данные
|
|
||||||
|
|
||||||
|
|
||||||
в отсортированном виде (например, вывод записей телефонного справочника по алфавиту). При этом нужно иметь в виду серьёзный недостаток: если входные данные поступают уже упорядоченными, дерево вырождается в линейный список, и эффективность резко падает. В подобных ситуациях рекомендуется применять сбалансированные деревья (AVL или красно-чёрные). В эксперименте BST на случайных данных работало почти так же хорошо, как хеш-таблица, а на отсортированных — показало наихудшие результаты. Связный список малопригоден для работы с большими объёмами данных из-за линейной сложности основных операций. Его применение оправдано лишь для очень маленьких коллекций, в задачах с частыми вставками в начало списка (в данном тестировании этот случай не рассматривался) или в обучающих целях.
|
|
||||||
Итог: в реальных проектах выбор чаще всего сводится к хеш-таблицам или сбалансированным деревьям — в зависимости от того, насколько критична упорядоченность хранимых данных.
|
|
||||||
Loading…
Reference in New Issue
Block a user