Compare commits

..

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

2151 changed files with 1 additions and 2497921 deletions

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>

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

@ -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

@ -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 @@

View File

@ -1,71 +0,0 @@
def create_node(name, phone):
return {'name': name, 'phone': phone, 'left': None, 'right': None}
def bst_insert(root, name, phone):
if root is None:
return create_node(name, phone)
if name == root['name']:
root['phone'] = phone
elif name < root['name']:
root['left'] = bst_insert(root['left'], name, phone)
else:
root['right'] = bst_insert(root['right'], name, phone)
return root
def bst_find(root, name):
if root is None:
return None
if name == root['name']:
return root['phone']
elif name < root['name']:
return bst_find(root['left'], name)
else:
return bst_find(root['right'], name)
def _find_min(node):
while node['left'] is not None:
node = node['left']
return node
def bst_delete(root, name):
if root is None:
return None
if name < root['name']:
root['left'] = bst_delete(root['left'], name)
elif name > root['name']:
root['right'] = bst_delete(root['right'], name)
else:
if root['left'] is None:
return root['right']
if root['right'] is None:
return root['left']
min_node = _find_min(root['right'])
root['name'] = min_node['name']
root['phone'] = min_node['phone']
root['right'] = bst_delete(root['right'], min_node['name'])
return root
def bst_list_all(root):
result = []
def inorder(node):
if node is None:
return
inorder(node['left'])
result.append((node['name'], node['phone']))
inorder(node['right'])
inorder(root)
return result
if __name__ == '__main__':
root = None
root = bst_insert(root, 'Иван', '123-456')
root = bst_insert(root, 'Борис', '789-012')
root = bst_insert(root, 'Анна', '345-678')
root = bst_insert(root, 'Иван', '111-222')
print(bst_list_all(root))
print(bst_find(root, 'Иван'))
print(bst_find(root, 'Петр'))
root = bst_delete(root, 'Борис')
print(bst_list_all(root))

View File

@ -1,126 +0,0 @@
import random
import time
import csv
import sys
sys.setrecursionlimit(20000)
from linked_list_phonebook import ll_insert, ll_find, ll_delete, ll_list_all
from hash_table_phonebook import ht_insert, ht_find, ht_delete, ht_list_all
from bst_phonebook import bst_insert, bst_find, bst_delete, bst_list_all
def generate_records(n, seed=42):
random.seed(seed)
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 prepare_datasets(base_records):
shuffled = base_records.copy()
random.shuffle(shuffled)
sorted_records = sorted(base_records, key=lambda x: x[0])
return shuffled, sorted_records
def run_experiment(struct_funcs, records, mode_name, repeats=5):
results = []
for rep in range(repeats):
struct = struct_funcs['create']()
start = time.perf_counter()
for name, phone in records:
struct = struct_funcs['insert'](struct, name, phone)
end = time.perf_counter()
insert_time = end - start
existing_names = [name for name, _ in records]
sample_existing = random.sample(existing_names, 100)
nonexistent = [f"NotExist_{i}" for i in range(10)]
search_names = sample_existing + nonexistent
random.shuffle(search_names)
start = time.perf_counter()
for name in search_names:
_ = struct_funcs['find'](struct, name)
end = time.perf_counter()
find_time = end - start
to_delete = random.sample(existing_names, 50)
start = time.perf_counter()
for name in to_delete:
struct = struct_funcs['delete'](struct, name)
end = time.perf_counter()
delete_time = end - start
results.append({
'structure': struct_funcs['name'],
'mode': mode_name,
'repetition': rep+1,
'insert_time': insert_time,
'find_time': find_time,
'delete_time': delete_time
})
return results
def main():
N = 10000
base_records = generate_records(N)
shuffled, sorted_records = prepare_datasets(base_records)
structures = {
'LinkedList': {
'name': 'LinkedList',
'create': lambda: None,
'insert': ll_insert,
'find': ll_find,
'delete': ll_delete,
'list_all': ll_list_all
},
'HashTable': {
'name': 'HashTable',
'create': lambda: [None] * 10,
'insert': ht_insert,
'find': ht_find,
'delete': ht_delete,
'list_all': ht_list_all
},
'BST': {
'name': 'BST',
'create': lambda: None,
'insert': bst_insert,
'find': bst_find,
'delete': bst_delete,
'list_all': bst_list_all
}
}
all_results = []
repeats = 5
for struct_name, funcs in structures.items():
print(f"Testing {struct_name} on random order...")
res_random = run_experiment(funcs, shuffled, 'random', repeats)
all_results.extend(res_random)
print(f"Testing {struct_name} on sorted order...")
res_sorted = run_experiment(funcs, sorted_records, 'sorted', repeats)
all_results.extend(res_sorted)
with open('experiment_results.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['Structure', 'Mode', 'Repeat', 'Insert (sec)', 'Search (sec)', 'Delete (sec)'])
for r in all_results:
writer.writerow([
r['structure'],
r['mode'],
r['repetition'],
f"{r['insert_time']:.6f}",
f"{r['find_time']:.6f}",
f"{r['delete_time']:.6f}"
])
print("Experiment finished. Results saved to experiment_results.csv")
if __name__ == '__main__':
main()

View File

@ -1,31 +0,0 @@
Structure,Mode,Repeat,Insert (sec),Search (sec),Delete (sec)
LinkedList,random,1,4.432559,0.034196,0.014270
LinkedList,random,2,4.999931,0.038043,0.020281
LinkedList,random,3,4.771456,0.030191,0.014131
LinkedList,random,4,4.707315,0.033500,0.016198
LinkedList,random,5,4.721361,0.036586,0.011988
LinkedList,sorted,1,4.139028,0.024011,0.010482
LinkedList,sorted,2,4.212383,0.024592,0.011765
LinkedList,sorted,3,4.674211,0.027756,0.012189
LinkedList,sorted,4,4.610210,0.031519,0.012244
LinkedList,sorted,5,4.565687,0.029739,0.012747
HashTable,random,1,0.659990,0.003889,0.001728
HashTable,random,2,0.666055,0.005980,0.002002
HashTable,random,3,0.669948,0.004087,0.002176
HashTable,random,4,0.661882,0.007439,0.001897
HashTable,random,5,0.680420,0.004016,0.001649
HashTable,sorted,1,0.648261,0.004277,0.002922
HashTable,sorted,2,0.654924,0.004136,0.001793
HashTable,sorted,3,0.645509,0.003900,0.002249
HashTable,sorted,4,0.637906,0.004056,0.001657
HashTable,sorted,5,0.643536,0.003846,0.001741
BST,random,1,0.029415,0.000515,0.000183
BST,random,2,0.027684,0.000216,0.000142
BST,random,3,0.026213,0.000252,0.000159
BST,random,4,0.026987,0.000207,0.000135
BST,random,5,0.028321,0.000271,0.000183
BST,sorted,1,10.293772,0.093178,0.053520
BST,sorted,2,10.142204,0.088924,0.049079
BST,sorted,3,10.142037,0.078281,0.059416
BST,sorted,4,10.139818,0.100162,0.056881
BST,sorted,5,10.102982,0.082247,0.051973
1 Structure Mode Repeat Insert (sec) Search (sec) Delete (sec)
2 LinkedList random 1 4.432559 0.034196 0.014270
3 LinkedList random 2 4.999931 0.038043 0.020281
4 LinkedList random 3 4.771456 0.030191 0.014131
5 LinkedList random 4 4.707315 0.033500 0.016198
6 LinkedList random 5 4.721361 0.036586 0.011988
7 LinkedList sorted 1 4.139028 0.024011 0.010482
8 LinkedList sorted 2 4.212383 0.024592 0.011765
9 LinkedList sorted 3 4.674211 0.027756 0.012189
10 LinkedList sorted 4 4.610210 0.031519 0.012244
11 LinkedList sorted 5 4.565687 0.029739 0.012747
12 HashTable random 1 0.659990 0.003889 0.001728
13 HashTable random 2 0.666055 0.005980 0.002002
14 HashTable random 3 0.669948 0.004087 0.002176
15 HashTable random 4 0.661882 0.007439 0.001897
16 HashTable random 5 0.680420 0.004016 0.001649
17 HashTable sorted 1 0.648261 0.004277 0.002922
18 HashTable sorted 2 0.654924 0.004136 0.001793
19 HashTable sorted 3 0.645509 0.003900 0.002249
20 HashTable sorted 4 0.637906 0.004056 0.001657
21 HashTable sorted 5 0.643536 0.003846 0.001741
22 BST random 1 0.029415 0.000515 0.000183
23 BST random 2 0.027684 0.000216 0.000142
24 BST random 3 0.026213 0.000252 0.000159
25 BST random 4 0.026987 0.000207 0.000135
26 BST random 5 0.028321 0.000271 0.000183
27 BST sorted 1 10.293772 0.093178 0.053520
28 BST sorted 2 10.142204 0.088924 0.049079
29 BST sorted 3 10.142037 0.078281 0.059416
30 BST sorted 4 10.139818 0.100162 0.056881
31 BST sorted 5 10.102982 0.082247 0.051973

View File

@ -1,47 +0,0 @@
from linked_list_phonebook import ll_insert, ll_find, ll_delete, ll_list_all
def hash_function(name, table_size):
return hash(name) % table_size
def ht_insert(buckets, name, phone):
idx = hash_function(name, len(buckets))
head = buckets[idx]
new_head = ll_insert(head, name, phone)
buckets[idx] = new_head
return buckets
def ht_find(buckets, name):
idx = hash_function(name, len(buckets))
head = buckets[idx]
return ll_find(head, name)
def ht_delete(buckets, name):
idx = hash_function(name, len(buckets))
head = buckets[idx]
new_head = ll_delete(head, name)
buckets[idx] = new_head
return buckets
def ht_list_all(buckets):
all_records = []
for head in buckets:
current = head
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
if __name__ == '__main__':
SIZE = 5
buckets = [None] * SIZE
ht_insert(buckets, 'Иван', '123-456')
ht_insert(buckets, 'Борис', '789-012')
ht_insert(buckets, 'Анна', '345-678')
ht_insert(buckets, 'Иван', '111-222')
print(ht_list_all(buckets))
print(ht_find(buckets, 'Анна'))
print(ht_find(buckets, 'Петр'))
ht_delete(buckets, 'Борис')
print(ht_list_all(buckets))

View File

@ -1,67 +0,0 @@
def create_node(name, phone):
return {'name': name, 'phone': phone, 'next': None}
def ll_insert(head, name, phone):
current = head
while current is not None:
if current['name'] == name:
current['phone'] = phone
return head
current = current['next']
new_node = create_node(name, phone)
if head is None:
return new_node
current = head
while current['next'] is not None:
current = current['next']
current['next'] = new_node
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']
prev = head
current = head['next']
while current is not None:
if current['name'] == name:
prev['next'] = current['next']
return head
prev = current
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 pair: pair[0])
return records
if __name__ == '__main__':
head = None
head = ll_insert(head, 'Иван', '123-456')
head = ll_insert(head, 'Борис', '789-012')
head = ll_insert(head, 'Анна', '345-678')
head = ll_insert(head, 'Иван', '111-222')
print(ll_list_all(head))
print(ll_find(head, 'Иван'))
print(ll_find(head, 'Петр'))
head = ll_delete(head, 'Борис')
print(ll_list_all(head))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View File

@ -1,39 +0,0 @@
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
df = pd.read_csv('experiment_results.csv')
mean_times = df.groupby(['Structure', 'Mode'])[['Insert (sec)', 'Search (sec)', 'Delete (sec)']].mean().reset_index()
structures = mean_times['Structure'].unique()
modes = mean_times['Mode'].unique()
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
operations = ['Insert (sec)', 'Search (sec)', 'Delete (sec)']
titles = ['Insertion', 'Search', 'Deletion']
for ax, op, title in zip(axes, operations, titles):
x = np.arange(len(structures))
width = 0.35
random_vals = []
sorted_vals = []
for s in structures:
random_row = mean_times[(mean_times['Structure'] == s) & (mean_times['Mode'] == 'random')]
sorted_row = mean_times[(mean_times['Structure'] == s) & (mean_times['Mode'] == 'sorted')]
random_vals.append(random_row[op].values[0] if not random_row.empty else 0)
sorted_vals.append(sorted_row[op].values[0] if not sorted_row.empty else 0)
ax.bar(x - width/2, random_vals, width, label='Random')
ax.bar(x + width/2, sorted_vals, width, label='Sorted')
ax.set_xticks(x)
ax.set_xticklabels(structures)
ax.set_ylabel('Time (seconds)')
ax.set_title(title)
ax.legend()
plt.tight_layout()
plt.savefig('performance_comparison.png', dpi=150)
plt.show()

View File

@ -1,16 +0,0 @@
maze,strategy,time_ms,visited_cells,path_length
Small 10x6,BFS,0.05722500009142095,25.0,16.0
Small 10x6,DFS,0.05680966667872175,24.0,16.0
Small 10x6,AStar,0.04801966664066034,23.0,16.0
Medium 10x10,BFS,0.04772166676048073,47.0,16.0
Medium 10x10,DFS,0.034641333362136116,44.0,30.0
Medium 10x10,AStar,0.0983669999641279,47.0,16.0
Large 20x20,BFS,0.09949400002066493,100.0,36.0
Large 20x20,DFS,0.07004933331700158,75.0,68.0
Large 20x20,AStar,0.16450733316257052,85.0,36.0
Empty 15x15,BFS,0.13264433331035738,133.0,17.0
Empty 15x15,DFS,0.11371733338213137,161.0,89.0
Empty 15x15,AStar,0.1543506666621397,65.0,17.0
No exit 10x10,BFS,0.04392100011803753,25.0,0.0
No exit 10x10,DFS,0.05871466661725814,25.0,0.0
No exit 10x10,AStar,0.046440666665148456,25.0,0.0
1 maze strategy time_ms visited_cells path_length
2 Small 10x6 BFS 0.05722500009142095 25.0 16.0
3 Small 10x6 DFS 0.05680966667872175 24.0 16.0
4 Small 10x6 AStar 0.04801966664066034 23.0 16.0
5 Medium 10x10 BFS 0.04772166676048073 47.0 16.0
6 Medium 10x10 DFS 0.034641333362136116 44.0 30.0
7 Medium 10x10 AStar 0.0983669999641279 47.0 16.0
8 Large 20x20 BFS 0.09949400002066493 100.0 36.0
9 Large 20x20 DFS 0.07004933331700158 75.0 68.0
10 Large 20x20 AStar 0.16450733316257052 85.0 36.0
11 Empty 15x15 BFS 0.13264433331035738 133.0 17.0
12 Empty 15x15 DFS 0.11371733338213137 161.0 89.0
13 Empty 15x15 AStar 0.1543506666621397 65.0 17.0
14 No exit 10x10 BFS 0.04392100011803753 25.0 0.0
15 No exit 10x10 DFS 0.05871466661725814 25.0 0.0
16 No exit 10x10 AStar 0.046440666665148456 25.0 0.0

View File

@ -1,438 +0,0 @@
import sys
import os
from collections import deque
import heapq
import time
import csv
import matplotlib.pyplot as plt
import numpy as np
class GridPoint:
def __init__(self, x, y):
self.x = x
self.y = y
self.blocked = False
self.is_start = False
self.is_exit = False
def can_step(self):
return not self.blocked
class Labyrinth:
def __init__(self, w, h):
self.w = w
self.h = h
self.grid = [[GridPoint(x, y) for x in range(w)] for y in range(h)]
self.start_point = None
self.exit_point = None
def get_point(self, x, y):
if 0 <= x < self.w and 0 <= y < self.h:
return self.grid[y][x]
return None
def set_point(self, x, y, typ):
p = self.get_point(x, y)
if not p:
return
if typ == 'wall':
p.blocked = True
elif typ == 'start':
if self.start_point:
self.start_point.is_start = False
p.is_start = True
p.blocked = False
self.start_point = p
elif typ == 'exit':
if self.exit_point:
self.exit_point.is_exit = False
p.is_exit = True
p.blocked = False
self.exit_point = p
elif typ == 'path':
p.blocked = False
def neighbors(self, p):
dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)]
res = []
for dx, dy in dirs:
nx, ny = p.x + dx, p.y + dy
nb = self.get_point(nx, ny)
if nb and nb.can_step():
res.append(nb)
return res
class MazeLoader:
def load(self, filename):
raise NotImplementedError
class TextMazeLoader(MazeLoader):
def load(self, filename):
with open(filename, 'r') as f:
lines = [line.rstrip('\n') for line in f]
h = len(lines)
w = max(len(line) for line in lines) if h > 0 else 0
start_cnt = 0
exit_cnt = 0
lab = Labyrinth(w, h)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
if ch == '#':
lab.set_point(x, y, 'wall')
elif ch == 'S':
lab.set_point(x, y, 'start')
start_cnt += 1
elif ch == 'E':
lab.set_point(x, y, 'exit')
exit_cnt += 1
else:
lab.set_point(x, y, 'path')
if start_cnt != 1 or exit_cnt != 1:
raise ValueError(f"Need exactly one S and one E. Found S={start_cnt}, E={exit_cnt}")
return lab
class SearchAlgorithm:
def find_way(self, lab, start, goal):
raise NotImplementedError
def _build_path(self, prev, start, goal):
path = []
cur = goal
while cur:
path.append(cur)
cur = prev.get(cur)
path.reverse()
return path
def get_visited(self):
return getattr(self, '_visited', 0)
class BreadthFirst(SearchAlgorithm):
def find_way(self, lab, start, goal):
q = deque([start])
prev = {start: None}
seen = {start}
while q:
cur = q.popleft()
if cur == goal:
self._visited = len(seen)
return self._build_path(prev, start, goal)
for nb in lab.neighbors(cur):
if nb not in seen:
seen.add(nb)
prev[nb] = cur
q.append(nb)
self._visited = len(seen)
return []
class DepthFirst(SearchAlgorithm):
def find_way(self, lab, start, goal):
stack = [start]
prev = {start: None}
seen = {start}
while stack:
cur = stack.pop()
if cur == goal:
self._visited = len(seen)
return self._build_path(prev, start, goal)
for nb in lab.neighbors(cur):
if nb not in seen:
seen.add(nb)
prev[nb] = cur
stack.append(nb)
self._visited = len(seen)
return []
class AStar(SearchAlgorithm):
def _dist(self, a, b):
return abs(a.x - b.x) + abs(a.y - b.y)
def find_way(self, lab, start, goal):
heap = []
cnt = 0
start_f = self._dist(start, goal)
heapq.heappush(heap, (start_f, cnt, start))
cnt += 1
prev = {}
g = {start: 0}
f = {start: start_f}
seen = set()
while heap:
cur_f, _, cur = heapq.heappop(heap)
seen.add(cur)
if cur == goal:
self._visited = len(seen)
return self._build_path(prev, start, goal)
if cur_f > f.get(cur, float('inf')):
continue
for nb in lab.neighbors(cur):
new_g = g[cur] + 1
if new_g < g.get(nb, float('inf')):
prev[nb] = cur
g[nb] = new_g
new_f = new_g + self._dist(nb, goal)
f[nb] = new_f
heapq.heappush(heap, (new_f, cnt, nb))
cnt += 1
self._visited = len(seen)
return []
class LabyrinthSolver:
def __init__(self, lab):
self.lab = lab
self.algorithm = None
def set_algorithm(self, algo):
self.algorithm = algo
def solve(self):
if not self.algorithm:
return None
t0 = time.perf_counter()
path = self.algorithm.find_way(self.lab, self.lab.start_point, self.lab.exit_point)
t1 = time.perf_counter()
ms = (t1 - t0) * 1000
return ms, self.algorithm.get_visited(), len(path)
class Player:
def __init__(self, start, lab):
self.current = start
self.last = None
self.lab = lab
def move(self, cell):
if cell and cell.can_step():
self.last = self.current
self.current = cell
return True
return False
def undo(self):
if self.last:
self.current, self.last = self.last, None
return True
return False
class Command:
def do(self):
raise NotImplementedError
def revert(self):
raise NotImplementedError
class MoveCommand(Command):
def __init__(self, player, dx, dy, lab):
self.player = player
self.dx = dx
self.dy = dy
self.lab = lab
self.done = False
def do(self):
nx = self.player.current.x + self.dx
ny = self.player.current.y + self.dy
target = self.lab.get_point(nx, ny)
if target and target.can_step():
self.player.move(target)
self.done = True
return True
return False
def revert(self):
if self.done:
self.player.undo()
self.done = False
return True
return False
class InteractiveView:
def __init__(self, lab, player):
self.lab = lab
self.player = player
def render(self):
os.system('cls' if os.name == 'nt' else 'clear')
print("=" * (self.lab.w * 2 + 4))
print(" LABYRINTH (P = player)")
print("=" * (self.lab.w * 2 + 4))
for y in range(self.lab.h):
print(" ", end='')
for x in range(self.lab.w):
p = self.lab.get_point(x, y)
if self.player.current == p:
print('P', end=' ')
elif p == self.lab.start_point:
print('S', end=' ')
elif p == self.lab.exit_point:
print('E', end=' ')
elif p.blocked:
print('#', end=' ')
else:
print('.', end=' ')
print()
print("=" * (self.lab.w * 2 + 4))
print(f" Position: ({self.player.current.x},{self.player.current.y})")
print(" Controls: h(left) j(down) k(up) l(right) u=undo q=quit")
print(" Auto-search: b=BFS d=DFS a=A*")
def run_experiment(maze_file, algo, runs=5):
loader = TextMazeLoader()
lab = loader.load(maze_file)
total_ms = 0
total_visited = 0
total_len = 0
for _ in range(runs):
solver = LabyrinthSolver(lab)
solver.set_algorithm(algo)
stats = solver.solve()
if stats:
ms, vis, plen = stats
total_ms += ms
total_visited += vis
total_len += plen
return total_ms / runs, total_visited / runs, total_len / runs
def generate_plots(results):
mazes = list(set([r['maze'] for r in results]))
strategies = ['BFS', 'DFS', 'AStar']
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
x = np.arange(len(mazes))
width = 0.25
for i, strat in enumerate(strategies):
times = []
for maze in mazes:
val = next((r['time_ms'] for r in results if r['maze'] == maze and r['strategy'] == strat), 0)
times.append(val)
axes[0].bar(x + i*width, times, width, label=strat)
axes[0].set_xlabel('Maze')
axes[0].set_ylabel('Time (ms)')
axes[0].set_title('Execution Time')
axes[0].set_xticks(x + width)
axes[0].set_xticklabels(mazes, rotation=45, ha='right')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
for i, strat in enumerate(strategies):
visited = []
for maze in mazes:
val = next((r['visited_cells'] for r in results if r['maze'] == maze and r['strategy'] == strat), 0)
visited.append(val)
axes[1].bar(x + i*width, visited, width, label=strat)
axes[1].set_xlabel('Maze')
axes[1].set_ylabel('Visited Cells')
axes[1].set_title('Visited Cells')
axes[1].set_xticks(x + width)
axes[1].set_xticklabels(mazes, rotation=45, ha='right')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
for i, strat in enumerate(strategies):
lengths = []
for maze in mazes:
val = next((r['path_length'] for r in results if r['maze'] == maze and r['strategy'] == strat), 0)
lengths.append(val)
axes[2].bar(x + i*width, lengths, width, label=strat)
axes[2].set_xlabel('Maze')
axes[2].set_ylabel('Path Length')
axes[2].set_title('Path Length')
axes[2].set_xticks(x + width)
axes[2].set_xticklabels(mazes, rotation=45, ha='right')
axes[2].legend()
axes[2].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('performance_comparison.png', dpi=150, bbox_inches='tight')
plt.show()
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == 'experiment':
print("Running experiments on all mazes...")
maze_files = [
("maze/maze1.txt", "Small 10x6"),
("maze/maze10x10.txt", "Medium 10x10"),
("maze/maze20x20.txt", "Large 20x20"),
("maze/maze_empty.txt", "Empty 15x15"),
("maze/maze_no_exit.txt", "No exit 10x10")
]
algorithms = [
("BFS", BreadthFirst()),
("DFS", DepthFirst()),
("AStar", AStar())
]
results = []
for fname, label in maze_files:
print(f"Testing {label}...")
for aname, algo in algorithms:
try:
avg_t, avg_v, avg_l = run_experiment(fname, algo, runs=3)
results.append({
'maze': label,
'strategy': aname,
'time_ms': avg_t,
'visited_cells': avg_v,
'path_length': avg_l
})
print(f" {aname}: time={avg_t:.3f}ms visited={avg_v:.0f} length={avg_l:.0f}")
except Exception as e:
print(f" {aname}: ERROR {e}")
# save csv
with open('experiment_results.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=['maze', 'strategy', 'time_ms', 'visited_cells', 'path_length'])
writer.writeheader()
writer.writerows(results)
generate_plots(results)
print("Done. Results saved to experiment_results.csv and performance_comparison.png")
sys.exit(0)
# else interactive mode
loader = TextMazeLoader()
lab = loader.load("maze/maze1.txt")
player = Player(lab.start_point, lab)
view = InteractiveView(lab, player)
view.render()
solver = LabyrinthSolver(lab)
history = []
while True:
key = input("\n > ").lower()
if key == 'q':
print("Goodbye!")
break
elif key == 'b':
solver.set_algorithm(BreadthFirst())
ms, vis, plen = solver.solve()
print(f"BFS: {ms:.3f}ms, visited={vis}, length={plen}")
elif key == 'd':
solver.set_algorithm(DepthFirst())
ms, vis, plen = solver.solve()
print(f"DFS: {ms:.3f}ms, visited={vis}, length={plen}")
elif key == 'a':
solver.set_algorithm(AStar())
ms, vis, plen = solver.solve()
print(f"A*: {ms:.3f}ms, visited={vis}, length={plen}")
elif key in ('h','j','k','l'):
moves = {'h': (-1,0), 'l': (1,0), 'k': (0,-1), 'j': (0,1)}
dx, dy = moves[key]
cmd = MoveCommand(player, dx, dy, lab)
if cmd.do():
history.append(cmd)
view.render()
if player.current == lab.exit_point:
print("\n*** YOU REACHED THE EXIT! ***")
print(f"Total moves: {len(history)}")
break
else:
print("Can't go there - wall!")
elif key == 'u':
if history:
cmd = history.pop()
cmd.revert()
view.render()
print("Undo last move")
else:
print("Nothing to undo")
else:
print("Unknown command")

View File

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

View File

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

View File

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

View File

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

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