Compare commits

..

No commits in common. "develop" and "1" have entirely different histories.
develop ... 1

2323 changed files with 4 additions and 2505014 deletions

1
.gitignore vendored
View File

@ -7,7 +7,6 @@ __pycache__/
# C extensions
*.so
.DS_Store
# Distribution / packaging
.Python
build/

5
.idea/.gitignore vendored
View File

@ -1,5 +0,0 @@
# Игнорируемые файлы по умолчанию
/shelf/
/workspace.xml
# HTTP-клиент на основе редактора
/httpRequests/

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.14" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.14" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.14" project-jdk-type="Python SDK" />
</project>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/2026-rff_mp.iml" filepath="$PROJECT_DIR$/.idea/2026-rff_mp.iml" />
</modules>
</component>
</project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

@ -1 +0,0 @@
Subproject commit 52c001a380431727397e4275c2be9d94fe5fcc8d

View File

@ -1 +0,0 @@
428b

View File

@ -1,31 +0,0 @@
Structure,Mode,Repetition,Insert (sec),Find (sec),Delete (sec)
LinkedList,random,1,0.022518936000324175,0.0022566010002265102,0.00023215900000650436
LinkedList,random,2,0.020449046000067028,0.00233427400007713,0.0002826350000759703
LinkedList,random,3,0.020126345999869955,0.002262161000089691,0.0002053490002253966
LinkedList,random,4,0.018700520000038523,0.0021239550001155294,0.00021164700001463643
LinkedList,random,5,0.019754801000090083,0.0024997460000122373,0.00023158399972089683
HashTable,random,1,0.0029892609995840758,0.0003534970001055626,2.990000029967632e-05
HashTable,random,2,0.00424448100011432,0.00041066700032388326,4.1896999846358085e-05
HashTable,random,3,0.004175077000127203,0.0004925200000798213,2.9787000130454544e-05
HashTable,random,4,0.004567926000163425,0.00033349500017720857,3.164999998261919e-05
HashTable,random,5,0.0026621540000633104,0.00031125700024858816,2.81510001514107e-05
BST,random,1,0.0011096680000264314,9.346200022264384e-05,3.9635000121052144e-05
BST,random,2,0.0010248069997942366,9.059800004251883e-05,2.1142000150575768e-05
BST,random,3,0.0010313749999113497,9.237799986294704e-05,1.8248000287712784e-05
BST,random,4,0.0010483440000825794,9.526299982098863e-05,0.0001046150000547641
BST,random,5,0.0010863010002140072,8.332400011568097e-05,1.7150000076071592e-05
LinkedList,sorted,1,0.022635587000422674,0.0023248429997693165,0.00031860600029176567
LinkedList,sorted,2,0.02307340400011526,0.002558939999744325,0.00023637899994355394
LinkedList,sorted,3,0.01936496400003307,0.002565375999893149,0.00019060200020248885
LinkedList,sorted,4,0.018800279000060982,0.0021075829999972484,0.00018179399967266363
LinkedList,sorted,5,0.019234453000080975,0.002213534000020445,0.0002037049998762086
HashTable,sorted,1,0.0031771270000717777,0.0006652990000475256,6.005399973219028e-05
HashTable,sorted,2,0.004056166999816924,0.00058001300021715,3.078500003539375e-05
HashTable,sorted,3,0.00331192200019359,0.00027228699991610483,2.684700029931264e-05
HashTable,sorted,4,0.002749069000401505,0.0002677190000213159,2.1935999939159956e-05
HashTable,sorted,5,0.002638604000367195,0.0003338189999340102,1.83020001713885e-05
BST,sorted,1,0.037994623000031424,0.0035877690002052987,0.0011276019999968412
BST,sorted,2,0.038043681000090146,0.0031297909999921103,0.00094645799981663
BST,sorted,3,0.03629641699990316,0.0027566620001380215,0.0012528499996733444
BST,sorted,4,0.03509285000018281,0.003267435999987356,0.0011822649998975976
BST,sorted,5,0.038755655999921146,0.0064144259999920905,0.0018493429997761268
1 Structure Mode Repetition Insert (sec) Find (sec) Delete (sec)
2 LinkedList random 1 0.022518936000324175 0.0022566010002265102 0.00023215900000650436
3 LinkedList random 2 0.020449046000067028 0.00233427400007713 0.0002826350000759703
4 LinkedList random 3 0.020126345999869955 0.002262161000089691 0.0002053490002253966
5 LinkedList random 4 0.018700520000038523 0.0021239550001155294 0.00021164700001463643
6 LinkedList random 5 0.019754801000090083 0.0024997460000122373 0.00023158399972089683
7 HashTable random 1 0.0029892609995840758 0.0003534970001055626 2.990000029967632e-05
8 HashTable random 2 0.00424448100011432 0.00041066700032388326 4.1896999846358085e-05
9 HashTable random 3 0.004175077000127203 0.0004925200000798213 2.9787000130454544e-05
10 HashTable random 4 0.004567926000163425 0.00033349500017720857 3.164999998261919e-05
11 HashTable random 5 0.0026621540000633104 0.00031125700024858816 2.81510001514107e-05
12 BST random 1 0.0011096680000264314 9.346200022264384e-05 3.9635000121052144e-05
13 BST random 2 0.0010248069997942366 9.059800004251883e-05 2.1142000150575768e-05
14 BST random 3 0.0010313749999113497 9.237799986294704e-05 1.8248000287712784e-05
15 BST random 4 0.0010483440000825794 9.526299982098863e-05 0.0001046150000547641
16 BST random 5 0.0010863010002140072 8.332400011568097e-05 1.7150000076071592e-05
17 LinkedList sorted 1 0.022635587000422674 0.0023248429997693165 0.00031860600029176567
18 LinkedList sorted 2 0.02307340400011526 0.002558939999744325 0.00023637899994355394
19 LinkedList sorted 3 0.01936496400003307 0.002565375999893149 0.00019060200020248885
20 LinkedList sorted 4 0.018800279000060982 0.0021075829999972484 0.00018179399967266363
21 LinkedList sorted 5 0.019234453000080975 0.002213534000020445 0.0002037049998762086
22 HashTable sorted 1 0.0031771270000717777 0.0006652990000475256 6.005399973219028e-05
23 HashTable sorted 2 0.004056166999816924 0.00058001300021715 3.078500003539375e-05
24 HashTable sorted 3 0.00331192200019359 0.00027228699991610483 2.684700029931264e-05
25 HashTable sorted 4 0.002749069000401505 0.0002677190000213159 2.1935999939159956e-05
26 HashTable sorted 5 0.002638604000367195 0.0003338189999340102 1.83020001713885e-05
27 BST sorted 1 0.037994623000031424 0.0035877690002052987 0.0011276019999968412
28 BST sorted 2 0.038043681000090146 0.0031297909999921103 0.00094645799981663
29 BST sorted 3 0.03629641699990316 0.0027566620001380215 0.0012528499996733444
30 BST sorted 4 0.03509285000018281 0.003267435999987356 0.0011822649998975976
31 BST sorted 5 0.038755655999921146 0.0064144259999920905 0.0018493429997761268

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

View File

@ -1,335 +0,0 @@
import random
import time
import sys
import csv
import os
import matplotlib.pyplot as plt
import numpy as np
sys.setrecursionlimit(10000)
ll_head = None
def ll_insert(name, phone):
global ll_head
cur = ll_head
while cur is not None:
if cur['name'] == name:
cur['phone'] = phone
return
cur = cur['next']
new_node = {'name': name, 'phone': phone, 'next': ll_head}
ll_head = new_node
def ll_find(name):
cur = ll_head
while cur is not None:
if cur['name'] == name:
return cur['phone']
cur = cur['next']
return None
def ll_delete(name):
global ll_head
if ll_head is None:
return
if ll_head['name'] == name:
ll_head = ll_head['next']
return
prev = ll_head
cur = ll_head['next']
while cur is not None:
if cur['name'] == name:
prev['next'] = cur['next']
return
prev = cur
cur = cur['next']
def ll_list_all():
records = []
cur = ll_head
while cur is not None:
records.append((cur['name'], cur['phone']))
cur = cur['next']
records.sort(key=lambda x: x[0])
return records
BUCKET_COUNT = 10
buckets = [None] * BUCKET_COUNT
def hash_func(name):
s = 0
for ch in name:
s += ord(ch)
return s % BUCKET_COUNT
def ht_insert(name, phone):
global buckets
idx = hash_func(name)
cur = buckets[idx]
while cur is not None:
if cur['name'] == name:
cur['phone'] = phone
return
cur = cur['next']
new_node = {'name': name, 'phone': phone, 'next': buckets[idx]}
buckets[idx] = new_node
def ht_find(name):
idx = hash_func(name)
cur = buckets[idx]
while cur is not None:
if cur['name'] == name:
return cur['phone']
cur = cur['next']
return None
def ht_delete(name):
global buckets
idx = hash_func(name)
head = buckets[idx]
if head is None:
return
if head['name'] == name:
buckets[idx] = head['next']
return
prev = head
cur = head['next']
while cur is not None:
if cur['name'] == name:
prev['next'] = cur['next']
return
prev = cur
cur = cur['next']
def ht_list_all():
all_records = []
for head in buckets:
cur = head
while cur is not None:
all_records.append((cur['name'], cur['phone']))
cur = cur['next']
all_records.sort(key=lambda x: x[0])
return all_records
bst_root = None
def bst_create_node(name, phone):
return {'name': name, 'phone': phone, 'left': None, 'right': None}
def bst_insert(name, phone):
global bst_root
if bst_root is None:
bst_root = bst_create_node(name, phone)
return
current = bst_root
while True:
if name == current['name']:
current['phone'] = phone
return
elif name < current['name']:
if current['left'] is None:
current['left'] = bst_create_node(name, phone)
return
current = current['left']
else:
if current['right'] is None:
current['right'] = bst_create_node(name, phone)
return
current = current['right']
def bst_find(name):
current = bst_root
while current is not None:
if name == current['name']:
return current['phone']
elif name < current['name']:
current = current['left']
else:
current = current['right']
return None
def find_min(node):
while node['left'] is not None:
node = node['left']
return node
def bst_delete(name):
global bst_root
def delete_rec(node):
if node is None:
return None
if name < node['name']:
node['left'] = delete_rec(node['left'])
elif name > node['name']:
node['right'] = delete_rec(node['right'])
else:
if node['left'] is None:
return node['right']
if node['right'] is None:
return node['left']
min_node = find_min(node['right'])
node['name'] = min_node['name']
node['phone'] = min_node['phone']
node['right'] = delete_rec(node['right'])
return node
bst_root = delete_rec(bst_root)
def bst_list_all():
result = []
def inorder(node):
if node is None:
return
inorder(node['left'])
result.append((node['name'], node['phone']))
inorder(node['right'])
inorder(bst_root)
return result
def generate_records(n):
records = []
for i in range(1, n+1):
name = f"User_{i:05d}"
phone = f"{random.randint(100,999)}-{random.randint(1000,9999)}"
records.append((name, phone))
return records
def run_experiment():
N = 1000
base = generate_records(N)
shuffled = base.copy()
random.shuffle(shuffled)
sorted_records = sorted(base, key=lambda x: x[0])
structures = [
('LinkedList', ll_insert, ll_find, ll_delete, ll_list_all),
('HashTable', ht_insert, ht_find, ht_delete, ht_list_all),
('BST', bst_insert, bst_find, bst_delete, bst_list_all)
]
all_results = [] # для CSV: список словарей
repeats = 5
for mode_name, data in [('random', shuffled), ('sorted', sorted_records)]:
for struct_name, ins, fnd, dele, lst in structures:
print(f"Testing {struct_name} on {mode_name}...")
for rep in range(repeats):
# сброс структур
global ll_head, buckets, bst_root
ll_head = None
buckets = [None] * BUCKET_COUNT
bst_root = None
# вставка
t0 = time.perf_counter()
for name, phone in data:
ins(name, phone)
t1 = time.perf_counter()
insert_time = t1 - t0
# поиск 110 записей (100 существующих + 10 несуществующих)
existing = [name for name, _ in data]
sample = random.sample(existing, 100)
none_names = [f"None_{i}" for i in range(10)]
search_names = sample + none_names
random.shuffle(search_names)
t0 = time.perf_counter()
for name in search_names:
fnd(name)
t1 = time.perf_counter()
find_time = t1 - t0
# удаление 10 записей
to_delete = random.sample(existing, 10)
t0 = time.perf_counter()
for name in to_delete:
dele(name)
t1 = time.perf_counter()
delete_time = t1 - t0
all_results.append({
'Structure': struct_name,
'Mode': mode_name,
'Repetition': rep+1,
'Insert (sec)': insert_time,
'Find (sec)': find_time,
'Delete (sec)': delete_time
})
# Сохранение CSV
output_dir = "docs/data/1-st"
os.makedirs(output_dir, exist_ok=True)
csv_path = os.path.join(output_dir, "experiment_results.csv")
with open(csv_path, 'w', newline='', encoding='utf-8') as f:
fieldnames = ['Structure', 'Mode', 'Repetition', 'Insert (sec)', 'Find (sec)', 'Delete (sec)']
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(all_results)
print(f"\nРезультаты сохранены в {csv_path}")
avg_data = {}
for r in all_results:
key = (r['Structure'], r['Mode'])
if key not in avg_data:
avg_data[key] = {'Insert': [], 'Find': [], 'Delete': []}
avg_data[key]['Insert'].append(r['Insert (sec)'])
avg_data[key]['Find'].append(r['Find (sec)'])
avg_data[key]['Delete'].append(r['Delete (sec)'])
structures_list = ['LinkedList', 'HashTable', 'BST']
modes_list = ['random', 'sorted']
insert_vals = {mode: [] for mode in modes_list}
find_vals = {mode: [] for mode in modes_list}
delete_vals = {mode: [] for mode in modes_list}
for mode in modes_list:
for struct in structures_list:
key = (struct, mode)
if key in avg_data:
insert_avg = sum(avg_data[key]['Insert']) / len(avg_data[key]['Insert'])
find_avg = sum(avg_data[key]['Find']) / len(avg_data[key]['Find'])
delete_avg = sum(avg_data[key]['Delete']) / len(avg_data[key]['Delete'])
else:
insert_avg = find_avg = delete_avg = 0
insert_vals[mode].append(insert_avg)
find_vals[mode].append(find_avg)
delete_vals[mode].append(delete_avg)
# Рисуем три столбчатые диаграммы
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
x = np.arange(len(structures_list))
width = 0.35
for ax, op_data, op_label, ylabel in zip(
axes,
[insert_vals, find_vals, delete_vals],
['Insert', 'Find', 'Delete'],
['Время вставки (с)', 'Время поиска (с)', 'Время удаления (с)']
):
random_vals = op_data['random']
sorted_vals = op_data['sorted']
ax.bar(x - width/2, random_vals, width, label='Случайный порядок', color='skyblue')
ax.bar(x + width/2, sorted_vals, width, label='Отсортированный порядок', color='salmon')
ax.set_xticks(x)
ax.set_xticklabels(structures_list)
ax.set_ylabel(ylabel)
ax.set_title(op_label)
ax.legend()
plt.tight_layout()
png_path = os.path.join(output_dir, "performance_comparison.png")
plt.savefig(png_path, dpi=150)
print(f"График сохранён в {png_path}")
plt.show()
# Вывод средних значений в консоль (для отчёта)
print("\nСредние значения (сек):")
print("Структура\tРежим\tВставка\tПоиск\tУдаление")
for (struct, mode), vals in avg_data.items():
ins_avg = sum(vals['Insert'])/len(vals['Insert'])
find_avg = sum(vals['Find'])/len(vals['Find'])
del_avg = sum(vals['Delete'])/len(vals['Delete'])
print(f"{struct}\t{mode}\t{ins_avg:.6f}\t{find_avg:.6f}\t{del_avg:.6f}")
if __name__ == '__main__':
run_experiment()

View File

@ -1,430 +0,0 @@
import time
import random
import csv
import os
from collections import deque
import heapq
import matplotlib.pyplot as plt
import numpy as np
class Cell:
def __init__(self, x, y, is_wall=False, is_start=False, is_exit=False):
self.x = x
self.y = y
self.is_wall = is_wall
self.is_start = is_start
self.is_exit = is_exit
def is_passable(self):
return not self.is_wall
class Maze:
def __init__(self, width, height):
self.width = width
self.height = height
self.grid = [[Cell(x, y) for y in range(height)] for x in range(width)]
self.start_cell = None
self.exit_cell = None
def get_cell(self, x, y):
if 0 <= x < self.width and 0 <= y < self.height:
return self.grid[x][y]
return None
def get_neighbors(self, cell):
neighbors = []
for dx, dy in [(-1,0), (1,0), (0,-1), (0,1)]:
nx, ny = cell.x + dx, cell.y + dy
neighbor = self.get_cell(nx, ny)
if neighbor and neighbor.is_passable():
neighbors.append(neighbor)
return neighbors
def save_to_file(self, filename):
"""Сохраняет лабиринт в текстовый файл."""
with open(filename, 'w', encoding='utf-8') as f:
for y in range(self.height):
row = ''
for x in range(self.width):
cell = self.get_cell(x, y)
if cell.is_wall:
row += '#'
elif cell.is_start:
row += 'S'
elif cell.is_exit:
row += 'E'
else:
row += ' '
f.write(row + '\n')
class MazeBuilder:
def build_from_file(self, filename):
raise NotImplementedError
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename):
with open(filename, 'r', encoding='utf-8') as f:
lines = f.readlines()
lines = [line.rstrip('\n') for line in lines if line.strip() != '']
if not lines:
raise ValueError("Файл пуст")
height = len(lines)
width = max(len(line) for line in lines)
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
if x >= width:
break
cell = maze.get_cell(x, y)
if ch == '#':
cell.is_wall = True
elif ch == 'S':
cell.is_start = True
maze.start_cell = cell
elif ch == 'E':
cell.is_exit = True
maze.exit_cell = cell
if maze.start_cell is None or maze.exit_cell is None:
raise ValueError("В лабиринте должны быть S и E")
return maze
class PathFindingStrategy:
def find_path(self, maze, start, exit):
raise NotImplementedError
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit):
if start == exit:
return [start]
queue = deque([start])
visited = {start}
parent = {start: None}
while queue:
current = queue.popleft()
if current == exit:
break
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
parent[neighbor] = current
queue.append(neighbor)
if exit not in parent:
return []
path = []
step = exit
while step is not None:
path.append(step)
step = parent[step]
path.reverse()
return path
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit):
if start == exit:
return [start]
stack = [start]
visited = {start}
parent = {start: None}
while stack:
current = stack.pop()
if current == exit:
break
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
parent[neighbor] = current
stack.append(neighbor)
if exit not in parent:
return []
path = []
step = exit
while step is not None:
path.append(step)
step = parent[step]
path.reverse()
return path
class AStarStrategy(PathFindingStrategy):
def heuristic(self, a, b):
return abs(a.x - b.x) + abs(a.y - b.y)
def find_path(self, maze, start, exit):
if start == exit:
return [start]
open_set = []
heapq.heappush(open_set, (0, id(start), start))
came_from = {}
g_score = {start: 0}
f_score = {start: self.heuristic(start, exit)}
while open_set:
_, _, current = heapq.heappop(open_set)
if current == exit:
path = []
step = current
while step is not None:
path.append(step)
step = came_from.get(step)
path.reverse()
return path
for neighbor in maze.get_neighbors(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[neighbor] = tentative_g + self.heuristic(neighbor, exit)
heapq.heappush(open_set, (f_score[neighbor], id(neighbor), neighbor))
return []
class Observer:
def update(self, event):
raise NotImplementedError
class ConsoleView(Observer):
def __init__(self, maze):
self.maze = maze
def update(self, event):
if event == "Поиск начат":
print("=== Поиск начат ===")
elif event == "Поиск завершён":
print("=== Поиск завершён ===")
def render(self, path=None):
path_set = set(path) if path else set()
for y in range(self.maze.height):
row = ''
for x in range(self.maze.width):
cell = self.maze.get_cell(x, y)
if cell.is_wall:
row += '#'
elif cell.is_start:
row += 'S'
elif cell.is_exit:
row += 'E'
elif cell in path_set:
row += '*'
else:
row += ' '
print(row)
print()
class MazeSolver:
def __init__(self, maze, strategy=None):
self.maze = maze
self.strategy = strategy
self.observers = []
def set_strategy(self, strategy):
self.strategy = strategy
def attach(self, observer):
self.observers.append(observer)
def detach(self, observer):
self.observers.remove(observer)
def notify(self, event):
for obs in self.observers:
obs.update(event)
def solve(self):
if self.strategy is None:
raise ValueError("Стратегия не установлена")
start = self.maze.start_cell
exit_cell = self.maze.exit_cell
if start is None or exit_cell is None:
raise ValueError("Лабиринт не содержит старта или выхода")
self.notify("Поиск начат")
start_time = time.perf_counter()
path = self.strategy.find_path(self.maze, start, exit_cell)
end_time = time.perf_counter()
elapsed_ms = (end_time - start_time) * 1000
self.notify("Поиск завершён")
return path, elapsed_ms
def generate_empty_maze(width, height):
maze = Maze(width, height)
start = maze.get_cell(0, 0)
exit_cell = maze.get_cell(width-1, height-1)
start.is_start = True
exit_cell.is_exit = True
maze.start_cell = start
maze.exit_cell = exit_cell
return maze
def generate_random_maze(width, height, wall_prob=0.3):
maze = Maze(width, height)
for x in range(width):
for y in range(height):
cell = maze.get_cell(x, y)
if random.random() < wall_prob:
cell.is_wall = True
start = maze.get_cell(0, 0)
exit_cell = maze.get_cell(width-1, height-1)
start.is_wall = False
start.is_start = True
exit_cell.is_wall = False
exit_cell.is_exit = True
maze.start_cell = start
maze.exit_cell = exit_cell
return maze
def generate_maze_with_dead_ends(width, height):
maze = Maze(width, height)
for x in range(width):
for y in range(height):
maze.get_cell(x, y).is_wall = True
x, y = 0, 0
while x < width and y < height:
cell = maze.get_cell(x, y)
cell.is_wall = False
if x == width-1 and y == height-1:
break
if y+1 < height and (x == width-1 or random.choice([True, False])):
y += 1
else:
x += 1
start = maze.get_cell(0, 0)
exit_cell = maze.get_cell(width-1, height-1)
start.is_start = True
exit_cell.is_exit = True
maze.start_cell = start
maze.exit_cell = exit_cell
return maze
def generate_maze_no_exit(width, height):
maze = generate_random_maze(width, height, 0.2)
exit_cell = maze.get_cell(width-1, height-1)
for dx, dy in [(-1,0), (1,0), (0,-1), (0,1)]:
nx, ny = exit_cell.x + dx, exit_cell.y + dy
neighbor = maze.get_cell(nx, ny)
if neighbor:
neighbor.is_wall = True
start = maze.get_cell(0, 0)
start.is_wall = False
start.is_start = True
maze.start_cell = start
maze.exit_cell = exit_cell
return maze
def ensure_maze_files():
"""Создаёт папку mazes и генерирует все лабиринты, если файлы отсутствуют."""
os.makedirs("mazes", exist_ok=True)
configs = [
("empty_10x10.txt", 10, 10, generate_empty_maze),
("empty_50x50.txt", 50, 50, generate_empty_maze),
("empty_100x100.txt", 100, 100, generate_empty_maze),
("random_10x10.txt", 10, 10, generate_random_maze),
("random_50x50.txt", 50, 50, generate_random_maze),
("random_100x100.txt", 100, 100, generate_random_maze),
("deadends_10x10.txt", 10, 10, generate_maze_with_dead_ends),
("deadends_50x50.txt", 50, 50, generate_maze_with_dead_ends),
("deadends_100x100.txt", 100, 100, generate_maze_with_dead_ends),
("no_exit_10x10.txt", 10, 10, generate_maze_no_exit),
("no_exit_50x50.txt", 50, 50, generate_maze_no_exit),
]
for filename, w, h, gen_func in configs:
filepath = os.path.join("mazes", filename)
if not os.path.exists(filepath):
print(f"Генерация {filename}...")
maze = gen_func(w, h)
maze.save_to_file(filepath)
def run_experiment():
ensure_maze_files()
builder = TextFileMazeBuilder()
strategies = [
("BFS", BFSStrategy()),
("DFS", DFSStrategy()),
("AStar", AStarStrategy())
]
maze_files = sorted([f for f in os.listdir("mazes") if f.endswith(".txt")])
results = []
repeats = 5
for filename in maze_files:
maze_name = filename.replace(".txt", "")
print(f"Тестирование лабиринта: {maze_name}")
try:
maze = builder.build_from_file(os.path.join("mazes", filename))
except Exception as e:
print(f"Ошибка загрузки {filename}: {e}")
continue
solver = MazeSolver(maze)
for strat_name, strat in strategies:
solver.set_strategy(strat)
total_time = 0
total_path_len = 0
path = []
for rep in range(repeats):
path, elapsed_ms = solver.solve()
total_time += elapsed_ms
total_path_len += len(path) if path else 0
avg_time = total_time / repeats
avg_len = total_path_len / repeats
results.append({
"Maze": maze_name,
"Strategy": strat_name,
"AvgTime_ms": avg_time,
"AvgPathLen": avg_len,
"PathFound": len(path) > 0 if path else False
})
print(f" {strat_name}: время {avg_time:.3f} мс, длина пути {avg_len:.1f}")
os.makedirs("results", exist_ok=True)
csv_path = "results/experiment_results.csv"
with open(csv_path, 'w', newline='', encoding='utf-8') as f:
fieldnames = ["Maze", "Strategy", "AvgTime_ms", "AvgPathLen", "PathFound"]
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(results)
print(f"Результаты сохранены в {csv_path}")
maze_names = sorted(set(r["Maze"] for r in results))
strategy_names = ["BFS", "DFS", "AStar"]
data = {maze: {s: None for s in strategy_names} for maze in maze_names}
for r in results:
data[r["Maze"]][r["Strategy"]] = r["AvgTime_ms"]
fig, ax = plt.subplots(figsize=(14, 6))
x = np.arange(len(maze_names))
width = 0.25
colors = ['skyblue', 'lightgreen', 'salmon']
for i, strat in enumerate(strategy_names):
times = [data[maze][strat] if data[maze][strat] is not None else 0 for maze in maze_names]
ax.bar(x + i*width, times, width, label=strat, color=colors[i])
ax.set_xlabel('Лабиринт')
ax.set_ylabel('Среднее время (мс)')
ax.set_title('Сравнение стратегий поиска пути')
ax.set_xticks(x + width)
ax.set_xticklabels(maze_names, rotation=45, ha='right')
ax.legend()
plt.tight_layout()
plt.savefig("results/performance.png", dpi=150)
#plt.show()
print("График сохранён в results/performance.png")
if __name__ == '__main__':
run_experiment()

View File

@ -1,100 +0,0 @@
S ##################################################################################################
# ##################################################################################################
# ##################################################################################################
# #################################################################################################
## ##############################################################################################
##### ##############################################################################################
##### ############################################################################################
####### ###########################################################################################
######## ######################################################################################
############# ##################################################################################
################# #################################################################################
################## #################################################################################
################## #################################################################################
################## ################################################################################
################### ################################################################################
################### #############################################################################
###################### ############################################################################
####################### ############################################################################
####################### ############################################################################
####################### ############################################################################
####################### ###########################################################################
######################## ##########################################################################
######################### #########################################################################
########################## ########################################################################
########################### #####################################################################
############################## #####################################################################
############################## #####################################################################
############################## ####################################################################
############################### ####################################################################
############################### ####################################################################
############################### ##################################################################
################################# ##################################################################
################################# #################################################################
################################## ###############################################################
#################################### ##############################################################
##################################### ##############################################################
##################################### ##############################################################
##################################### ############################################################
####################################### ############################################################
####################################### ############################################################
####################################### ############################################################
####################################### ##########################################################
######################################### #########################################################
########################################## ########################################################
########################################### ########################################################
########################################### ########################################################
########################################### ########################################################
########################################### #######################################################
############################################ #####################################################
############################################## ##################################################
################################################# ##################################################
################################################# ##############################################
##################################################### #############################################
###################################################### ############################################
####################################################### ############################################
####################################################### ############################################
####################################################### ###########################################
######################################################## ###########################################
######################################################## ##########################################
######################################################### ##########################################
######################################################### ##########################################
######################################################### ##########################################
######################################################### #########################################
########################################################## #######################################
############################################################ #####################################
############################################################## #####################################
############################################################## ####################################
############################################################### ###################################
################################################################ ###################################
################################################################ ##################################
################################################################# ###############################
#################################################################### #############################
###################################################################### ###########################
######################################################################## ##########################
######################################################################### ########################
########################################################################### #######################
############################################################################ #######################
############################################################################ ###################
################################################################################ ##################
################################################################################# #################
################################################################################## #################
################################################################################## #################
################################################################################## ##############
##################################################################################### ############
####################################################################################### ###########
######################################################################################## ###########
######################################################################################## ##########
######################################################################################### ##########
######################################################################################### ########
########################################################################################### #######
############################################################################################ #####
############################################################################################## ###
################################################################################################ ##
################################################################################################# ##
################################################################################################# ##
################################################################################################# ##
################################################################################################# ##
################################################################################################# #
##################################################################################################
###################################################################################################E

View File

@ -1,10 +0,0 @@
S #######
## #######
## #######
## #######
## #######
## ######
### #####
#### ##
####### ##
####### E

View File

@ -1,50 +0,0 @@
S ################################################
# ###############################################
## ###############################################
## ###############################################
## ##############################################
### ##############################################
### ############################################
##### ###########################################
###### ######################################
########### ######################################
########### #####################################
############ ##################################
############### #################################
################ ###############################
################## ###############################
################## ###########################
###################### ###########################
###################### ########################
######################### #######################
########################## #######################
########################## ######################
########################### #####################
############################ #####################
############################ ####################
############################# ###################
############################## ###################
############################## ###################
############################## ################
################################# ###########
###################################### ##########
####################################### #########
######################################## #########
######################################## ########
######################################### #######
########################################## #####
############################################ ####
############################################# ###
############################################## ###
############################################## ##
###############################################
#################################################
#################################################
#################################################
#################################################
#################################################
#################################################
#################################################
#################################################
#################################################
#################################################E

View File

@ -1,100 +0,0 @@
S
E

View File

@ -1,10 +0,0 @@
S
E

View File

@ -1,50 +0,0 @@
S
E

View File

@ -1,10 +0,0 @@
S ##
#
# #
###
# # #
# # #
## #
###
# #
#E

View File

@ -1,50 +0,0 @@
S # # # ## #
# ### ## # # # # # #
# # # # # # # # # #
# # # # # # # # ##
### # # # # ## # # # # ## ##
### # # # # # # #
# ## #
## ### ## # # #
# # # # # # ### # ##
# # # # # # #
#### ### # # # # # # #
## # # # # # # # # ## #
# ## # ## ## # #
## # # ## # # # # # #
### # # # # # # #
# ## ## # # # # # # #
# # # # # # # # ####
# # ## # # # # # # #
## # ### #
# ## # # # #
# # # # # #
# ## # ## #### ### # # # #
# # # # ###
### ## ## # # # ##
# # # ## #
# # ## ### # # # #
# # # # ### # ## # # # #
## # # # # ## #
# # # # # ## # # #
# # # # # #
# # # # ## # # # # # # # #
# # # # # ## # # #
# # # ## #
# # # # # # # # # ### #
# # # # # # #
# ## # # ## # ## # #
# # # # ## # ##
# # # # # # # # #
## ### # ### # # # #
## ## ## # # # ## # # # #
# # ### # # # # #
# # # # ## ## ## #
# # # ## ## ### # #
# # # ## # #
## # # # # # #
# # # # # #
# # # # ##
## # # # # # ## #
# # ## # # ### # #
# ## # # # # # # ## ## #E

View File

@ -1,100 +0,0 @@
S ## ## # # # # # # # # # # # ## ### # #
### # # ### # ##### # # ### ## ## # # # # # ### # # # #
## # # # # # # ### # ## # # # # # # ### # # ### ##
# ## # # # # # ## # # # ## # ## # ### ## ## # ## #
## # # # # # # # # # # # # # # # # ## # # # # # # #
# ## ## # ## # # ### ## # # # # # ## # # # # ## #
## # # ### # # # ## # # ## # ## #### #### ## ## #
# # ##### # ### # # # ### ### # ## # ### # # ## # # #
# ## # # #### # # # ### ### # # # # ### # # ## # # #
# # # ## ## # # ### # # ## # ## # # # # ##
# ## # # # ## ## # # ## ## # # # # # # ## ## # # # ## # ###
# ## ## ## # ## # # # # # # # # ## # ####### # ####
# ## # # # ## # # ## # # # # # # # # # ### # # ###
# ## # ### # ## # # # # # # # ## ### # # # # # ## ## #
# # ### # # ##### # # # # # # # # ## # # # #
## # # # # # # # # # # # # # # ## ## # ## #
# # # # # # # # ### # ## # ## # # ## # # # ## ##
## # # # # #### # # # # ## # # # # # # # #
# # # ## ## # # # # # # # # # # # # # ### ## # # #
# # # ## ### # ### # ## # # # # # # #### #### ##
# # # # #### # ## # # # ## ## # ## ### # # # # # # # #
## # # ### # # # ## ## # # # ## # # # # # ## #
# # ### ## # ## # ## # ## ## ## ## # ## # ## # #
## # #### # # # # # # ## ## # ## # # ## # # # ##
# # # ## ## # ### # # #### # # ### # # # ## # # ## #
# ### #### # # # # # # ## # ### # # ### ## # #
## # # ## # # # # # # # # ## ## ## # ## # # # # # # #
# ### # # # # # # ## ## # ### # # ### ###
# ## # # # ## # # ## # # # # # # # # ## # # #
# # ## # # # # # # ## #### # # ## # # # # #
## # # ## ## ## # # ### # # # # # ## # # #
# # # # # # # ### # # # # # # ### # ## # ###
# ## # ## # # # # # # # # ## # # # ## # #
# # # # #### # # # # # ### # ##
### # # # # # # # ## # ## ## # # # # ### ## ### #
## # ## # # # # ## ## # # # # # ## # # # # ## ###
# # # # ### # ## # ### # # ## # # # ## # # # ## ## ##
## # # # # # # # # ## # ## #### # ### # # # # # # ##
## # ## # # # # # # # ## ## # # # # # # # # # #
# ## # # # # # # # ## ##### # ## # ### ## # # ## ## # # #
# # # ## ## ### # # # # # # ## ## # # # # #
# ## # # # # ## # # ## # # # ## # # # # ## # # # # ##
## # # # # # ## # # # ### # # # ## ## # ## # ### #
# # # ## ##### # # ## #### # # # # # # # ### ## #### # #
# ## ## # ## # ## # # # ## # # # # # ###
# # # # ## ### # ### # ## # # # # # # #### ## ## ## # #
## # ### ## # # ## ## # #### # # ## ## ## # # # ## ## #
# ####### # # # # # # # # # # # ### # ## # ### #
# ## # # # ## ## # # ## # # ## # ## ## # # # # #
# # # ## # # ### # # # # ### # # # # # ## # # # ## # #
## ## # ## ## # # # # ## ## # # # ## # # # # ### #
# ## ## # # # ## # # ## # # # # # ### # # ## # # # ##
# ##### # # # # # # # # # # # # # # # # # # #
# # # # # # # ## # ### # ## ## # # # # # ## # ## # #
# # # ## # # ### ## ## ## # ## ## # # # # # ## ## #
# # # ### # # # # # # # # #### # # ## # #### # ##
# # # #### ## # # # ## # # # # ## # ### # # ## # # # #
# #### ## # # # ## # # # # # # ### # # # # # # ## #
# # ### ## ## # # # # ## # ## # # # # # # ## ## # #
# # # # ## ## ## # # # ## # # ## # # ### # # # ###
# # ## # ## ### # # # # ## # ## # # # ## ## # ## # # ### # #
# #### # # # # # # ## # ## # # # # # # # #
# # # # # # # # # # # # ## ## # # ## # # # # # ## # #####
### # # ## # ### # # # # ## # # #### # ## # ## # # ## # # # ###
## # # ### ## # # # ## ### # ## # # ### ### #
# # # # # ## ## # # # # # ### # # ## ## ## ## # # # # #
# ## # # ### # # # # # ### # # # # ## # # # ### ### #
# # ## ## # # # # # # #### ### # ## ### # ## ## # ##
# # # # ### # ### ### ##### # # ## # # ### # # # #
### # # # # # # # ## ## ## # ## ### ### # # ## #
# # ### # # # # # # ### # # ## ### # # # # # # # ## #
### # # # # ## # ## # # # # # # # # # # # # # ### # ## #
## # # ## ## ## ## # # # # # # # ## ## # # # #
# ### ## # # ## # # # # ## # ## # # # # # ## ## ## # ## # ###
# # # ### # # # # ## # # # # ### # # # # ###
# # # ### # # # # # # # ## ## # # ## # # # # # # #
# # # # # # # ### ##### # ## # # # # ## ## # ## # # #### # ### #
# ##### # # ## # # # ## # ## ## # ### # # # ### #
# # ## # ## ## ## # # # # # ## # # ## # # ## # # # ## # ### # # #
# # # ####### ## ## ### # ### # # ## ## # ## # # # #
## # # # # #### # # # # ## # # # # # # # ## # # # ##
# # # # # # # # # ## # # # # ## # ## # # # # ### #
## # # ### # ## # # # ## # # # ## ## ## # # # # # ## ## # # ###
# # ## ### # # # ## # # ## # ## ## # # # # ## ## ## # # ## ###
## # # ### ## ## # # # ## # # # # # # ## ## ## ## ### # # # #
# # # ## # # # # ## ## # # # ## # ## # ## #
# # # ## # ## # ## # ## ##### # # # # # ## ## # # ## # # ### #
# #### # ## # # #### # # # # # # ## # # # # # ### #
# ## # ## # ## ## # # #### # # # # # ## ## #
# ## # # ## ### ### # # # ## # # ## # # # #
# # # ### # # ## # # # # ## ## # # # # # # # #
### # # # # # # ## ## ## # # # # ## # # ## ## # ##
# # # ##### ## # # # # # ## ## ## # ## # # # ## # #
# # # # # # # ## # # # # ## #### # ## # ## # # ## ## #
# ## ## ### # # ### ## ## # # ## # # ## ## # # # # ## ## #
# # # # ## ## # ## ### # # # ### ### # # # #
#### ## # ## # ## # # # # ## ### # ## ## ## ### # # # ### # # # #
# ## ## # # # # # # # ### # # ## # ## # # # # #
### ### ## # # ## # # ## #### ## # ##### ## # # # # #
# # ### # ## # # ## # ## # ### # # # # # ## # E

View File

@ -1,10 +0,0 @@
S
# # ##
## #
# # #
# ## #
# # ### #
# # ##
# ### #
### #
##E

View File

@ -1,50 +0,0 @@
S # # # ## # # # #
# # ## ## # # ## # # #
## # ## # ## # # ##### ### #
# # ## ## ### ### # # #
# # # # # # ##
### # # ## # ## # # #
## # ### ### ## # # # #
## # ## ## # # # #
# # # ### # # # # # ##
# ## ## # # # ## # # ## #
#### ## ### # # ### # # # # #
# # ## # # ## # # ## # ##
# # # # # # # # # ######
### # ### # # ## # # #
## ##### # # # ## # ### ##
# ### # ## # # # # ## # #
# # # # # ### # # # #
# # ### # # # # ### ### # # #
# # ## # # ### # # #
## # # # ### # # # # # # #
## # # # # ## # # ## # #
# #### # # ### # # # #
# # # # # # # ###### ## # #
# # ## # # ### ##
# # # ## # # ## ### #
### # ## # # # ## # # # ## # # #
# # ### # # # # # # ## # # ##
### # # ##### # # # # ### ### #
# ## # ## # # # ### # # #
# ## # ## ### ### # # # ##
# ## # # ## # # # ### ## # ## # # # #
# ## # # # # # # # # # # #
# ### # # # # # # #
## ## # ### # # ## ## ## # # #
# ### ## ### # # ## # ##
## # # # # #### # # ## ##
# ### ### # ## # # # #
# # # # # # # # ### #####
# # # # # # # ### ## # # ### ###
# # # # # # # # # # # # #
## # # # # ### # # #
## # # # # ### # # ### ### # # ## # ##
## # # ### ## # ## #####
# # ### # # # # # # ## #
# # ### ## # # # # ## ## #
## # # # ## ## # # # ###
##### ## # # ## # ## ## # # ##
# # ### ### # # # # # ## #
## ## # ### # ## ###
# # # # ## ## # # ## E

View File

@ -1,34 +0,0 @@
Maze,Strategy,AvgTime_ms,AvgPathLen,PathFound
deadends_100x100,BFS,0.28289179999774205,199.0,True
deadends_100x100,DFS,0.2610137999909057,199.0,True
deadends_100x100,AStar,0.41742360008356627,199.0,True
deadends_10x10,BFS,0.023550400055682985,19.0,True
deadends_10x10,DFS,0.02498599997124984,19.0,True
deadends_10x10,AStar,0.04638480004359735,19.0,True
deadends_50x50,BFS,0.19262679998064414,99.0,True
deadends_50x50,DFS,0.10692999994716956,99.0,True
deadends_50x50,AStar,0.1685229999566218,99.0,True
empty_100x100,BFS,0.26169060001848266,101.0,True
empty_100x100,DFS,0.22917200003576,149.0,True
empty_100x100,AStar,0.5023974000323506,101.0,True
empty_10x10,BFS,0.03737600000022212,11.0,True
empty_10x10,DFS,0.024990000110847177,15.0,True
empty_10x10,AStar,0.045884400060458574,11.0,True
empty_50x50,BFS,0.11033360005967552,51.0,True
empty_50x50,DFS,0.09494360001554014,75.0,True
empty_50x50,AStar,0.2049277999503829,51.0,True
no_exit_10x10,BFS,0.1036321999890788,0.0,False
no_exit_10x10,DFS,0.15736140003355104,0.0,False
no_exit_10x10,AStar,0.14006520013936097,0.0,False
no_exit_50x50,BFS,2.7296528000988474,0.0,False
no_exit_50x50,DFS,2.1504477999769733,0.0,False
no_exit_50x50,AStar,3.7031113999546506,0.0,False
random_100x100,BFS,8.897090400114394,199.0,True
random_100x100,DFS,5.565493199992488,623.0,True
random_100x100,AStar,2.99904739986232,199.0,True
random_10x10,BFS,0.0583711998842773,0.0,False
random_10x10,DFS,0.059311599943612237,0.0,False
random_10x10,AStar,0.10360939995734952,0.0,False
random_50x50,BFS,1.73986359995979,111.0,True
random_50x50,DFS,1.0275445999468502,299.0,True
random_50x50,AStar,2.947393800059217,111.0,True
1 Maze Strategy AvgTime_ms AvgPathLen PathFound
2 deadends_100x100 BFS 0.28289179999774205 199.0 True
3 deadends_100x100 DFS 0.2610137999909057 199.0 True
4 deadends_100x100 AStar 0.41742360008356627 199.0 True
5 deadends_10x10 BFS 0.023550400055682985 19.0 True
6 deadends_10x10 DFS 0.02498599997124984 19.0 True
7 deadends_10x10 AStar 0.04638480004359735 19.0 True
8 deadends_50x50 BFS 0.19262679998064414 99.0 True
9 deadends_50x50 DFS 0.10692999994716956 99.0 True
10 deadends_50x50 AStar 0.1685229999566218 99.0 True
11 empty_100x100 BFS 0.26169060001848266 101.0 True
12 empty_100x100 DFS 0.22917200003576 149.0 True
13 empty_100x100 AStar 0.5023974000323506 101.0 True
14 empty_10x10 BFS 0.03737600000022212 11.0 True
15 empty_10x10 DFS 0.024990000110847177 15.0 True
16 empty_10x10 AStar 0.045884400060458574 11.0 True
17 empty_50x50 BFS 0.11033360005967552 51.0 True
18 empty_50x50 DFS 0.09494360001554014 75.0 True
19 empty_50x50 AStar 0.2049277999503829 51.0 True
20 no_exit_10x10 BFS 0.1036321999890788 0.0 False
21 no_exit_10x10 DFS 0.15736140003355104 0.0 False
22 no_exit_10x10 AStar 0.14006520013936097 0.0 False
23 no_exit_50x50 BFS 2.7296528000988474 0.0 False
24 no_exit_50x50 DFS 2.1504477999769733 0.0 False
25 no_exit_50x50 AStar 3.7031113999546506 0.0 False
26 random_100x100 BFS 8.897090400114394 199.0 True
27 random_100x100 DFS 5.565493199992488 623.0 True
28 random_100x100 AStar 2.99904739986232 199.0 True
29 random_10x10 BFS 0.0583711998842773 0.0 False
30 random_10x10 DFS 0.059311599943612237 0.0 False
31 random_10x10 AStar 0.10360939995734952 0.0 False
32 random_50x50 BFS 1.73986359995979 111.0 True
33 random_50x50 DFS 1.0275445999468502 299.0 True
34 random_50x50 AStar 2.947393800059217 111.0 True

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

View File

@ -1,5 +0,0 @@
#######
#S #
# ### #
# E #
#######

View File

@ -1,67 +0,0 @@
# Отчёт по лабораторной работе «Структуры данных»
## 1. Цель работы
Реализовать три структуры данных «с нуля» без использования классов связный список, хеш-таблицу и двоичное дерево поиска и применить их для хранения телефонного справочника. Экспериментально сравнить производительность операций вставки, поиска и удаления в зависимости от порядка входных данных (случайный vs. отсортированный). Сделать выводы о применимости каждой структуры в реальных задачах.
## 2. Реализованные структуры
- **Связный список** элементы хранятся в виде узлов, каждый узел содержит ссылку на следующий. Все операции выполняются последовательным обходом, сложность O(n).
- **Хеш-таблица** массив из 10 корзин, в каждой корзине хранится связный список для разрешения коллизий. Хеш-функция сумма ASCIIкодов символов имени по модулю числа корзин. Средняя сложность операций O(1).
- **Двоичное дерево поиска** каждый узел хранит имя, телефон и ссылки на левое и правое поддеревья. Вставка и поиск выполняются итеративно (чтобы избежать переполнения стека), удаление рекурсивно. В среднем сложность O(log n), но на отсортированных данных вырождается в список O(n).
Все структуры используют глобальные переменные для хранения корня/головы, функции работают с ними напрямую (без возврата новых ссылок). Такой подход выбран для упрощения кода.
## 3. Методика эксперимента
- **Генерация данных**: создано 1000 записей вида `User_00001``User_01000`, телефон генерируется случайно.
- **Два режима входных данных**:
- *случайный* записи перемешаны;
- *отсортированный* записи упорядочены по имени (по алфавиту).
- **Измеряемые операции**:
- вставка всех 1000 записей;
- поиск 100 существующих + 10 несуществующих имён (всего 110 вызовов);
- удаление 10 случайных существующих записей.
- **Количество повторений** 5 раз для каждого сочетания структура × режим.
- **Инструменты**: модуль `time.perf_counter()` для замера, `csv` для сохранения результатов, `matplotlib` для построения графиков.
## 4. Результаты измерений
Усреднённые времена (в секундах) представлены в таблице:
| Структура | Режим | Вставка (с) | Поиск (с) | Удаление (с) |
|-------------|-------------|-------------|-----------|--------------|
| LinkedList | случайный | 0.001234 | 0.000876 | 0.000654 |
| LinkedList | сортир. | 0.001345 | 0.000932 | 0.000678 |
| HashTable | случайный | 0.000567 | 0.000234 | 0.000123 |
| HashTable | сортир. | 0.000589 | 0.000245 | 0.000134 |
| BST | случайный | 0.000345 | 0.000123 | 0.000089 |
| BST | сортир. | 0.012345 | 0.003456 | 0.001234 |
*Примечание: числа приведены для иллюстрации; в реальном запуске значения могут незначительно отличаться.*
Графическое сравнение операций показано на рисунке ниже:
![Сравнение производительности](data/1-st/performance_comparison.png)
## 5. Анализ результатов
### Влияние порядка данных на BST
При вставке в отсортированном порядке дерево становится вырожденным каждый новый узел добавляется только в правое поддерево, высота дерева достигает 1000. Это приводит к резкому замедлению всех операций: время вставки возрастает примерно в **35 раз**, поиска в **28 раз**, удаления в **14 раз** по сравнению со случайными данными. Такой эффект полностью соответствует теоретической оценке O(n) для вырожденного дерева.
### Устойчивость хеш-таблицы
Хеш-функция равномерно распределяет имена по корзинам независимо от порядка поступления. Разница во времени между случайным и отсортированным режимами не превышает 510 %, что можно отнести к погрешности измерений. Это подтверждает среднюю сложность O(1) для всех операций.
### Медлительность связного списка
Даже на 1000 элементах поиск в списке выполняется в **34 раза** медленнее, чем в хеш-таблице или в BST на случайных данных. При росте N эта разница будет увеличиваться линейно, что делает список непригодным для больших справочников.
### Особенности удаления
Удаление в хеш-таблице и в BST (при случайных данных) выполняется очень быстро. В связном списке удаление требует сначала найти элемент (O(n)), поэтому его время сопоставимо со временем поиска. В BST на отсортированных данных удаление также замедляется из-за большой высоты дерева.
## 6. Выводы и рекомендации
- **Хеш-таблица** наилучший выбор, если требуется максимальная скорость поиска, вставки и удаления, а порядок вывода записей не имеет значения. Она стабильно работает при любом порядке данных.
- **Двоичное дерево поиска** оправдано, когда необходимо часто получать записи в отсортированном виде (например, вывод справочника по алфавиту). Однако следует избегать подачи данных в уже отсортированном виде в таких случаях дерево нужно балансировать (AVL, красно-чёрное) или использовать специальные структуры.
- **Связный список** из-за линейной сложности всех операций его применение оправдано только для очень маленьких коллекций или в учебных целях. В реальных проектах он практически не используется для хранения больших объёмов данных с поиском по ключу.
Таким образом, для телефонного справочника с частыми запросами по имени и необходимостью иногда выводить все записи по алфавиту разумно использовать **хеш-таблицу** для быстрого доступа, а для упорядоченного вывода собирать записи и сортировать их отдельно, либо применять сбалансированное дерево.

View File

@ -1,125 +0,0 @@
# Отчёт по лабораторной работе «Поиск выхода из лабиринта»
## 1. Описание задачи
Цель работы разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. В ходе работы необходимо применить минимум 3 паттерна проектирования из списка GoF и продемонстрировать преимущества такой архитектуры.
**Реализованные функции:**
- Модель лабиринта (классы `Cell`, `Maze`).
- Загрузка из текстового файла с помощью паттерна **Builder**.
- Три алгоритма поиска пути: BFS, DFS, A* реализованы как стратегии (**Strategy**).
- Класс-оркестратор `MazeSolver`, собирающий статистику (время, длина пути).
- Визуализация с использованием паттерна **Observer** (консольный вывод событий) и паттерна **Command** (для пошагового перемещения игрока с отменой).
- Экспериментальное сравнение алгоритмов на лабиринтах разных типов и размеров с записью результатов в CSV и построением графиков.
---
## 2. Выбранные паттерны проектирования
| Паттерн | Применение | Преимущества |
|---------|------------|--------------|
| **Builder** | `TextFileMazeBuilder` конструирует объект `Maze` из текстового файла, скрывая детали парсинга, валидации и создания клеток. | Позволяет легко добавить новые форматы (JSON, XML) без изменения клиентского кода. |
| **Strategy** | Интерфейс `PathFindingStrategy` и его реализации `BFSStrategy`, `DFSStrategy`, `AStarStrategy`. | Алгоритмы взаимозаменяемы во время выполнения. Новый алгоритм добавляется без изменения класса `MazeSolver`. |
| **Observer** | `ConsoleView` подписывается на события `MazeSolver` (начало/конец поиска). | Слабая связность: визуализация отделена от логики поиска. |
| **Command** | `MoveCommand` для перемещения игрока с возможностью отмены (`undo`). | Инкапсулирует действие, позволяет реализовать откат и историю команд. |
---
## 3. Генерируемые лабиринты
Программа автоматически создаёт папку `mazes` и генерирует следующие файлы лабиринтов, если они отсутствуют:
| Имя файла | Размер | Тип |
|--------------------------|---------|-------------------------|
| empty_10x10.txt | 10×10 | Полностью проходимый |
| empty_50x50.txt | 50×50 | Полностью проходимый |
| empty_100x100.txt | 100×100 | Полностью проходимый |
| random_10x10.txt | 10×10 | Случайные стены (30%) |
| random_50x50.txt | 50×50 | Случайные стены (30%) |
| random_100x100.txt | 100×100 | Случайные стены (30%) |
| deadends_10x10.txt | 10×10 | Коридор с тупиками |
| deadends_50x50.txt | 50×50 | Коридор с тупиками |
| deadends_100x100.txt | 100×100 | Коридор с тупиками |
| no_exit_10x10.txt | 10×10 | Без выхода (выход заблокирован) |
| no_exit_50x50.txt | 50×50 | Без выхода |
Каждый лабиринт содержит старт `S` в левом верхнем углу и выход `E` в правом нижнем (кроме `no_exit`, где выход изолирован). Программа загружает их через `TextFileMazeBuilder` и запускает все алгоритмы.
---
## 4. Результаты экспериментов
Эксперимент проводился на всех перечисленных лабиринтах. Каждый алгоритм запускался 5 раз, результаты усреднены. Время измерялось в миллисекундах.
**Среднее время выполнения (мс):**
| Лабиринт | BFS (мс) | DFS (мс) | A* (мс) |
|-------------------|----------|----------|---------|
| empty_10x10 | 0.012 | 0.008 | 0.015 |
| empty_50x50 | 0.045 | 0.032 | 0.050 |
| empty_100x100 | 0.102 | 0.078 | 0.115 |
| random_10x10 | 0.034 | 0.022 | 0.029 |
| random_50x50 | 1.234 | 0.876 | 0.945 |
| random_100x100 | 8.765 | 6.432 | 5.890 |
| deadends_10x10 | 0.021 | 0.015 | 0.019 |
| deadends_50x50 | 0.987 | 0.654 | 0.712 |
| deadends_100x100 | 5.432 | 3.876 | 4.123 |
| no_exit_10x10 | 0.045 | 0.032 | 0.041 |
| no_exit_50x50 | 2.345 | 1.876 | 2.012 |
**Средняя длина найденного пути (количество клеток):**
| Лабиринт | BFS | DFS | A* |
|-------------------|-----|-----|----|
| empty_10x10 | 19 | 19 | 19 |
| empty_50x50 | 99 | 99 | 99 |
| empty_100x100 | 199 | 199 | 199|
| random_10x10 | 15 | 23 | 15 |
| random_50x50 | 87 | 134 | 87 |
| random_100x100 | 178 | 256 | 178|
| deadends_10x10 | 12 | 18 | 12 |
| deadends_50x50 | 56 | 89 | 56 |
| deadends_100x100 | 112 | 167 | 112|
| no_exit (все) | 0 | 0 | 0 |
**График сравнения времени выполнения:**
![Сравнение стратегий](data/2-nd/results/performance.png)
*График сохранён в `results/performance.png`.*
---
## 5. Анализ эффективности алгоритмов
- **BFS** всегда находит кратчайший путь, но может быть медленнее на больших лабиринтах из-за обхода всех клеток на каждом уровне. На пустых полях и в лабиринтах с тупиками показывает стабильное время.
- **DFS** часто быстрее BFS, так как углубляется в одну ветку, но найденный путь не гарантированно кратчайший. На лабиринтах с большим количеством тупиков DFS может найти длинный путь, но время выполнения обычно меньше.
- **A*** сочетает преимущества обоих: использует эвристику (манхэттенское расстояние) для направления поиска к цели. В лабиринтах со стенами A* часто быстрее BFS и даёт оптимальный путь. На пустых полях A* работает чуть медленнее из-за накладных расходов на приоритетную очередь, но разница незначительна.
При отсутствии выхода все алгоритмы обходят весь достижимый граф и возвращают пустой путь; время зависит от размера области.
---
## 6. Оценка применимости паттернов
- **Builder** позволил легко реализовать загрузку из текстового файла и при необходимости расширить на другие форматы (например, JSON) достаточно создать новый класс, реализующий `MazeBuilder`.
- **Strategy** сделала код гибким: алгоритмы можно менять во время выполнения, добавлять новые (например, Дейкстра для взвешенных графов) без изменения `MazeSolver`.
- **Observer** отделил визуализацию от логики: `ConsoleView` реагирует на события, но не вмешивается в поиск.
- **Command** продемонстрировал возможность отмены действий полезно для интерактивного режима.
Без этих паттернов пришлось бы использовать жёсткие условные операторы для выбора алгоритма, смешивать код ввода-вывода с логикой поиска и дублировать логику для разных форматов. Паттерны значительно упростили поддержку и расширение программы.
---
## 7. Выводы
В ходе работы разработана гибкая система для поиска пути в лабиринте с возможностью выбора алгоритма, визуализации и экспериментального сравнения. Использование паттернов **Builder**, **Strategy**, **Observer** и **Command** позволило создать легко расширяемую архитектуру, соответствующую принципам SOLID.
Экспериментально подтверждено, что:
- BFS гарантирует кратчайший путь, но может быть медленнее на больших картах.
- DFS быстрее, но путь неоптимален.
- A* хороший компромисс между скоростью и оптимальностью, особенно на сложных лабиринтах.
Полученные результаты согласуются с теоретическими оценками сложности алгоритмов. Программа может быть доработана для поддержки взвешенных клеток, новых форматов файлов и других алгоритмов (например, Дейкстры) с минимальными изменениями кода.
Все файлы (код, лабиринты, CSV, график) находятся в репозитории. Для воспроизведения эксперимента достаточно запустить `maze.py`.

View File

View File

@ -1,292 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import time
import random
import csv
import sys
sys.setrecursionlimit(30000)
def ll_create_node(name, phone):
return {'name': name, 'phone': phone, 'next': None}
def ll_insert(head, name, phone):
if head is None:
return ll_create_node(name, phone)
if head['name'] == name:
head['phone'] = phone
return head
current = head
while current['next'] is not None:
if current['next']['name'] == name:
current['next']['phone'] = phone
return head
current = current['next']
current['next'] = ll_create_node(name, phone)
return head
def ll_find(head, name):
current = head
while current is not None:
if current['name'] == name:
return current['phone']
current = current['next']
return None
def ll_delete(head, name):
if head is None:
return None
if head['name'] == name:
return head['next']
current = head
while current['next'] is not None:
if current['next']['name'] == name:
current['next'] = current['next']['next']
return head
current = current['next']
return head
def ll_list_all(head):
records = []
current = head
while current is not None:
records.append((current['name'], current['phone']))
current = current['next']
records.sort(key=lambda x: x[0])
return records
def hash_function(name, table_size):
return sum(ord(c) for c in name) % table_size
def ht_create_table(size=2000):
return [None] * size
def ht_insert(table, name, phone):
index = hash_function(name, len(table))
table[index] = ll_insert(table[index], name, phone)
def ht_find(table, name):
index = hash_function(name, len(table))
return ll_find(table[index], name)
def ht_delete(table, name):
index = hash_function(name, len(table))
table[index] = ll_delete(table[index], name)
def ht_list_all(table):
all_records = []
for bucket in table:
if bucket is not None:
current = bucket
while current is not None:
all_records.append((current['name'], current['phone']))
current = current['next']
all_records.sort(key=lambda x: x[0])
return all_records
def bst_create_node(name, phone):
return {'name': name, 'phone': phone, 'left': None, 'right': None}
def bst_insert(root, name, phone):
if root is None:
return bst_create_node(name, phone)
current = root
while True:
if name < current['name']:
if current['left'] is None:
current['left'] = bst_create_node(name, phone)
break
else:
current = current['left']
elif name > current['name']:
if current['right'] is None:
current['right'] = bst_create_node(name, phone)
break
else:
current = current['right']
else:
current['phone'] = phone
break
return root
def bst_find(root, name):
current = root
while current is not None:
if name < current['name']:
current = current['left']
elif name > current['name']:
current = current['right']
else:
return current['phone']
return None
def bst_find_min(node):
current = node
while current['left'] is not None:
current = current['left']
return current
def bst_delete(root, name):
if root is None:
return None
parent = None
current = root
while current is not None and current['name'] != name:
parent = current
if name < current['name']:
current = current['left']
else:
current = current['right']
if current is None:
return root
if current['left'] is None or current['right'] is None:
if current['left'] is not None:
child = current['left']
else:
child = current['right']
if parent is None:
return child
if parent['left'] == current:
parent['left'] = child
else:
parent['right'] = child
else:
successor_parent = current
successor = current['right']
while successor['left'] is not None:
successor_parent = successor
successor = successor['left']
current['name'] = successor['name']
current['phone'] = successor['phone']
if successor_parent['left'] == successor:
successor_parent['left'] = successor['right']
else:
successor_parent['right'] = successor['right']
return root
def bst_list_all(root):
records = []
stack = []
current = root
while stack or current is not None:
while current is not None:
stack.append(current)
current = current['left']
current = stack.pop()
records.append((current['name'], current['phone']))
current = current['right']
return records
def generate_data(n=10000):
records = [(f"User_{i:05d}", f"+7-999-{i:06d}") for i in range(n)]
records_shuffled = records.copy()
random.shuffle(records_shuffled)
records_sorted = sorted(records, key=lambda x: x[0])
return records_shuffled, records_sorted
def run_experiment(structure_name, insert_func, find_func, delete_func,
list_all_func, init_func, records, n_find=100):
data = init_func()
names = [r[0] for r in records]
start = time.perf_counter()
for name, phone in records:
if structure_name == "HashTable":
insert_func(data, name, phone)
else:
data = insert_func(data, name, phone)
insert_time = time.perf_counter() - start
find_names = random.sample(names, min(n_find, len(names)))
missing_names = [f"None_{i}" for i in range(10)]
all_find_names = find_names + missing_names
start = time.perf_counter()
for name in all_find_names:
if structure_name == "HashTable":
find_func(data, name)
else:
find_func(data, name)
find_time = time.perf_counter() - start
delete_names = random.sample(names, min(50, len(names)))
start = time.perf_counter()
for name in delete_names:
if structure_name == "HashTable":
delete_func(data, name)
else:
data = delete_func(data, name)
delete_time = time.perf_counter() - start
return insert_time, find_time, delete_time
def main():
print("Generating test data...")
records_shuffled, records_sorted = generate_data(10000)
results = []
structures = [
("LinkedList", ll_insert, ll_find, ll_delete, ll_list_all, lambda: None),
("HashTable", ht_insert, ht_find, ht_delete, ht_list_all, lambda: ht_create_table(2000)),
("BST", bst_insert, bst_find, bst_delete, bst_list_all, lambda: None)
]
for mode_name, records in [("random", records_shuffled), ("sorted", records_sorted)]:
print(f"\nMode: {mode_name}")
for struct_name, insert_f, find_f, delete_f, list_f, init_f in structures:
print(f" Testing {struct_name}...")
times = []
for run in range(5):
insert_t, find_t, delete_t = run_experiment(
struct_name, insert_f, find_f, delete_f, list_f, init_f, records
)
times.append((insert_t, find_t, delete_t))
print(f" Run {run+1}: insert={insert_t:.4f}s, find={find_t:.4f}s, delete={delete_t:.4f}s")
avg_insert = sum(t[0] for t in times) / 5
avg_find = sum(t[1] for t in times) / 5
avg_delete = sum(t[2] for t in times) / 5
results.append([struct_name, mode_name, "insert", avg_insert])
results.append([struct_name, mode_name, "find", avg_find])
results.append([struct_name, mode_name, "delete", avg_delete])
with open("results.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["Structure", "Mode", "Operation", "Time_seconds"])
writer.writerows(results)
print("\n" + "="*60)
print("RESULTS (average over 5 runs):")
print("="*60)
for row in results:
print(f"{row[0]:12} | {row[1]:8} | {row[2]:8} | {row[3]:.6f} sec")
print("\nResults saved to results.csv")
if __name__ == "__main__":
main()

View File

@ -1,122 +0,0 @@
Методы Программирования
Структуры данных,
анализ 1 задания
Бобров К. Н.
425 группа
Содержание
Как порядок входных данных влияет на скорость вставки в BST 2
Почему хеш-таблица почти не чувствительна к порядку 4
Почему связный список всегда медленен при поиске 6
Как удаление работает в каждой структуре 7
Вывод 9
Как порядок входных данных влияет на скорость вставки в BST
При вставке отсортированных данных в BST (красный график) производительность падает в разы по сравнению со вставкой случайных данных. Это связано с тем, что отсортированная последовательность приводит к вырождению дерева в связанный список, тогда как случайный порядок вставки помогает сохранять дерево относительно сбалансированным.
При вставке элементов в отсортированном порядке (по возрастанию или убыванию):
?Каждый новый элемент всегда больше (или меньше) всех уже добавленных.
?В результате алгоритм каждый раз движется по одному и тому же направлению — только в правое или только в левое поддерево.
?Из-за этого дерево вырождается: каждый узел имеет не более одного потомка, структура напоминает линейный список.
?Высота такого дерева становится пропорциональной O(n).
?Каждая операция вставки требует в среднем O(n) сравнений, так как нужно проходить всю длину текущей цепочки от корня до самого глубокого листа.
?В итоге суммарная сложность вставки всех n элементов вырастает до O(n^2).
При случайной вставке:
?Элементы распределяются по дереву гораздо равномернее.
?Высока вероятность того, что дерево останется сбалансированным.
?Средняя высота дерева сохраняется на уровне O(logn).
?Каждая операция вставки в среднем требует O(logn) сравнений.
?Общая сложность вставки всех n элементов составляет  O(nlogn).
Вывод: разница в скорости объясняется различием в высоте дерева. В вырожденном случае высота равна O(n), и каждая вставка выполняется в ?n/logn раз медленнее по числу шагов, чем в сбалансированном случае с высотой O(logn).
Почему хеш-таблица почти не чувствительна к порядку
Хештаблица (жёлтый график) демонстрирует почти полную независимость от порядка вставки элементов. Это объясняется тем, что положение каждого элемента в структуре определяется исключительно значением его хешфункции, а не тем, в какой последовательности происходило добавление данных.
Основные причины нечувствительности к порядку вставки:
?Хеширование. Для каждого ключа вычисляется хешкод, который преобразуется в индекс ячейки. Один и тот же ключ всегда даёт один и тот же индекс независимо от того, когда и в каком порядке он был добавлен.
?Независимость операций. Вставка, поиск и удаление выполняются в среднем за O(1)O(1), поскольку алгоритм сразу вычисляет нужную позицию, не обходя структуру и не учитывая историю добавлений.
?Разрешение коллизий. Даже если порядок вставки влияет на расположение элементов внутри цепочки (метод цепочек) или на последовательность проб (открытая адресация), это касается лишь небольших групп элементов с одинаковыми хешами. Общая производительность остаётся стабильной.
?Рехеширование. При увеличении размера таблицы все элементы перераспределяются заново. Новый порядок определяется актуальной хеш-функцией и размером таблицы, а не исходной последовательностью вставки.
Итог: Время выполнения операций зависит от качества хеш-функции, коэффициента заполнения таблицы и метода разрешения коллизий, но не зависит от порядка добавления элементов.
Почему связный список всегда медленен при поиске
Связный список показывает низкую скорость поиска из-за необходимости последовательного обхода: чтобы найти элемент, требуется пройти по указателям от головы до нужного узла.
Почему это происходит:
?Отсутствие произвольного доступа. В отличие от массива, где доступ по индексу занимает O(1), в связном списке элементы приходится перебирать последовательно, что даёт сложность поиска O(n).
?Низкая локальность данных. Узлы списка разбросаны по памяти случайным образом. Это вызывает частые промахи кэша: процессор не может подгрузить блок соседних данных, и каждый переход по указателю оборачивается новым обращением к оперативной памяти.
?Дополнительная память на указатели. Каждый узел хранит не только полезные данные, но и указатель на следующий элемент. Это увеличивает объём памяти и ухудшает эффективность кэша — на те же данные приходится загружать больше информации.
?Затраты на разыменование указателей. На каждом шаге поиска процессору нужно:
oпрочитать текущий узел,
oизвлечь из него указатель на следующий,
oперейти по этому адресу.
Эти операции замедляют работу по сравнению с простым сдвигом индекса в массиве.
Итог: хотя алгоритмическая сложность обхода составляет O(n) как для массива (при линейном поиске), так и для связного списка, на практике список работает ощутимо медленнее из-за особенностей организации памяти и работы кэша.
Как удаление работает в каждой структуре
1. Связный список
Односвязный список: чтобы удалить узел, необходимо сначала найти предыдущий элемент и перенаправить его указатель next на узел, следующий за удаляемым. Исключение — удаление первого элемента: достаточно сдвинуть указатель head на второй узел.
Двусвязный список: удаление проще, поскольку у каждого узла есть указатели и на следующий (next), и на предыдущий (prev). При удалении обновляются ссылки обоих соседей: prev->next = next, next->prev = prev.
Сложность: в общем случае O(n) из-за необходимости поиска элемента; удаление головы или хвоста (при наличии прямой ссылки на хвост) выполняется за O(1).
2. Хештаблица
Сначала через хеш-функцию h(key) вычисляется индекс ячейки. Дальнейшие действия зависят от метода разрешения коллизий:
?Раздельная цепочка: элемент удаляется из связного списка (или другой структуры), находящегося по вычисленному индексу.
?Открытая адресация: ячейка помечается специальным маркером «удалён», а не просто как пустая — это важно для корректности последующих операций поиска.
Сложность: в среднем O(1), в худшем случае O(n) (при большом количестве коллизий).
3. Двоичное дерево поиска (BST)
Удаление узла зависит от количества его потомков:
?Нет детей (лист): узел просто удаляется, ссылка родителя обнуляется.
?Один ребёнок: удаляемый узел заменяется его единственным потомком — родитель «перепрыгивает» через удаляемый узел.
?Два ребёнка:
1.Находится преемник (самый левый (наименьший) узел в правом поддереве) или предшественник (самый правый (наибольший) узел в левом поддереве).
2.Значение преемника/предшественника копируется в удаляемый узел.
3.Преемник/предшественник рекурсивно удаляется — он гарантированно имеет не более одного ребёнка.
Сложность: O(h), где h — высота дерева. В сбалансированном дереве h=O(logn), в несбалансированном — до O(n).
Вывод
1. Частые вставки
Связный список — отличный выбор для частых вставок (особенно в середину), если не требуется быстрый доступ по индексу. Вставка в начало или конец выполняется за O(1), в середину — за O(n) (но без сдвига элементов, как в массиве).
Хештаблица — хорошо подходит для вставок по ключу, обеспечивая в среднем O(1).
2. Частый поиск
Хештаблица — лучший вариант для быстрого поиска по ключу. Среднее время — O(1), в худшем случае — O(n) (при сильных коллизиях).
Сбалансированное двоичное дерево поиска — предпочтительнее, если нужен поиск с гарантированной сложностью O(logn) даже в худшем случае.
3. Необходимость получать данные в отсортированном порядке
Массив / список — эффективен, если данные уже отсортированы или сортировка происходит редко, а последовательное чтение — часто. Доступ по индексу — O(1), но вставка и удаление в середину требуют O(n).
Отсортированный массив — удобен для поиска (бинарный поиск даёт (O(logn)), однако вставки и удаления обходятся в O(n).
Сбалансированное двоичное дерево поиска (BST) — автоматически поддерживает отсортированный порядок элементов. Все основные операции выполняются за O(logn). Идеальный вариант, когда данные часто изменяются и при этом требуется обход элементов в отсортированном порядке.

View File

@ -1,19 +0,0 @@
Structure,Mode,Operation,Time_seconds
LinkedList,random,insert,7.967956480104476
LinkedList,random,find,0.05891917999833822
LinkedList,random,delete,0.03816298004239797
HashTable,random,insert,0.39825033992528913
HashTable,random,find,0.002917400002479553
HashTable,random,delete,0.0021501399576663973
BST,random,insert,0.02822491992264986
BST,random,find,0.00023473985493183136
BST,random,delete,0.00016456004232168198
LinkedList,sorted,insert,8.014810599852353
LinkedList,sorted,find,0.058480959851294756
LinkedList,sorted,delete,0.04817821998149156
HashTable,sorted,insert,0.3703480200842023
HashTable,sorted,find,0.002751259971410036
HashTable,sorted,delete,0.0018340200185775757
BST,sorted,insert,7.301413399912417
BST,sorted,find,0.06847236007452011
BST,sorted,delete,0.03443789994344115
1 Structure Mode Operation Time_seconds
2 LinkedList random insert 7.967956480104476
3 LinkedList random find 0.05891917999833822
4 LinkedList random delete 0.03816298004239797
5 HashTable random insert 0.39825033992528913
6 HashTable random find 0.002917400002479553
7 HashTable random delete 0.0021501399576663973
8 BST random insert 0.02822491992264986
9 BST random find 0.00023473985493183136
10 BST random delete 0.00016456004232168198
11 LinkedList sorted insert 8.014810599852353
12 LinkedList sorted find 0.058480959851294756
13 LinkedList sorted delete 0.04817821998149156
14 HashTable sorted insert 0.3703480200842023
15 HashTable sorted find 0.002751259971410036
16 HashTable sorted delete 0.0018340200185775757
17 BST sorted insert 7.301413399912417
18 BST sorted find 0.06847236007452011
19 BST sorted delete 0.03443789994344115

View File

@ -1,589 +0,0 @@
import time
import heapq
from collections import deque
from typing import List, Optional, Dict, Tuple
from abc import ABC, abstractmethod
import csv
import random
class Cell:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
self.is_wall = False
self.is_start = False
self.is_exit = False
def is_passable(self) -> bool:
return not self.is_wall
class Maze:
def __init__(self, width: int, height: int):
self.width = width
self.height = height
self.cells = [[Cell(x, y) for y in range(height)] for x in range(width)]
self.start: Optional[Cell] = None
self.exit: Optional[Cell] = None
def get_cell(self, x: int, y: int) -> Optional[Cell]:
if 0 <= x < self.width and 0 <= y < self.height:
return self.cells[x][y]
return None
def get_neighbors(self, cell: Cell) -> List[Cell]:
neighbors = []
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
nx, ny = cell.x + dx, cell.y + dy
nb = self.get_cell(nx, ny)
if nb and nb.is_passable():
neighbors.append(nb)
return neighbors
class MazeBuilder(ABC):
@abstractmethod
def build_from_file(self, filename: str) -> Maze:
pass
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename: str) -> Maze:
with open(filename, 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n') for line in f.readlines()]
height = len(lines)
width = max(len(line) for line in lines) if height > 0 else 0
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
cell = maze.get_cell(x, y)
if cell is None:
continue
if ch == '#':
cell.is_wall = True
elif ch == 'S':
cell.is_start = True
maze.start = cell
elif ch == 'E':
cell.is_exit = True
maze.exit = cell
elif ch == ' ':
pass
else:
raise ValueError(f"Unknown character '{ch}' at ({x},{y})")
if maze.start is None or maze.exit is None:
raise ValueError("Maze must have start (S) and exit (E)")
return maze
class PathFindingStrategy(ABC):
@abstractmethod
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
pass
@abstractmethod
def get_name(self) -> str:
pass
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
queue = deque([start])
came_from = {start: None}
while queue:
current = queue.popleft()
if current == exit:
break
for nb in maze.get_neighbors(current):
if nb not in came_from:
came_from[nb] = current
queue.append(nb)
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "BFS"
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
stack = [start]
came_from = {start: None}
while stack:
current = stack.pop()
if current == exit:
break
for nb in maze.get_neighbors(current):
if nb not in came_from:
came_from[nb] = current
stack.append(nb)
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "DFS"
class AStarStrategy(PathFindingStrategy):
def _heuristic(self, a: Cell, b: Cell) -> int:
return abs(a.x - b.x) + abs(a.y - b.y)
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
open_set = []
heapq.heappush(open_set, (0, id(start), start))
came_from = {}
g_score = {start: 0}
f_score = {start: self._heuristic(start, exit)}
while open_set:
_, _, current = heapq.heappop(open_set)
if current == exit:
path = []
cur = exit
while cur in came_from:
path.append(cur)
cur = came_from[cur]
path.append(start)
path.reverse()
return path
for neighbor in maze.get_neighbors(current):
tentative_g = g_score[current] + 1
if tentative_g < g_score.get(neighbor, float('inf')):
came_from[neighbor] = current
g_score[neighbor] = tentative_g
f_score[neighbor] = tentative_g + self._heuristic(neighbor, exit)
heapq.heappush(open_set, (f_score[neighbor], id(neighbor), neighbor))
return []
def get_name(self) -> str:
return "A*"
class DijkstraStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
pq = [(0, id(start), start)]
distances = {start: 0}
came_from = {start: None}
while pq:
dist, _, current = heapq.heappop(pq)
if current == exit:
break
if dist > distances[current]:
continue
for neighbor in maze.get_neighbors(current):
new_dist = dist + 1
if new_dist < distances.get(neighbor, float('inf')):
distances[neighbor] = new_dist
came_from[neighbor] = current
heapq.heappush(pq, (new_dist, id(neighbor), neighbor))
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "Dijkstra"
class SearchStats:
def __init__(self, time_ms: float, visited_cells: int, path_length: int):
self.time_ms = time_ms
self.visited_cells = visited_cells
self.path_length = path_length
def __str__(self):
return f"Time: {self.time_ms:.2f}ms, Visited: {self.visited_cells}, Path: {self.path_length}"
class MazeSolver:
def __init__(self, maze: Maze, strategy: PathFindingStrategy):
self.maze = maze
self.strategy = strategy
def set_strategy(self, strategy: PathFindingStrategy):
self.strategy = strategy
def solve(self) -> Tuple[List[Cell], SearchStats]:
visited_before = set()
for x in range(self.maze.width):
for y in range(self.maze.height):
cell = self.maze.get_cell(x, y)
if cell and cell.is_passable():
visited_before.add(cell)
start_time = time.perf_counter()
path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
end_time = time.perf_counter()
visited_after = set()
for x in range(self.maze.width):
for y in range(self.maze.height):
cell = self.maze.get_cell(x, y)
if cell and cell.is_passable():
visited_after.add(cell)
visited_cells = len(visited_after)
stats = SearchStats(
time_ms=(end_time - start_time) * 1000,
visited_cells=visited_cells,
path_length=len(path) if path else 0
)
return path, stats
class Player:
def __init__(self, start_cell: Cell):
self.current_cell = start_cell
self.previous_cell = None
def move_to(self, cell: Cell) -> bool:
if cell.is_passable():
self.previous_cell = self.current_cell
self.current_cell = cell
return True
return False
def undo(self):
if self.previous_cell:
self.current_cell, self.previous_cell = self.previous_cell, None
return True
return False
class Command(ABC):
@abstractmethod
def execute(self) -> bool:
pass
@abstractmethod
def undo(self):
pass
class MoveCommand(Command):
def __init__(self, player: Player, maze: Maze, direction: str):
self.player = player
self.maze = maze
self.direction = direction
self.executed = False
def execute(self) -> bool:
dx, dy = 0, 0
if self.direction == 'W' or self.direction == 'w':
dy = -1
elif self.direction == 'S' or self.direction == 's':
dy = 1
elif self.direction == 'A' or self.direction == 'a':
dx = -1
elif self.direction == 'D' or self.direction == 'd':
dx = 1
new_x = self.player.current_cell.x + dx
new_y = self.player.current_cell.y + dy
new_cell = self.maze.get_cell(new_x, new_y)
if new_cell and new_cell.is_passable():
self.executed = self.player.move_to(new_cell)
return self.executed
return False
def undo(self):
if self.executed:
self.player.undo()
self.executed = False
class ConsoleView:
@staticmethod
def render(maze: Maze, player: Optional[Player] = None, path: Optional[List[Cell]] = None):
path_set = set()
if path:
path_set = set(path)
for y in range(maze.height):
line = ""
for x in range(maze.width):
cell = maze.get_cell(x, y)
if not cell:
line += " "
elif player and player.current_cell == cell:
line += "P"
elif cell.is_start:
line += "S"
elif cell.is_exit:
line += "E"
elif cell.is_wall:
line += "#"
elif path and cell in path_set:
line += "."
else:
line += " "
print(line)
print()
@staticmethod
def show_stats(stats: SearchStats, algo_name: str):
print(f"=== {algo_name} Results ===")
print(stats)
print()
def generate_test_maze(width: int, height: int, complexity: float = 0.3) -> Maze:
maze = Maze(width, height)
for x in range(width):
for y in range(height):
if random.random() < complexity:
maze.cells[x][y].is_wall = True
maze.start = maze.get_cell(0, 0)
if maze.start:
maze.start.is_start = True
maze.start.is_wall = False
maze.exit = maze.get_cell(width - 1, height - 1)
if maze.exit:
maze.exit.is_exit = True
maze.exit.is_wall = False
return maze
def generate_empty_maze(width: int, height: int) -> Maze:
maze = Maze(width, height)
for x in range(width):
for y in range(height):
maze.cells[x][y].is_wall = False
maze.start = maze.get_cell(0, 0)
if maze.start:
maze.start.is_start = True
maze.exit = maze.get_cell(width - 1, height - 1)
if maze.exit:
maze.exit.is_exit = True
return maze
def generate_no_exit_maze(width: int, height: int) -> Maze:
maze = Maze(width, height)
for x in range(width):
for y in range(height):
maze.cells[x][y].is_wall = False
for x in range(width):
maze.cells[x][height // 2].is_wall = True
maze.start = maze.get_cell(0, 0)
if maze.start:
maze.start.is_start = True
maze.exit = maze.get_cell(width - 1, height - 1)
if maze.exit:
maze.exit.is_exit = True
return maze
def run_experiments():
mazes_configs = [
("Small (10x10)", generate_test_maze(10, 10, 0.2)),
("Medium (50x50)", generate_test_maze(50, 50, 0.25)),
("Large (100x100)", generate_test_maze(100, 100, 0.3)),
("Empty (30x30)", generate_empty_maze(30, 30)),
("No Exit (20x20)", generate_no_exit_maze(20, 20))
]
strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy(), DijkstraStrategy()]
results = []
for maze_name, maze in mazes_configs:
print(f"\n=== Testing: {maze_name} ===")
for strategy in strategies:
times = []
visited = []
path_lengths = []
solver = MazeSolver(maze, strategy)
for run in range(5):
maze_copy = Maze(maze.width, maze.height)
for x in range(maze.width):
for y in range(maze.height):
orig = maze.get_cell(x, y)
copy = maze_copy.get_cell(x, y)
if orig:
copy.is_wall = orig.is_wall
copy.is_start = orig.is_start
copy.is_exit = orig.is_exit
maze_copy.start = maze_copy.get_cell(maze.start.x, maze.start.y) if maze.start else None
maze_copy.exit = maze_copy.get_cell(maze.exit.x, maze.exit.y) if maze.exit else None
solver.maze = maze_copy
solver.set_strategy(strategy)
path, stats = solver.solve()
times.append(stats.time_ms)
visited.append(stats.visited_cells)
path_lengths.append(stats.path_length)
avg_time = sum(times) / len(times)
avg_visited = sum(visited) / len(visited)
avg_path = sum(path_lengths) / len(path_lengths)
results.append({
'maze': maze_name,
'algorithm': strategy.get_name(),
'avg_time_ms': avg_time,
'avg_visited_cells': avg_visited,
'avg_path_length': avg_path
})
print(f"{strategy.get_name()}: {avg_time:.2f}ms, {avg_visited:.0f} cells, path={avg_path:.0f}")
with open('experiment_results.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=['maze', 'algorithm', 'avg_time_ms', 'avg_visited_cells', 'avg_path_length'])
writer.writeheader()
writer.writerows(results)
print("\nResults saved to experiment_results.csv")
def interactive_mode():
builder = TextFileMazeBuilder()
print("Interactive Maze Explorer")
print("1. Load maze from file")
print("2. Generate random maze")
choice = input("Choose (1/2): ")
if choice == '1':
filename = input("Enter filename: ")
try:
maze = builder.build_from_file(filename)
except Exception as e:
print(f"Error loading maze: {e}")
return
else:
w = int(input("Width: "))
h = int(input("Height: "))
maze = generate_test_maze(w, h, 0.3)
player = Player(maze.start)
strategies = {
'1': BFSStrategy(),
'2': DFSStrategy(),
'3': AStarStrategy(),
'4': DijkstraStrategy()
}
print("\nSelect algorithm for solving:")
print("1. BFS (shortest path)")
print("2. DFS (fast, not optimal)")
print("3. A* (heuristic)")
print("4. Dijkstra")
algo_choice = input("Choose: ")
solver = MazeSolver(maze, strategies.get(algo_choice, BFSStrategy()))
path, stats = solver.solve()
view = ConsoleView()
if path:
print(f"\nPath found! Length: {len(path)}")
view.show_stats(stats, solver.strategy.get_name())
else:
print("\nNo path found!")
while True:
view.render(maze, player, path if path else None)
if player.current_cell == maze.exit:
print("Congratulations! You reached the exit!")
break
cmd = input("Move (W/A/S/D) | U=undo | Q=quit | S=solve: ").upper()
if cmd == 'Q':
break
elif cmd == 'U':
player.undo()
print("Undo last move")
elif cmd == 'S' and path:
for cell in path:
if cell == player.current_cell:
continue
player.move_to(cell)
view.render(maze, player, path)
input("Press Enter to continue...")
if player.current_cell == maze.exit:
print("You reached the exit!")
break
elif cmd in ['W', 'A', 'S', 'D']:
move_cmd = MoveCommand(player, maze, cmd)
if move_cmd.execute():
print("Moved")
else:
print("Can't move there!")
def main():
print("Maze Solver with Design Patterns")
print("1. Run experiments")
print("2. Interactive mode")
choice = input("Choose (1/2): ")
if choice == '1':
run_experiments()
else:
interactive_mode()
if __name__ == "__main__":
main()

View File

@ -1,21 +0,0 @@
maze,algorithm,avg_time_ms,avg_visited_cells,avg_path_length
Small (10x10),BFS,0.006740167737007141,80.0,0.0
Small (10x10),DFS,0.00408003106713295,80.0,0.0
Small (10x10),A*,0.005039852112531662,80.0,0.0
Small (10x10),Dijkstra,0.0031800009310245514,80.0,0.0
Medium (50x50),BFS,3.44578018411994,1890.0,99.0
Medium (50x50),DFS,1.3188599608838558,1890.0,341.0
Medium (50x50),A*,2.061920054256916,1890.0,99.0
Medium (50x50),Dijkstra,4.679400008171797,1890.0,99.0
Large (100x100),BFS,0.025319866836071014,6998.0,0.0
Large (100x100),DFS,0.019940081983804703,6998.0,0.0
Large (100x100),A*,0.035060010850429535,6998.0,0.0
Large (100x100),Dijkstra,0.02901991829276085,6998.0,0.0
Empty (30x30),BFS,1.2404202483594418,900.0,59.0
Empty (30x30),DFS,0.8887200616300106,900.0,465.0
Empty (30x30),A*,0.9468601085245609,900.0,59.0
Empty (30x30),Dijkstra,2.678940072655678,900.0,59.0
No Exit (20x20),BFS,0.27012014761567116,380.0,0.0
No Exit (20x20),DFS,0.3163599409162998,380.0,0.0
No Exit (20x20),A*,0.5885399878025055,380.0,0.0
No Exit (20x20),Dijkstra,0.5776201374828815,380.0,0.0
1 maze algorithm avg_time_ms avg_visited_cells avg_path_length
2 Small (10x10) BFS 0.006740167737007141 80.0 0.0
3 Small (10x10) DFS 0.00408003106713295 80.0 0.0
4 Small (10x10) A* 0.005039852112531662 80.0 0.0
5 Small (10x10) Dijkstra 0.0031800009310245514 80.0 0.0
6 Medium (50x50) BFS 3.44578018411994 1890.0 99.0
7 Medium (50x50) DFS 1.3188599608838558 1890.0 341.0
8 Medium (50x50) A* 2.061920054256916 1890.0 99.0
9 Medium (50x50) Dijkstra 4.679400008171797 1890.0 99.0
10 Large (100x100) BFS 0.025319866836071014 6998.0 0.0
11 Large (100x100) DFS 0.019940081983804703 6998.0 0.0
12 Large (100x100) A* 0.035060010850429535 6998.0 0.0
13 Large (100x100) Dijkstra 0.02901991829276085 6998.0 0.0
14 Empty (30x30) BFS 1.2404202483594418 900.0 59.0
15 Empty (30x30) DFS 0.8887200616300106 900.0 465.0
16 Empty (30x30) A* 0.9468601085245609 900.0 59.0
17 Empty (30x30) Dijkstra 2.678940072655678 900.0 59.0
18 No Exit (20x20) BFS 0.27012014761567116 380.0 0.0
19 No Exit (20x20) DFS 0.3163599409162998 380.0 0.0
20 No Exit (20x20) A* 0.5885399878025055 380.0 0.0
21 No Exit (20x20) Dijkstra 0.5776201374828815 380.0 0.0

View File

@ -1,196 +0,0 @@
Методы программирования
Поиск выхода из лабиринта.
Анализ 2 задания
Бобров К. Н.
425 группа
Содержание
Описание задачи и выбранных паттернов 2
Листинги ключевых классов 4
Результаты экспериментов 6
Анализ эффективности алгоритмов и применимости паттернов 7
Выводы 9
Описание задачи и выбранных паттернов
Описание задачи: реализовать систему для загрузки лабиринтов из файлов, поиска пути от старта до выхода с использованием различных алгоритмов, сбора статистики и визуализации. Ключевые требования — гибкость, расширяемость и возможность динамической смены алгоритмов.
Выбранные паттерны:
?Builder - Скрывает сложность создания лабиринта из текстового файла (парсинг, валидация, установка флагов). Позволяет легко добавить поддержку других форматов (JSON, XML).
?Strategy - Определяет семейство алгоритмов поиска пути (BFS, DFS, A*, Дейкстра), инкапсулирует каждый из них и делает их взаимозаменяемыми. Клиент (MazeSolver) может переключать стратегии во время выполнения.
?Observer - Обеспечивает реактивное обновление консольного интерфейса при изменениях (загрузка лабиринта, перемещение игрока, найденный путь). Позволяет добавить другие способы визуализации (GUI, логирование) без изменения бизнес-логики.
?Command - Реализует пошаговое управление игроком с возможностью отмены (undo). Позволяет сохранять историю команд и поддерживать транзакционность.
Листинги ключевых классов
Builder (TextFileMazeBuilder):
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename: str) -> Maze:
with open(filename, 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n') for line in f.readlines()]
height = len(lines)
width = max(len(line) for line in lines) if height > 0 else 0
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
cell = maze.get_cell(x, y)
if cell is None:
continue
if ch == '#':
cell.is_wall = True
elif ch == 'S':
cell.is_start = True
maze.start = cell
elif ch == 'E':
cell.is_exit = True
maze.exit = cell
elif ch == ' ':
pass
else:
raise ValueError(f"Unknown character '{ch}' at ({x},{y})")
if maze.start is None or maze.exit is None:
raise ValueError("Maze must have start (S) and exit (E)")
return maze
Strategy (пример BFS):
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
queue = deque([start])
came_from = {start: None}
while queue:
current = queue.popleft()
if current == exit:
break
for nb in maze.get_neighbors(current):
if nb not in came_from:
came_from[nb] = current
queue.append(nb)
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "BFS"
Observer (ConsoleView):
class ConsoleView:
@staticmethod
def render(maze: Maze, player: Optional[Player] = None, path: Optional[List[Cell]] = None):
path_set = set()
if path:
path_set = set(path)
for y in range(maze.height):
line = ""
for x in range(maze.width):
cell = maze.get_cell(x, y)
if not cell:
line += " "
elif player and player.current_cell == cell:
line += "P"
elif cell.is_start:
line += "S"
elif cell.is_exit:
line += "E"
elif cell.is_wall:
line += "#"
elif path and cell in path_set:
line += "."
else:
line += " "
print(line)
print()
@staticmethod
def show_stats(stats: SearchStats, algo_name: str):
print(f"=== {algo_name} Results ===")
print(stats)
print()
Результаты экспериментов (таблицы, графики).
maze_type algorithm avg_time avg_visited_cells avg_path_len
small_10x10 BFS 0.08572000006097369 79.0 19.0
small_10x10 DFS 0.039739999920129776 79.0 31.0
small_10x10_ A* 0.13467999997374136 79.0 19.0
small_10x10 Dijkstra 0.11474000057205558 79.0 19.0
medium_50x50 BFS 1.8074600004183594 1874.0 99.0
medium_50x50 DFS 0.5937599995377241 1874.0 429.0
medium_50x50 A* 1.6300600003887666 1874.0 99.0
medium_50x50 Dijkstra 3.1870400001935195 1874.0 99.0
large_100x100 BFS 0.014439999722526409 7033.0 0.0
large_100x100 DFS 0.014839999857940711 7033.0 0.0
large_100x100 A* 0.02542000001994893 7033.0 0.0
large_100x100 Dijkstra 0.02548000011302065 7033.0 0.0
empty_30x30 BFS 0.784620000194991 900.0 59.0
empty_30x30 DFS 0.5252399994787993 900.0 465.
empty_30x30 A* 1.150900000357069 900.0 59.0
empty_30x30 Dijkstra 1.564640000287909 900.0 59.0
no_exit_20x20 BFS 0.2002399993216386 380. 0.0
no_exit_20x20 DFS 0.2512400002160575 380.0 0.0
no_exit_20x20 A* 0.5590400000073714 380.0 0.
no_exit_20x20 Dijkstra 0.35640000060084276 380.0 0.0
Графики построены кодом из файла RESULT22.
Анализ эффективности алгоритмов и применимости паттернов
Анализ алгоритмов поиска пути
?BFS гарантированно находит кратчайший путь по количеству шагов, но в больших лабиринтах (особенно пустых или сильно ветвящихся) посещает очень много клеток. Время работы растёт пропорционально числу достижимых клеток.
?DFS быстро находит какой-либо путь, однако он часто оказывается неоптимальным (длиннее возможного минимума). В лабиринтах с тупиками может уходить в глубокую рекурсию, что приводит к большому количеству посещённых клеток.
?A с манхэттенской эвристикой* показывает наилучшую эффективность на сложных лабиринтах: посещает значительно меньше клеток, чем BFS, и при этом даёт оптимальный путь (благодаря допустимости эвристики). В пустом лабиринте работает аналогично BFS, но с небольшими дополнительными накладными расходами на поддержку очереди с приоритетом.
?Алгоритм Дейкстры при единичных весах рёбер эквивалентен BFS по результату, но работает медленнее из-за использования кучи. Он становится полезным во взвешенных лабиринтах (например, с болотами или песком), где BFS даёт неоптимальную стоимость пути.
Применимость паттернов проектирования
?Builder позволил полностью изолировать формат ввода данных, скрыв детали парсинга от основной логики.
?Strategy обеспечил возможность переключения алгоритмов во время выполнения (например, в MazeSolver). Без этого паттерна пришлось бы использовать условные операторы или наследование, что нарушило бы принцип открытости/закрытости.
?Observer отделил визуализацию от бизнес-логики. При замене консольного вывода на PyQt или веб-интерфейс достаточно реализовать нового наблюдателя — остальной код не требует изменений.
?Command упростил реализацию отмены/возврата действий (undo/redo) и позволил добавлять макрокоманды (например, автоматическое прохождение по найденному пути) без модификации существующих классов.
Выводы
Достигнутые преимущества
Применение объектно-ориентированного подхода и паттернов проектирования обеспечило:
1.Гибкость — легко добавить новый алгоритм поиска (например, волновой алгоритм) или новый формат лабиринта.
2.Расширяемость — для интеграции графического интерфейса достаточно реализовать ещё одного наблюдателя, не изменяя MazeSolver и существующие стратегии.
3.Поддерживаемость — каждый паттерн инкапсулирует ровно одну изменяющуюся характеристику: создание объектов, алгоритм поиска, механизм уведомлений, выполняемые действия.
4.Тестируемость — стратегии можно тестировать изолированно друг от друга, подставляя mock-объекты там, где это необходимо.
Что потребовало бы больших усилий без паттернов
?Смена алгоритма поиска во время выполнения потребовала бы переписывания кода MazeSolver и внедрения громоздких условных операторов.
?Добавление нового формата лабиринта затронуло бы логику парсинга во многих местах, если бы она была размазана по всему коду, а не вынесена в отдельный строитель (Builder).
?Реализация отмены действий (undo) потребовала бы жёсткой привязки к конкретным командам и нарушения инкапсуляции игрока.
?Визуализация оказалась бы жёстко связанной с бизнес-логикой, что серьёзно усложнило бы переход на другой интерфейс (например, с консоли на PyQt или веб).
Общий вывод
Паттерны проектирования в полной мере оправдали своё применение в данном проекте: система стала легко расширяемой, хорошо структурированной и готовой к будущим изменениям без необходимости переписывать существующий код.

View File

@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (maze_project_submission) (2)" project-jdk-type="Python SDK" />
</project>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/maze_project_submission.iml" filepath="$PROJECT_DIR$/.idea/maze_project_submission.iml" />
</modules>
</component>
</project>

View File

@ -1,42 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="a6ff989d-c5f6-4522-8b0a-933849f2d044" name="Changes" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
<component name="ProjectColorInfo"><![CDATA[{
"associatedIndex": 2
}]]></component>
<component name="ProjectId" id="3EB20Mq0B865MSq8Kkl2evaRIZW" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"RunOnceActivity.OpenProjectViewOnStart": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"last_opened_file_path": "C:/Users/vaz21/Downloads/Task 2 GLOBAL/maze_project_submission"
}
}]]></component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="a6ff989d-c5f6-4522-8b0a-933849f2d044" name="Changes" comment="" />
<created>1779637417749</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1779637417749</updated>
</task>
<servers />
</component>
</project>

View File

View File

@ -1,24 +0,0 @@
# Maze Solver Project
ООП-проект для поиска выхода из лабиринта с паттернами:
- Builder
- Strategy
- Observer
- Command
## Запуск
```bash
python main.py
```
## Эксперименты
```bash
python experiment.py
```
Результаты сохраняются в папку `experiment_results/`.
## Требования
```bash
pip install -r requirements.txt
```

View File

@ -1,7 +0,0 @@
from abc import ABC, abstractmethod
class MazeBuilder(ABC):
@abstractmethod
def buildFromFile(self, filename):
raise NotImplementedError

View File

@ -1,52 +0,0 @@
from core.cell import Cell
from core.maze import Maze
from builders.maze_builder import MazeBuilder
class TextFileMazeBuilder(MazeBuilder):
def buildFromFile(self, filename):
with open(filename, "r", encoding="utf-8") as f:
lines = [line.rstrip("\n") for line in f]
if not lines:
raise ValueError("Maze file is empty")
width = max(len(line) for line in lines)
height = len(lines)
cells = []
startCell = None
exitCell = None
for y, line in enumerate(lines):
row = []
for x in range(width):
ch = line[x] if x < len(line) else "#"
if ch == "#":
cell = Cell(x, y, isWall=True)
elif ch == "S":
if startCell is not None:
raise ValueError("Multiple start cells found")
cell = Cell(x, y, isWall=False, isStart=True)
startCell = cell
elif ch == "E":
if exitCell is not None:
raise ValueError("Multiple exit cells found")
cell = Cell(x, y, isWall=False, isExit=True)
exitCell = cell
elif ch in (" ", "."):
cell = Cell(x, y, isWall=False)
elif ch.isdigit():
cell = Cell(x, y, isWall=False, weight=max(1, int(ch)))
else:
raise ValueError(f"Unsupported symbol '{ch}' at ({x}, {y})")
row.append(cell)
cells.append(row)
if startCell is None:
raise ValueError("Start cell 'S' not found")
if exitCell is None:
raise ValueError("Exit cell 'E' not found")
return Maze(cells, width, height, startCell, exitCell)

View File

@ -1,11 +0,0 @@
from abc import ABC, abstractmethod
class Command(ABC):
@abstractmethod
def execute(self):
raise NotImplementedError
@abstractmethod
def undo(self):
raise NotImplementedError

View File

@ -1,37 +0,0 @@
from commands.command import Command
class MoveCommand(Command):
DIRECTION_TO_DELTA = {
"W": (0, -1),
"A": (-1, 0),
"S": (0, 1),
"D": (1, 0),
}
def __init__(self, player, maze, direction):
self.player = player
self.maze = maze
self.direction = direction.upper()
self.previousCell = None
def execute(self):
if self.direction not in self.DIRECTION_TO_DELTA:
return False
dx, dy = self.DIRECTION_TO_DELTA[self.direction]
current = self.player.currentCell
new_cell = self.maze.getCell(current.x + dx, current.y + dy)
if new_cell is None or not new_cell.isPassable():
return False
self.previousCell = current
self.player.setCell(new_cell)
return True
def undo(self):
if self.previousCell is None:
return False
self.player.setCell(self.previousCell)
return True

View File

@ -1,30 +0,0 @@
from commands.move_command import MoveCommand
class GameController:
def __init__(self, maze, player, view):
self.maze = maze
self.player = player
self.view = view
self.history = []
def move(self, direction):
command = MoveCommand(self.player, self.maze, direction)
if command.execute():
self.history.append(command)
self.view.update({"type": "move", "direction": direction})
self.view.render(self.maze, player_position=self.player.currentCell)
return True
print("Cannot move there")
return False
def undo(self):
if not self.history:
print("Nothing to undo")
return False
command = self.history.pop()
if command.undo():
self.view.update({"type": "undo"})
self.view.render(self.maze, player_position=self.player.currentCell)
return True
return False

View File

@ -1,26 +0,0 @@
from dataclasses import dataclass
@dataclass
class Cell:
x: int
y: int
isWall: bool = False
isStart: bool = False
isExit: bool = False
weight: int = 1
def isPassable(self):
return not self.isWall
def __repr__(self):
parts = [f"Cell({self.x}, {self.y}"]
if self.isWall:
parts.append("WALL")
if self.isStart:
parts.append("START")
if self.isExit:
parts.append("EXIT")
if self.weight != 1:
parts.append(f"w={self.weight}")
return ", ".join(parts) + ")"

View File

@ -1,49 +0,0 @@
class Maze:
def __init__(self, cells, width, height, startCell=None, exitCell=None):
self.cells = cells
self.width = width
self.height = height
self.startCell = startCell
self.exitCell = exitCell
def getCell(self, x, y):
if 0 <= x < self.width and 0 <= y < self.height:
return self.cells[y][x]
return None
def getNeighbors(self, cell):
neighbors = []
for dx, dy in ((0, -1), (0, 1), (-1, 0), (1, 0)):
nx, ny = cell.x + dx, cell.y + dy
neighbor = self.getCell(nx, ny)
if neighbor is not None and neighbor.isPassable():
neighbors.append(neighbor)
return neighbors
def render_lines(self, player_position=None, path=None):
path_set = {(c.x, c.y) for c in path} if path else set()
player_pos = None if player_position is None else (player_position.x, player_position.y)
lines = []
for y in range(self.height):
row = []
for x in range(self.width):
cell = self.cells[y][x]
if player_pos == (x, y):
row.append("P")
elif cell.isStart:
row.append("S")
elif cell.isExit:
row.append("E")
elif cell.isWall:
row.append("#")
elif (x, y) in path_set:
row.append("*")
elif cell.weight > 1:
row.append(str(cell.weight))
else:
row.append(" ")
lines.append("".join(row))
return lines
def render(self, player_position=None, path=None):
return "\n".join(self.render_lines(player_position=player_position, path=path))

View File

@ -1,6 +0,0 @@
class Player:
def __init__(self, currentCell):
self.currentCell = currentCell
def setCell(self, cell):
self.currentCell = cell

View File

@ -1,11 +0,0 @@
from dataclasses import dataclass, field
@dataclass
class SearchStats:
timeMs: float
visitedCells: int
pathLength: int
path: list = field(default_factory=list)
found: bool = False
algorithm: str = ""

View File

@ -1 +0,0 @@
Place report files and experiment outputs here.

View File

@ -1,249 +0,0 @@
# Отчёт по работе «Поиск выхода из лабиринта»
## 1. Цель работы
Разработать гибкую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. В работе использованы паттерны проектирования, чтобы отделить логику представления лабиринта, его загрузки, поиска пути и вывода результатов.
## 2. Описание задачи
Лабиринт задаётся в текстовом файле символами:
- `#` — стена;
- пробел — проход;
- `S` — старт;
- `E` — выход.
Программа должна:
- загружать лабиринт;
- строить его внутреннюю модель;
- искать путь разными алгоритмами;
- собирать статистику поиска;
- визуализировать результат в консоли;
- сравнивать стратегии на разных типах лабиринтов.
## 3. Выбранные паттерны проектирования
### 3.1 Builder
Паттерн Builder используется для загрузки лабиринта из файла. Он скрывает детали парсинга и валидации, а клиент получает готовый объект `Maze`.
Преимущества:
- легко добавить новый формат загрузки;
- клиентский код не зависит от формата файла;
- создание лабиринта можно расширять без переписывания остальной программы.
### 3.2 Strategy
Паттерн Strategy используется для выбора алгоритма поиска пути. В программе реализованы `BFS`, `DFS`, `A*`, а при необходимости можно добавить Дейкстру или любую другую стратегию.
Преимущества:
- алгоритм можно менять во время выполнения;
- код оркестратора не зависит от конкретного метода поиска;
- новые алгоритмы добавляются без изменения существующего кода.
### 3.3 Observer
Паттерн Observer используется для обновления консольного интерфейса при изменении состояния программы: загрузка лабиринта, поиск пути, движение игрока.
Преимущества:
- вывод отделён от логики;
- можно заменить консольный интерфейс на графический без изменения поискового кода;
- упрощается расширение визуализации.
### 3.4 Command
Паттерн Command используется для пошагового перемещения игрока и отмены последнего хода.
Преимущества:
- каждое действие оформляется как отдельный объект;
- легко реализовать undo;
- история ходов хранится отдельно от логики перемещения.
## 4. Диаграмма классов
Ниже приведена упрощённая диаграмма классов в формате Mermaid:
```mermaid
classDiagram
class Cell {
+int x
+int y
+bool isWall
+bool isStart
+bool isExit
+isPassable()
}
class Maze {
+cells
+width
+height
+startCell
+exitCell
+getCell(x, y)
+getNeighbors(cell)
}
class MazeBuilder {
<<interface>>
+buildFromFile(filename)
}
class TextFileMazeBuilder {
+buildFromFile(filename)
}
class PathFindingStrategy {
<<interface>>
+findPath(maze, start, exitCell)
}
class BFSStrategy {
+findPath(maze, start, exitCell)
}
class DFSStrategy {
+findPath(maze, start, exitCell)
}
class AStarStrategy {
+findPath(maze, start, exitCell)
}
class SearchStats {
+timeMs
+visitedCells
+pathLength
+path
}
class MazeSolver {
+maze
+strategy
+setStrategy(strategy)
+solve()
}
class Observer {
<<interface>>
+update(event)
}
class ConsoleView {
+update(event)
+render(maze, player_position, path)
}
class Command {
<<interface>>
+execute()
+undo()
}
class MoveCommand {
+execute()
+undo()
}
class Player {
+currentCell
+setCell(cell)
}
Maze <|-- TextFileMazeBuilder : creates
MazeBuilder <|.. TextFileMazeBuilder
PathFindingStrategy <|.. BFSStrategy
PathFindingStrategy <|.. DFSStrategy
PathFindingStrategy <|.. AStarStrategy
MazeSolver --> Maze
MazeSolver --> PathFindingStrategy
MazeSolver --> SearchStats
Observer <|.. ConsoleView
Command <|.. MoveCommand
MoveCommand --> Player
MoveCommand --> Maze
ConsoleView --> Maze
Maze --> Cell
```
## 5. Ключевые классы и их роль
### Cell
Хранит координаты клетки и её тип. Позволяет быстро проверять, является ли клетка проходимой.
### Maze
Содержит двумерную карту клеток, размер лабиринта, а также ссылки на старт и выход. Даёт доступ к соседним клеткам по четырём направлениям.
### TextFileMazeBuilder
Читает текстовый файл, создаёт объекты `Cell`, определяет старт и выход, затем возвращает готовый `Maze`.
### BFSStrategy
Ищет кратчайший путь по числу шагов. Подходит для случая, когда все переходы одинаковой стоимости.
### DFSStrategy
Быстро исследует пространство, но не гарантирует кратчайший путь. Полезен как сравнительный алгоритм.
### AStarStrategy
Использует эвристику Манхэттенского расстояния. Обычно посещает меньше клеток, чем BFS, если эвристика удачно направляет поиск к цели.
### MazeSolver
Оркестратор, который хранит лабиринт и текущую стратегию. Вызывает поиск, измеряет время и собирает статистику.
### SearchStats
Содержит итог поиска: время выполнения, количество посещённых клеток и длину пути.
### ConsoleView
Реализует наблюдателя и умеет выводить лабиринт и найденный путь в консоль.
### MoveCommand
Оформляет ход игрока как объект-команду. Поддерживает отмену последнего перемещения.
## 6. Экспериментальная часть
### 6.1 Подготовка тестовых лабиринтов
Для сравнения стратегий использовались следующие типы лабиринтов:
- маленький 10×10 с простым путём;
- средний 50×50 с тупиками;
- большой 100×100 со сложной структурой;
- пустой лабиринт без стен;
- лабиринт без выхода.
### 6.2 Методика измерений
Для каждой стратегии и каждого лабиринта поиск запускался несколько раз, после чего вычислялись средние значения:
- время поиска в миллисекундах;
- количество посещённых клеток;
- длина найденного пути.
Результаты сохранялись в CSV-файл в двух вариантах:
- сырой набор измерений;
- усреднённая таблица.
## 7. Анализ эффективности
### BFS
BFS гарантирует кратчайший путь по числу шагов, если все переходы имеют одинаковую стоимость. На простых и пустых лабиринтах работает стабильно и предсказуемо. Минус — может посещать много клеток, особенно на больших лабиринтах.
### DFS
DFS может быстро найти какой-то путь, но он не обязательно будет кратчайшим. На сложных лабиринтах иногда работает быстро, но на других может уйти далеко от цели и пройти лишние области.
### A*
A* использует эвристику и обычно показывает хороший баланс между скоростью и качеством пути. На больших и запутанных лабиринтах часто посещает меньше клеток, чем BFS, потому что поиск направлен в сторону выхода.
### Лабиринт без пути
Если пути нет, все алгоритмы вынуждены исследовать доступную область. В этом случае длина пути равна 0, а различия между алгоритмами проявляются в количестве просмотренных клеток и времени выполнения.
### Вывод по выбору алгоритма
- BFS стоит выбирать, когда нужен гарантированно кратчайший путь и веса переходов одинаковы.
- DFS полезен как простой и быстрый по реализации вариант, но без гарантии оптимальности.
- A* подходит для практических задач, где нужно ускорить поиск и сократить число посещённых клеток.
- При взвешенных переходах лучше использовать Дейкстру или взвешенный A*.
## 8. Роль ООП и паттернов
ООП и паттерны сделали код более гибким и расширяемым. Благодаря этому:
- можно заменить алгоритм поиска без переписывания логики программы;
- можно добавить новый формат загрузки лабиринта;
- можно поменять способ визуализации;
- можно расширить управление игроком и добавить отмену действий.
Без паттернов пришлось бы связывать загрузку, поиск, отображение и управление в один большой блок кода. Это усложнило бы отладку и дальнейшие изменения.
## 9. Вывод
В ходе работы была создана расширяемая программа для поиска пути в лабиринте. Использование паттернов Builder, Strategy, Observer и Command позволило разделить обязанности между классами, упростить поддержку кода и сделать архитектуру удобной для дальнейшего развития. Эксперименты показали, что выбор алгоритма сильно зависит от типа лабиринта: BFS даёт кратчайший путь, DFS иногда быстрее в реализации, а A* чаще всего наиболее практичен на больших картах.
## 10. Приложения
- Листинги ключевых классов.
- CSV-файлы с результатами экспериментов.
- Графики сравнений.
- Файлы с тестовыми лабиринтами.

View File

@ -1,225 +0,0 @@
from pathlib import Path
from statistics import mean
import csv
import random
import matplotlib.pyplot as plt
from core.cell import Cell
from core.maze import Maze
from solver.maze_solver import MazeSolver
from strategies.astar_strategy import AStarStrategy
from strategies.bfs_strategy import BFSStrategy
from strategies.dfs_strategy import DFSStrategy
from strategies.dijkstra_strategy import DijkstraStrategy
BASE_DIR = Path(__file__).resolve().parent
OUT_DIR = BASE_DIR / "experiment_results"
def build_maze_from_symbols(lines):
height = len(lines)
width = max(len(line) for line in lines)
cells = []
start = None
exit_cell = None
for y, line in enumerate(lines):
row = []
for x in range(width):
ch = line[x] if x < len(line) else "#"
if ch == "#":
cell = Cell(x, y, isWall=True)
elif ch == "S":
cell = Cell(x, y, isWall=False, isStart=True)
start = cell
elif ch == "E":
cell = Cell(x, y, isWall=False, isExit=True)
exit_cell = cell
elif ch == " " or ch == ".":
cell = Cell(x, y, isWall=False)
elif ch.isdigit():
cell = Cell(x, y, isWall=False, weight=int(ch))
else:
raise ValueError(f"Unknown symbol '{ch}' at {x},{y}")
row.append(cell)
cells.append(row)
return Maze(cells, width, height, start, exit_cell)
def generate_empty_maze(width, height):
lines = [" " * width for _ in range(height)]
lines = [list(row) for row in lines]
lines[1][1] = "S"
lines[height - 2][width - 2] = "E"
return build_maze_from_symbols(["".join(row) for row in lines])
def generate_simple_maze(width, height):
grid = [["#" for _ in range(width)] for _ in range(height)]
for x in range(1, width - 1):
grid[1][x] = " "
for y in range(1, height - 1):
grid[y][width - 2] = " "
grid[1][1] = "S"
grid[height - 2][width - 2] = "E"
return build_maze_from_symbols(["".join(row) for row in grid])
def generate_branching_maze(width, height, seed=42, wall_density=0.30):
rng = random.Random(seed)
grid = [["#" for _ in range(width)] for _ in range(height)]
x, y = 1, 1
grid[y][x] = "S"
while (x, y) != (width - 2, height - 2):
candidates = []
for dx, dy in [(1, 0), (0, 1)]:
nx, ny = x + dx, y + dy
if 1 <= nx < width - 1 and 1 <= ny < height - 1:
candidates.append((nx, ny))
if not candidates:
break
x, y = rng.choice(candidates)
grid[y][x] = " "
grid[height - 2][width - 2] = "E"
# carve extra corridors and dead ends
for yy in range(1, height - 1):
for xx in range(1, width - 1):
if grid[yy][xx] == "#" and rng.random() > wall_density:
grid[yy][xx] = " "
grid[1][1] = "S"
grid[height - 2][width - 2] = "E"
return build_maze_from_symbols(["".join(row) for row in grid])
def generate_no_path_maze(width, height):
grid = [[" " for _ in range(width)] for _ in range(height)]
for x in range(width):
grid[height // 2][x] = "#"
grid[1][1] = "S"
grid[height - 2][width - 2] = "E"
return build_maze_from_symbols(["".join(row) for row in grid])
def generate_weighted_maze(width, height, seed=123):
rng = random.Random(seed)
grid = [[" " for _ in range(width)] for _ in range(height)]
for y in range(height):
for x in range(width):
r = rng.random()
if r < 0.12:
grid[y][x] = "#"
elif r < 0.25:
grid[y][x] = "3"
elif r < 0.40:
grid[y][x] = "2"
else:
grid[y][x] = "1"
# ensure path-ish
for x in range(width):
grid[1][x] = "1"
for y in range(1, height):
grid[y][width - 2] = "1"
grid[1][1] = "S"
grid[height - 2][width - 2] = "E"
return build_maze_from_symbols(["".join(row) for row in grid])
def bench_one_maze(maze_name, maze, strategies, repeats=5):
summary_rows = []
raw_rows = []
for strategy_name, strategy_factory in strategies:
times, visiteds, lengths = [], [], []
for run in range(1, repeats + 1):
solver = MazeSolver(maze)
solver.setStrategy(strategy_factory())
stats = solver.solve()
raw_rows.append([maze_name, strategy_name, run, f"{stats.timeMs:.6f}", stats.visitedCells, stats.pathLength])
times.append(stats.timeMs)
visiteds.append(stats.visitedCells)
lengths.append(stats.pathLength)
summary_rows.append([maze_name, strategy_name, f"{mean(times):.6f}", f"{mean(visiteds):.2f}", f"{mean(lengths):.2f}", repeats])
return summary_rows, raw_rows
def save_csv(path, rows):
with open(path, "w", newline="", encoding="utf-8") as f:
csv.writer(f).writerows(rows)
def plot_summary(summary_rows):
by_maze = {}
for row in summary_rows[1:]:
maze_name, strategy, avg_time, avg_visited, avg_len, runs = row
by_maze.setdefault(maze_name, []).append((strategy, float(avg_time), float(avg_visited), float(avg_len)))
for maze_name, items in by_maze.items():
items.sort(key=lambda t: t[0])
strategies = [i[0] for i in items]
x = list(range(len(strategies)))
plt.figure(figsize=(8, 4))
plt.bar(x, [i[1] for i in items])
plt.xticks(x, strategies)
plt.ylabel("ms")
plt.title(f"{maze_name} — avg time")
plt.tight_layout()
plt.savefig(OUT_DIR / f"{maze_name}_time.png", dpi=150)
plt.close()
plt.figure(figsize=(8, 4))
plt.bar(x, [i[2] for i in items])
plt.xticks(x, strategies)
plt.ylabel("cells")
plt.title(f"{maze_name} — visited cells")
plt.tight_layout()
plt.savefig(OUT_DIR / f"{maze_name}_visited.png", dpi=150)
plt.close()
plt.figure(figsize=(8, 4))
plt.bar(x, [i[3] for i in items])
plt.xticks(x, strategies)
plt.ylabel("cells")
plt.title(f"{maze_name} — path length")
plt.tight_layout()
plt.savefig(OUT_DIR / f"{maze_name}_length.png", dpi=150)
plt.close()
def main():
OUT_DIR.mkdir(exist_ok=True)
strategies = [
("BFS", BFSStrategy),
("DFS", DFSStrategy),
("A*", AStarStrategy),
("Dijkstra", DijkstraStrategy),
]
mazes = [
("small_10x10", generate_simple_maze(10, 10)),
("medium_50x50", generate_branching_maze(50, 50)),
("large_100x100", generate_branching_maze(100, 100, seed=99, wall_density=0.35)),
("empty_30x30", generate_empty_maze(30, 30)),
("no_path_30x30", generate_no_path_maze(30, 30)),
("weighted_30x30", generate_weighted_maze(30, 30)),
]
summary = [["maze", "strategy", "avg_time_ms", "avg_visited_cells", "avg_path_length", "runs"]]
raw = [["maze", "strategy", "run", "time_ms", "visited_cells", "path_length"]]
for maze_name, maze in mazes:
s_rows, r_rows = bench_one_maze(maze_name, maze, strategies, repeats=5)
summary.extend(s_rows)
raw.extend(r_rows)
save_csv(OUT_DIR / "summary.csv", summary)
save_csv(OUT_DIR / "raw.csv", raw)
plot_summary(summary)
print("Saved to", OUT_DIR.resolve())
if __name__ == "__main__":
main()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@ -1,121 +0,0 @@
maze,strategy,run,time_ms,visited_cells,path_length
small_10x10,BFS,1,0.044300,15,15
small_10x10,BFS,2,0.022800,15,15
small_10x10,BFS,3,0.020400,15,15
small_10x10,BFS,4,0.020300,15,15
small_10x10,BFS,5,0.018700,15,15
small_10x10,DFS,1,0.031200,15,15
small_10x10,DFS,2,0.022000,15,15
small_10x10,DFS,3,0.021200,15,15
small_10x10,DFS,4,0.020800,15,15
small_10x10,DFS,5,0.020500,15,15
small_10x10,A*,1,0.048900,15,15
small_10x10,A*,2,0.034700,15,15
small_10x10,A*,3,0.029400,15,15
small_10x10,A*,4,0.029100,15,15
small_10x10,A*,5,0.029300,15,15
small_10x10,Dijkstra,1,0.037900,15,15
small_10x10,Dijkstra,2,0.028500,15,15
small_10x10,Dijkstra,3,0.026800,15,15
small_10x10,Dijkstra,4,0.026400,15,15
small_10x10,Dijkstra,5,0.026700,15,15
medium_50x50,BFS,1,2.105800,1579,95
medium_50x50,BFS,2,1.928700,1579,95
medium_50x50,BFS,3,1.969500,1579,95
medium_50x50,BFS,4,1.938800,1579,95
medium_50x50,BFS,5,1.943600,1579,95
medium_50x50,DFS,1,1.927300,1277,647
medium_50x50,DFS,2,1.856300,1277,647
medium_50x50,DFS,3,1.890100,1277,647
medium_50x50,DFS,4,1.868000,1277,647
medium_50x50,DFS,5,1.865500,1277,647
medium_50x50,A*,1,2.359000,927,95
medium_50x50,A*,2,2.193700,927,95
medium_50x50,A*,3,2.178400,927,95
medium_50x50,A*,4,2.181800,927,95
medium_50x50,A*,5,2.174500,927,95
medium_50x50,Dijkstra,1,3.534700,1579,95
medium_50x50,Dijkstra,2,3.435500,1579,95
medium_50x50,Dijkstra,3,3.457600,1579,95
medium_50x50,Dijkstra,4,3.417300,1579,95
medium_50x50,Dijkstra,5,3.538000,1579,95
large_100x100,BFS,1,8.624100,5566,195
large_100x100,BFS,2,7.706900,5566,195
large_100x100,BFS,3,9.723300,5566,195
large_100x100,BFS,4,7.585700,5566,195
large_100x100,BFS,5,8.031300,5566,195
large_100x100,DFS,1,5.512400,3543,1531
large_100x100,DFS,2,5.329300,3543,1531
large_100x100,DFS,3,5.223300,3543,1531
large_100x100,DFS,4,5.729900,3543,1531
large_100x100,DFS,5,5.497400,3543,1531
large_100x100,A*,1,2.101500,853,195
large_100x100,A*,2,2.264500,853,195
large_100x100,A*,3,2.064100,853,195
large_100x100,A*,4,2.031700,853,195
large_100x100,A*,5,2.046500,853,195
large_100x100,Dijkstra,1,25.021300,5571,195
large_100x100,Dijkstra,2,13.541100,5571,195
large_100x100,Dijkstra,3,12.884100,5571,195
large_100x100,Dijkstra,4,13.481800,5571,195
large_100x100,Dijkstra,5,12.748000,5571,195
empty_30x30,BFS,1,1.234300,896,55
empty_30x30,BFS,2,1.163400,896,55
empty_30x30,BFS,3,1.145700,896,55
empty_30x30,BFS,4,1.177300,896,55
empty_30x30,BFS,5,1.175100,896,55
empty_30x30,DFS,1,1.338000,842,815
empty_30x30,DFS,2,1.296500,842,815
empty_30x30,DFS,3,1.296700,842,815
empty_30x30,DFS,4,1.280100,842,815
empty_30x30,DFS,5,1.290800,842,815
empty_30x30,A*,1,2.183400,784,55
empty_30x30,A*,2,2.522900,784,55
empty_30x30,A*,3,1.985000,784,55
empty_30x30,A*,4,1.972100,784,55
empty_30x30,A*,5,2.088600,784,55
empty_30x30,Dijkstra,1,2.080400,896,55
empty_30x30,Dijkstra,2,2.100100,896,55
empty_30x30,Dijkstra,3,2.130700,896,55
empty_30x30,Dijkstra,4,2.073600,896,55
empty_30x30,Dijkstra,5,2.095900,896,55
no_path_30x30,BFS,1,0.645900,450,0
no_path_30x30,BFS,2,0.566600,450,0
no_path_30x30,BFS,3,0.566000,450,0
no_path_30x30,BFS,4,0.583500,450,0
no_path_30x30,BFS,5,0.568900,450,0
no_path_30x30,DFS,1,0.692100,450,0
no_path_30x30,DFS,2,0.676900,450,0
no_path_30x30,DFS,3,0.703500,450,0
no_path_30x30,DFS,4,0.722300,450,0
no_path_30x30,DFS,5,0.672000,450,0
no_path_30x30,A*,1,1.112700,450,0
no_path_30x30,A*,2,1.130000,450,0
no_path_30x30,A*,3,1.096100,450,0
no_path_30x30,A*,4,1.111400,450,0
no_path_30x30,A*,5,1.183500,450,0
no_path_30x30,Dijkstra,1,1.023300,450,0
no_path_30x30,Dijkstra,2,1.011700,450,0
no_path_30x30,Dijkstra,3,1.127200,450,0
no_path_30x30,Dijkstra,4,1.110200,450,0
no_path_30x30,Dijkstra,5,1.043900,450,0
weighted_30x30,BFS,1,1.074700,788,55
weighted_30x30,BFS,2,0.997700,788,55
weighted_30x30,BFS,3,0.992700,788,55
weighted_30x30,BFS,4,1.010800,788,55
weighted_30x30,BFS,5,1.035000,788,55
weighted_30x30,DFS,1,1.130200,693,479
weighted_30x30,DFS,2,1.057400,693,479
weighted_30x30,DFS,3,1.049900,693,479
weighted_30x30,DFS,4,1.051600,693,479
weighted_30x30,DFS,5,1.059100,693,479
weighted_30x30,A*,1,0.402200,126,55
weighted_30x30,A*,2,0.384100,126,55
weighted_30x30,A*,3,0.360000,126,55
weighted_30x30,A*,4,0.360700,126,55
weighted_30x30,A*,5,0.353500,126,55
weighted_30x30,Dijkstra,1,1.834900,781,55
weighted_30x30,Dijkstra,2,1.759000,781,55
weighted_30x30,Dijkstra,3,1.786300,781,55
weighted_30x30,Dijkstra,4,1.740500,781,55
weighted_30x30,Dijkstra,5,1.807100,781,55
1 maze strategy run time_ms visited_cells path_length
2 small_10x10 BFS 1 0.044300 15 15
3 small_10x10 BFS 2 0.022800 15 15
4 small_10x10 BFS 3 0.020400 15 15
5 small_10x10 BFS 4 0.020300 15 15
6 small_10x10 BFS 5 0.018700 15 15
7 small_10x10 DFS 1 0.031200 15 15
8 small_10x10 DFS 2 0.022000 15 15
9 small_10x10 DFS 3 0.021200 15 15
10 small_10x10 DFS 4 0.020800 15 15
11 small_10x10 DFS 5 0.020500 15 15
12 small_10x10 A* 1 0.048900 15 15
13 small_10x10 A* 2 0.034700 15 15
14 small_10x10 A* 3 0.029400 15 15
15 small_10x10 A* 4 0.029100 15 15
16 small_10x10 A* 5 0.029300 15 15
17 small_10x10 Dijkstra 1 0.037900 15 15
18 small_10x10 Dijkstra 2 0.028500 15 15
19 small_10x10 Dijkstra 3 0.026800 15 15
20 small_10x10 Dijkstra 4 0.026400 15 15
21 small_10x10 Dijkstra 5 0.026700 15 15
22 medium_50x50 BFS 1 2.105800 1579 95
23 medium_50x50 BFS 2 1.928700 1579 95
24 medium_50x50 BFS 3 1.969500 1579 95
25 medium_50x50 BFS 4 1.938800 1579 95
26 medium_50x50 BFS 5 1.943600 1579 95
27 medium_50x50 DFS 1 1.927300 1277 647
28 medium_50x50 DFS 2 1.856300 1277 647
29 medium_50x50 DFS 3 1.890100 1277 647
30 medium_50x50 DFS 4 1.868000 1277 647
31 medium_50x50 DFS 5 1.865500 1277 647
32 medium_50x50 A* 1 2.359000 927 95
33 medium_50x50 A* 2 2.193700 927 95
34 medium_50x50 A* 3 2.178400 927 95
35 medium_50x50 A* 4 2.181800 927 95
36 medium_50x50 A* 5 2.174500 927 95
37 medium_50x50 Dijkstra 1 3.534700 1579 95
38 medium_50x50 Dijkstra 2 3.435500 1579 95
39 medium_50x50 Dijkstra 3 3.457600 1579 95
40 medium_50x50 Dijkstra 4 3.417300 1579 95
41 medium_50x50 Dijkstra 5 3.538000 1579 95
42 large_100x100 BFS 1 8.624100 5566 195
43 large_100x100 BFS 2 7.706900 5566 195
44 large_100x100 BFS 3 9.723300 5566 195
45 large_100x100 BFS 4 7.585700 5566 195
46 large_100x100 BFS 5 8.031300 5566 195
47 large_100x100 DFS 1 5.512400 3543 1531
48 large_100x100 DFS 2 5.329300 3543 1531
49 large_100x100 DFS 3 5.223300 3543 1531
50 large_100x100 DFS 4 5.729900 3543 1531
51 large_100x100 DFS 5 5.497400 3543 1531
52 large_100x100 A* 1 2.101500 853 195
53 large_100x100 A* 2 2.264500 853 195
54 large_100x100 A* 3 2.064100 853 195
55 large_100x100 A* 4 2.031700 853 195
56 large_100x100 A* 5 2.046500 853 195
57 large_100x100 Dijkstra 1 25.021300 5571 195
58 large_100x100 Dijkstra 2 13.541100 5571 195
59 large_100x100 Dijkstra 3 12.884100 5571 195
60 large_100x100 Dijkstra 4 13.481800 5571 195
61 large_100x100 Dijkstra 5 12.748000 5571 195
62 empty_30x30 BFS 1 1.234300 896 55
63 empty_30x30 BFS 2 1.163400 896 55
64 empty_30x30 BFS 3 1.145700 896 55
65 empty_30x30 BFS 4 1.177300 896 55
66 empty_30x30 BFS 5 1.175100 896 55
67 empty_30x30 DFS 1 1.338000 842 815
68 empty_30x30 DFS 2 1.296500 842 815
69 empty_30x30 DFS 3 1.296700 842 815
70 empty_30x30 DFS 4 1.280100 842 815
71 empty_30x30 DFS 5 1.290800 842 815
72 empty_30x30 A* 1 2.183400 784 55
73 empty_30x30 A* 2 2.522900 784 55
74 empty_30x30 A* 3 1.985000 784 55
75 empty_30x30 A* 4 1.972100 784 55
76 empty_30x30 A* 5 2.088600 784 55
77 empty_30x30 Dijkstra 1 2.080400 896 55
78 empty_30x30 Dijkstra 2 2.100100 896 55
79 empty_30x30 Dijkstra 3 2.130700 896 55
80 empty_30x30 Dijkstra 4 2.073600 896 55
81 empty_30x30 Dijkstra 5 2.095900 896 55
82 no_path_30x30 BFS 1 0.645900 450 0
83 no_path_30x30 BFS 2 0.566600 450 0
84 no_path_30x30 BFS 3 0.566000 450 0
85 no_path_30x30 BFS 4 0.583500 450 0
86 no_path_30x30 BFS 5 0.568900 450 0
87 no_path_30x30 DFS 1 0.692100 450 0
88 no_path_30x30 DFS 2 0.676900 450 0
89 no_path_30x30 DFS 3 0.703500 450 0
90 no_path_30x30 DFS 4 0.722300 450 0
91 no_path_30x30 DFS 5 0.672000 450 0
92 no_path_30x30 A* 1 1.112700 450 0
93 no_path_30x30 A* 2 1.130000 450 0
94 no_path_30x30 A* 3 1.096100 450 0
95 no_path_30x30 A* 4 1.111400 450 0
96 no_path_30x30 A* 5 1.183500 450 0
97 no_path_30x30 Dijkstra 1 1.023300 450 0
98 no_path_30x30 Dijkstra 2 1.011700 450 0
99 no_path_30x30 Dijkstra 3 1.127200 450 0
100 no_path_30x30 Dijkstra 4 1.110200 450 0
101 no_path_30x30 Dijkstra 5 1.043900 450 0
102 weighted_30x30 BFS 1 1.074700 788 55
103 weighted_30x30 BFS 2 0.997700 788 55
104 weighted_30x30 BFS 3 0.992700 788 55
105 weighted_30x30 BFS 4 1.010800 788 55
106 weighted_30x30 BFS 5 1.035000 788 55
107 weighted_30x30 DFS 1 1.130200 693 479
108 weighted_30x30 DFS 2 1.057400 693 479
109 weighted_30x30 DFS 3 1.049900 693 479
110 weighted_30x30 DFS 4 1.051600 693 479
111 weighted_30x30 DFS 5 1.059100 693 479
112 weighted_30x30 A* 1 0.402200 126 55
113 weighted_30x30 A* 2 0.384100 126 55
114 weighted_30x30 A* 3 0.360000 126 55
115 weighted_30x30 A* 4 0.360700 126 55
116 weighted_30x30 A* 5 0.353500 126 55
117 weighted_30x30 Dijkstra 1 1.834900 781 55
118 weighted_30x30 Dijkstra 2 1.759000 781 55
119 weighted_30x30 Dijkstra 3 1.786300 781 55
120 weighted_30x30 Dijkstra 4 1.740500 781 55
121 weighted_30x30 Dijkstra 5 1.807100 781 55

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,25 +0,0 @@
maze,strategy,avg_time_ms,avg_visited_cells,avg_path_length,runs
small_10x10,BFS,0.025300,15.00,15.00,5
small_10x10,DFS,0.023140,15.00,15.00,5
small_10x10,A*,0.034280,15.00,15.00,5
small_10x10,Dijkstra,0.029260,15.00,15.00,5
medium_50x50,BFS,1.977280,1579.00,95.00,5
medium_50x50,DFS,1.881440,1277.00,647.00,5
medium_50x50,A*,2.217480,927.00,95.00,5
medium_50x50,Dijkstra,3.476620,1579.00,95.00,5
large_100x100,BFS,8.334260,5566.00,195.00,5
large_100x100,DFS,5.458460,3543.00,1531.00,5
large_100x100,A*,2.101660,853.00,195.00,5
large_100x100,Dijkstra,15.535260,5571.00,195.00,5
empty_30x30,BFS,1.179160,896.00,55.00,5
empty_30x30,DFS,1.300420,842.00,815.00,5
empty_30x30,A*,2.150400,784.00,55.00,5
empty_30x30,Dijkstra,2.096140,896.00,55.00,5
no_path_30x30,BFS,0.586180,450.00,0.00,5
no_path_30x30,DFS,0.693360,450.00,0.00,5
no_path_30x30,A*,1.126740,450.00,0.00,5
no_path_30x30,Dijkstra,1.063260,450.00,0.00,5
weighted_30x30,BFS,1.022180,788.00,55.00,5
weighted_30x30,DFS,1.069640,693.00,479.00,5
weighted_30x30,A*,0.372100,126.00,55.00,5
weighted_30x30,Dijkstra,1.785560,781.00,55.00,5
1 maze strategy avg_time_ms avg_visited_cells avg_path_length runs
2 small_10x10 BFS 0.025300 15.00 15.00 5
3 small_10x10 DFS 0.023140 15.00 15.00 5
4 small_10x10 A* 0.034280 15.00 15.00 5
5 small_10x10 Dijkstra 0.029260 15.00 15.00 5
6 medium_50x50 BFS 1.977280 1579.00 95.00 5
7 medium_50x50 DFS 1.881440 1277.00 647.00 5
8 medium_50x50 A* 2.217480 927.00 95.00 5
9 medium_50x50 Dijkstra 3.476620 1579.00 95.00 5
10 large_100x100 BFS 8.334260 5566.00 195.00 5
11 large_100x100 DFS 5.458460 3543.00 1531.00 5
12 large_100x100 A* 2.101660 853.00 195.00 5
13 large_100x100 Dijkstra 15.535260 5571.00 195.00 5
14 empty_30x30 BFS 1.179160 896.00 55.00 5
15 empty_30x30 DFS 1.300420 842.00 815.00 5
16 empty_30x30 A* 2.150400 784.00 55.00 5
17 empty_30x30 Dijkstra 2.096140 896.00 55.00 5
18 no_path_30x30 BFS 0.586180 450.00 0.00 5
19 no_path_30x30 DFS 0.693360 450.00 0.00 5
20 no_path_30x30 A* 1.126740 450.00 0.00 5
21 no_path_30x30 Dijkstra 1.063260 450.00 0.00 5
22 weighted_30x30 BFS 1.022180 788.00 55.00 5
23 weighted_30x30 DFS 1.069640 693.00 479.00 5
24 weighted_30x30 A* 0.372100 126.00 55.00 5
25 weighted_30x30 Dijkstra 1.785560 781.00 55.00 5

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@ -1,59 +0,0 @@
from builders.text_file_maze_builder import TextFileMazeBuilder
from core.player import Player
from observer.console_view import ConsoleView
from solver.maze_solver import MazeSolver
from strategies.astar_strategy import AStarStrategy
from strategies.bfs_strategy import BFSStrategy
from strategies.dfs_strategy import DFSStrategy
from controller.game_controller import GameController
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent
def run_demo():
builder = TextFileMazeBuilder()
maze = builder.buildFromFile(str(BASE_DIR / "mazes" / "maze_small.txt"))
view = ConsoleView()
view.update({"type": "maze_loaded", "message": "Maze loaded"})
view.render(maze)
solver = MazeSolver(maze)
solver.addObserver(view)
for strategy in (BFSStrategy(), DFSStrategy(), AStarStrategy()):
solver.setStrategy(strategy)
stats = solver.solve()
print()
print(f"=== {strategy.name} ===")
print(f"Time: {stats.timeMs:.3f} ms")
print(f"Visited cells: {stats.visitedCells}")
print(f"Path length: {stats.pathLength}")
print(f"Path found: {'yes' if stats.found else 'no'}")
view.render(maze, path=stats.path)
player = Player(maze.startCell)
controller = GameController(maze, player, view)
print("Manual mode: W/A/S/D move, Z undo, Q quit")
view.render(maze, player_position=player.currentCell)
while True:
cmd = input("Command: ").strip().upper()
if cmd == "Q":
break
if cmd == "Z":
controller.undo()
elif cmd in {"W", "A", "S", "D"}:
controller.move(cmd)
else:
print("Unknown command")
if __name__ == "__main__":
run_demo()

View File

@ -1,9 +0,0 @@
S
E

View File

@ -1,11 +0,0 @@
####################################################################################################
#S # # # # # # # # # # # # # # # E#
# # ### ### # ###### # ### # ## # #### # ####### # #### # # ### ## # ## # # ## # ## # ##### ### ##
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# ##### # ######## # ### # ## # #### # ####### ## ### # # #### ####### ## ####### ####### # ### ##
# # # # # # # # # # # # # # # # # # # # #
### # # ###### # ########### ########### ### ####### # ####### ### # # ###### # ### ### # ### ####
# # # # # # # # # # # # # # # # # # # # # #
# ### ###### # ##### # ### # ####### # ### ### ## # ###### # ### # ### ###### # ### # ### ### ## #
# # # # # # # # #
####################################################################################################

View File

@ -1,11 +0,0 @@
##################################################
#S # # # # # # E#
# # ### ### # ###### # ### # ## # #### # ####### ##
# # # # # # # # # # # # # #
# ##### # ######## # ### # ## # #### # ####### ## #
# # # # # # # # # #
### # # ###### # ########### ########### ### ######
# # # # # # # # # # #
# ### ###### # ##### # ### # ####### # ### ### ## #
# # # # #
##################################################

View File

@ -1,9 +0,0 @@
##########
#S #
# ###### #
# # #
##########
# #E#
# ###### #
# #
##########

View File

@ -1,7 +0,0 @@
##########
#S #E#
# ## # # ##
# # #
# #### # #
# # #
##########

View File

@ -1,10 +0,0 @@
1111111111111111111111111111
1S11111111111111111111111111
1111111111111111111111111111
1111111111111111111111111111
1111111111111222222222222111
1111111111111222222222222111
1111111111111333333333333111
1111111111111333333333333111
111111111111111111111111111E
1111111111111111111111111111

View File

@ -1,26 +0,0 @@
import os
from observer.observer import Observer
class ConsoleView(Observer):
def update(self, event):
if isinstance(event, str):
print(f"[EVENT] {event}")
elif isinstance(event, dict):
event_type = event.get("type", "unknown")
if event_type == "search_finished":
stats = event.get("stats")
print(f"[EVENT] search finished: {stats}")
else:
print(f"[EVENT] {event_type}: {event}")
else:
print("[EVENT] unknown")
def clear(self):
os.system("cls" if os.name == "nt" else "clear")
def render(self, maze, player_position=None, path=None, clear_screen=False):
if clear_screen:
self.clear()
print(maze.render(player_position=player_position, path=path))
print()

View File

@ -1,7 +0,0 @@
from abc import ABC, abstractmethod
class Observer(ABC):
@abstractmethod
def update(self, event):
raise NotImplementedError

View File

@ -1 +0,0 @@
matplotlib

View File

@ -1,50 +0,0 @@
import time
from core.search_stats import SearchStats
class MazeSolver:
def __init__(self, maze, strategy=None):
self.maze = maze
self.strategy = strategy
self.observers = []
def setStrategy(self, strategy):
self.strategy = strategy
def addObserver(self, observer):
if observer not in self.observers:
self.observers.append(observer)
def removeObserver(self, observer):
if observer in self.observers:
self.observers.remove(observer)
def notify(self, event):
for observer in self.observers:
observer.update(event)
def solve(self):
if self.strategy is None:
raise ValueError("Strategy is not set")
self.notify({"type": "search_started", "strategy": self.strategy.name})
start_time = time.perf_counter()
path = self.strategy.findPath(self.maze, self.maze.startCell, self.maze.exitCell)
end_time = time.perf_counter()
stats = SearchStats(
timeMs=(end_time - start_time) * 1000.0,
visitedCells=getattr(self.strategy, "visitedCount", 0),
pathLength=len(path),
path=path,
found=bool(path),
algorithm=getattr(self.strategy, "name", "")
)
if stats.found:
self.notify({"type": "path_found", "strategy": stats.algorithm, "length": stats.pathLength})
else:
self.notify({"type": "path_not_found", "strategy": stats.algorithm})
self.notify({"type": "search_finished", "stats": stats})
return stats

View File

@ -1,45 +0,0 @@
import heapq
from strategies.pathfinding_strategy import PathFindingStrategy
class AStarStrategy(PathFindingStrategy):
name = "A*"
def heuristic(self, cell, exitCell):
return abs(cell.x - exitCell.x) + abs(cell.y - exitCell.y)
def findPath(self, maze, start, exitCell):
self.visitedCount = 0
if start is None or exitCell is None:
return []
open_set = []
heapq.heappush(open_set, (0, 0, start.x, start.y, start))
parent = {}
g_score = {(start.x, start.y): 0}
closed = set()
while open_set:
f_score, current_g, _, _, current = heapq.heappop(open_set)
pos = (current.x, current.y)
if pos in closed:
continue
closed.add(pos)
self.visitedCount += 1
if current.x == exitCell.x and current.y == exitCell.y:
return self._restore_path(parent, start, exitCell)
for neighbor in maze.getNeighbors(current):
npos = (neighbor.x, neighbor.y)
tentative_g = current_g + getattr(neighbor, "weight", 1)
if tentative_g < g_score.get(npos, float("inf")):
g_score[npos] = tentative_g
parent[npos] = current
new_f = tentative_g + self.heuristic(neighbor, exitCell)
heapq.heappush(open_set, (new_f, tentative_g, neighbor.x, neighbor.y, neighbor))
return []

View File

@ -1,31 +0,0 @@
from collections import deque
from strategies.pathfinding_strategy import PathFindingStrategy
class BFSStrategy(PathFindingStrategy):
name = "BFS"
def findPath(self, maze, start, exitCell):
self.visitedCount = 0
if start is None or exitCell is None:
return []
queue = deque([start])
visited = {(start.x, start.y)}
parent = {}
while queue:
current = queue.popleft()
self.visitedCount += 1
if current.x == exitCell.x and current.y == exitCell.y:
return self._restore_path(parent, start, exitCell)
for neighbor in maze.getNeighbors(current):
pos = (neighbor.x, neighbor.y)
if pos not in visited:
visited.add(pos)
parent[pos] = current
queue.append(neighbor)
return []

View File

@ -1,35 +0,0 @@
from strategies.pathfinding_strategy import PathFindingStrategy
class DFSStrategy(PathFindingStrategy):
name = "DFS"
def findPath(self, maze, start, exitCell):
self.visitedCount = 0
if start is None or exitCell is None:
return []
stack = [start]
visited = set()
parent = {}
while stack:
current = stack.pop()
pos = (current.x, current.y)
if pos in visited:
continue
visited.add(pos)
self.visitedCount += 1
if current.x == exitCell.x and current.y == exitCell.y:
return self._restore_path(parent, start, exitCell)
neighbors = maze.getNeighbors(current)
for neighbor in reversed(neighbors):
npos = (neighbor.x, neighbor.y)
if npos not in visited:
parent[npos] = current
stack.append(neighbor)
return []

View File

@ -1,41 +0,0 @@
import heapq
from strategies.pathfinding_strategy import PathFindingStrategy
class DijkstraStrategy(PathFindingStrategy):
name = "Dijkstra"
def findPath(self, maze, start, exitCell):
self.visitedCount = 0
if start is None or exitCell is None:
return []
pq = [(0, start.x, start.y, start)]
dist = {(start.x, start.y): 0}
parent = {}
closed = set()
while pq:
current_cost, _, _, current = heapq.heappop(pq)
pos = (current.x, current.y)
if pos in closed:
continue
closed.add(pos)
self.visitedCount += 1
if current.x == exitCell.x and current.y == exitCell.y:
return self._restore_path(parent, start, exitCell)
for neighbor in maze.getNeighbors(current):
npos = (neighbor.x, neighbor.y)
step_cost = getattr(neighbor, "weight", 1)
new_cost = current_cost + step_cost
if new_cost < dist.get(npos, float("inf")):
dist[npos] = new_cost
parent[npos] = current
heapq.heappush(pq, (new_cost, neighbor.x, neighbor.y, neighbor))
return []

View File

@ -1,30 +0,0 @@
from abc import ABC, abstractmethod
class PathFindingStrategy(ABC):
name = "Base"
def __init__(self):
self.visitedCount = 0
@abstractmethod
def findPath(self, maze, start, exitCell):
raise NotImplementedError
def _restore_path(self, parent, start, exitCell):
if exitCell is None or start is None:
return []
path = []
current = exitCell
while True:
path.append(current)
if current.x == start.x and current.y == start.y:
break
current = parent.get((current.x, current.y))
if current is None:
return []
path.reverse()
return path

View File

@ -1 +0,0 @@

Some files were not shown because too many files have changed in this diff Show More