[2]Lab-2 #333

Merged
git_admin merged 12 commits from soninrv/2026-rff_mp:soninrv_lab2 into develop 2026-05-30 11:21:52 +00:00
16 changed files with 1815 additions and 0 deletions

View File

@ -0,0 +1,305 @@
# 1. СВЯЗНЫЙ СПИСОК
def ll_create_node(name, phone):
return {'name': name, 'phone': phone, 'next': None}
def ll_insert(head, name, phone):
"""Добавить или обновить запись. Возвращает голову списка."""
node = head
while node is not None:
if node['name'] == name:
node['phone'] = phone # обновить
return head
node = node['next']
# Вставка в начало — O(1)
new_node = ll_create_node(name, phone)
new_node['next'] = head
return new_node
def ll_find(head, name):
"""Вернуть телефон или None."""
node = head
while node is not None:
if node['name'] == name:
return node['phone']
node = node['next']
return None
def ll_delete(head, name):
"""Удалить узел, вернуть новую голову."""
if head is None:
return None
if head['name'] == name:
return head['next']
prev, node = head, head['next']
while node is not None:
if node['name'] == name:
prev['next'] = node['next']
return head
prev, node = node, node['next']
return head
def ll_list_all(head):
"""Собрать все записи и вернуть отсортированный список (name, phone)."""
result = []
node = head
while node is not None:
result.append((node['name'], node['phone']))
node = node['next']
result.sort(key=lambda x: x[0])
return result
# 2. ХЕШ-ТАБЛИЦА (цепочки через связный список)
HT_SIZE = 1024 # число корзин (степень двойки)
def ht_create(size=HT_SIZE):
return [None] * size
def _ht_hash(name, size):
h = 5381
for ch in name:
h = ((h << 5) + h) ^ ord(ch)
return h % size
def ht_insert(buckets, name, phone):
idx = _ht_hash(name, len(buckets))
buckets[idx] = ll_insert(buckets[idx], name, phone)
def ht_find(buckets, name):
idx = _ht_hash(name, len(buckets))
return ll_find(buckets[idx], name)
def ht_delete(buckets, name):
idx = _ht_hash(name, len(buckets))
buckets[idx] = ll_delete(buckets[idx], name)
def ht_list_all(buckets):
result = []
for head in buckets:
result.extend(ll_list_all(head))
result.sort(key=lambda x: x[0])
return result
# 3. ДВОИЧНОЕ ДЕРЕВО ПОИСКА (BST)
def bst_create_node(name, phone):
return {'name': name, 'phone': phone, 'left': None, 'right': None}
def bst_insert(root, name, phone):
"""Вставить / обновить. Возвращает корень."""
if root is None:
return bst_create_node(name, phone)
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):
"""Вернуть телефон или None."""
while root is not None:
if name == root['name']:
return root['phone']
elif name < root['name']:
root = root['left']
else:
root = root['right']
return None
def _bst_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']
# Два потомка: заменить минимальным из правого поддерева
successor = _bst_min(root['right'])
root['name'] = successor['name']
root['phone'] = successor['phone']
root['right'] = bst_delete(root['right'], successor['name'])
return root
def bst_list_all(root):
"""Центрированный (in-order) обход → отсортированный список."""
result = []
stack = []
node = root
while stack or node is not None:
while node is not None:
stack.append(node)
node = node['left']
node = stack.pop()
result.append((node['name'], node['phone']))
node = node['right']
return result
"""
Экспериментальная часть: замер производительности трёх структур данных.
"""
import time
import csv
import random
import os
import sys
sys.setrecursionlimit(30000) # BST с отсортированными данными — глубокая рекурсия
# ── Параметры ──────────────────────────────────────────────────────────────
N = 10_000 # размер набора
REPEATS = 5 # повторений каждого замера
SEARCH_N = 100 # запросов на поиск (существующих)
SEARCH_MISS = 10 # запросов на поиск (отсутствующих)
DELETE_N = 50 # удалений
random.seed(42)
# ── Генерация данных ───────────────────────────────────────────────────────
records_sorted = [(f"User_{i:05d}", f"+7-000-{i:07d}") for i in range(N)]
records_shuffled = records_sorted[:]
random.shuffle(records_shuffled)
search_names_hit = [records_sorted[i][0] for i in random.sample(range(N), SEARCH_N)]
search_names_miss = [f"None_{i:04d}" for i in range(SEARCH_MISS)]
search_names = search_names_hit + search_names_miss
delete_names = [records_sorted[i][0] for i in random.sample(range(N), DELETE_N)]
# ── Вспомогательные функции ────────────────────────────────────────────────
def build_ll(records):
head = None
for name, phone in records:
head = ll_insert(head, name, phone)
return head
def build_ht(records):
buckets = ht_create()
for name, phone in records:
ht_insert(buckets, name, phone)
return buckets
def build_bst(records):
root = None
for name, phone in records:
root = bst_insert(root, name, phone)
return root
STRUCTURES = {
'LinkedList': {
'build': build_ll,
'find': ll_find,
'delete': lambda ds, name: ll_delete(ds, name), # возвращает новый head
'list_all': ll_list_all,
'mutable': False, # ll_delete возвращает новую голову
},
'HashTable': {
'build': build_ht,
'find': ht_find,
'delete': lambda ds, name: ht_delete(ds, name), # in-place, returns None
'list_all': ht_list_all,
'mutable': True,
},
'BST': {
'build': build_bst,
'find': bst_find,
'delete': lambda ds, name: bst_delete(ds, name), # возвращает новый корень
'list_all': bst_list_all,
'mutable': False,
},
}
MODES = {
'shuffled': records_shuffled,
'sorted': records_sorted,
}
# ── Замер ──────────────────────────────────────────────────────────────────
def measure(fn, *args, repeats=REPEATS):
times = []
for _ in range(repeats):
t0 = time.perf_counter()
fn(*args)
times.append(time.perf_counter() - t0)
return times
rows = [["structure", "mode", "operation", "run", "time_sec"]]
for struct_name, ops in STRUCTURES.items():
for mode_name, records in MODES.items():
print(f" {struct_name} / {mode_name} ...", flush=True)
# ── А. Вставка ──────────────────────────────────────────────────
insert_times = []
for run in range(REPEATS):
t0 = time.perf_counter()
ds = ops['build'](records)
insert_times.append(time.perf_counter() - t0)
rows.append([struct_name, mode_name, "insert", run + 1, insert_times[-1]])
# Строим структуру один раз для поиска и удаления
ds = ops['build'](records)
# ── Б. Поиск ────────────────────────────────────────────────────
def do_search(ds=ds):
for name in search_names:
ops['find'](ds, name)
search_times = measure(do_search)
for run, t in enumerate(search_times, 1):
rows.append([struct_name, mode_name, "find", run, t])
# ── В. Удаление ─────────────────────────────────────────────────
# Удаление изменяет структуру, поэтому каждый раз пересобираем
delete_times = []
for run in range(REPEATS):
ds2 = ops['build'](records)
t0 = time.perf_counter()
for name in delete_names:
result = ops['delete'](ds2, name)
if result is not None: # ll / bst возвращают новую голову/корень
ds2 = result
delete_times.append(time.perf_counter() - t0)
rows.append([struct_name, mode_name, "delete", run + 1, delete_times[-1]])
print(f" insert avg={sum(insert_times)/REPEATS:.4f}s "
f"find avg={sum(search_times)/REPEATS:.4f}s "
f"delete avg={sum(delete_times)/REPEATS:.4f}s")
with open("results.csv", "w", newline="", encoding="utf-8") as f:
csv.writer(f).writerows(rows)
print("\nРезультаты сохранены в docs/data/results.csv")

View File

@ -0,0 +1,49 @@
import csv
import statistics
import matplotlib.pyplot as plt
import numpy as np
rows = []
with open("results.csv", encoding="utf-8") as f:
for r in csv.DictReader(f):
rows.append(r)
STRUCTS = ["LinkedList", "HashTable", "BST"]
MODE_MAP = {"shuffled": "случайный", "sorted": "отсортированный"}
OPS = [("insert", "Вставка"), ("find", "Поиск"), ("delete", "Удаление")]
def stats(structure, mode, operation):
vals = [float(r["time_sec"]) for r in rows
if r["structure"] == structure and r["mode"] == mode and r["operation"] == operation]
if not vals:
return 0.0, 0.0
return statistics.mean(vals), statistics.stdev(vals) if len(vals) > 1 else 0.0
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
fig.suptitle("Среднее время операций (сек, лог-шкала, N=10 000, 5 повторений)")
x = np.arange(len(STRUCTS))
WIDTH = 0.35
for ax, (op_key, op_title) in zip(axes, OPS):
for i, mode in enumerate(["shuffled", "sorted"]):
avgs, stds = [], []
for s in STRUCTS:
avg, std = stats(s, mode, op_key)
avgs.append(avg)
stds.append(std)
offset = (i - 0.5) * WIDTH
ax.bar(x + offset, avgs, WIDTH, label=MODE_MAP[mode])
ax.errorbar(x + offset, avgs, yerr=stds, fmt="none", capsize=4)
ax.set_yscale("log")
ax.set_title(op_title)
ax.set_xticks(x)
ax.set_xticklabels(STRUCTS)
ax.set_ylabel("Время (сек)")
ax.yaxis.grid(True, which="both", linestyle="--", alpha=0.5)
ax.set_axisbelow(True)
ax.legend()
plt.tight_layout()
plt.savefig("../../performance_comparison.png", dpi=150)
plt.show()

View File

@ -0,0 +1,91 @@
structure,mode,operation,run,time_sec
LinkedList,shuffled,insert,1,3.294921400000021
LinkedList,shuffled,insert,2,2.92912730000171
LinkedList,shuffled,insert,3,2.8146583999987342
LinkedList,shuffled,insert,4,2.7935691000020597
LinkedList,shuffled,insert,5,2.8566659999996773
LinkedList,shuffled,find,1,0.03453739999895333
LinkedList,shuffled,find,2,0.03489120000085677
LinkedList,shuffled,find,3,0.034232199999678414
LinkedList,shuffled,find,4,0.03294129999994766
LinkedList,shuffled,find,5,0.03249359999972512
LinkedList,shuffled,delete,1,0.016195199998037424
LinkedList,shuffled,delete,2,0.016463700001622783
LinkedList,shuffled,delete,3,0.016346699998393888
LinkedList,shuffled,delete,4,0.016296699999656994
LinkedList,shuffled,delete,5,0.016424599998572376
LinkedList,sorted,insert,1,2.383058199997322
LinkedList,sorted,insert,2,2.375423099998443
LinkedList,sorted,insert,3,2.34873769999831
LinkedList,sorted,insert,4,2.3596142000023974
LinkedList,sorted,insert,5,2.3823104000002786
LinkedList,sorted,find,1,0.027813299999252195
LinkedList,sorted,find,2,0.02766450000126497
LinkedList,sorted,find,3,0.027582700000493787
LinkedList,sorted,find,4,0.02761159999863594
LinkedList,sorted,find,5,0.02766390000033425
LinkedList,sorted,delete,1,0.015935499999613967
LinkedList,sorted,delete,2,0.01771329999974114
LinkedList,sorted,delete,3,0.016032899999117944
LinkedList,sorted,delete,4,0.01585219999833498
LinkedList,sorted,delete,5,0.016385800001444295
HashTable,shuffled,insert,1,0.06008769999971264
HashTable,shuffled,insert,2,0.02979799999957322
HashTable,shuffled,insert,3,0.02958039999793982
HashTable,shuffled,insert,4,0.03261639999982435
HashTable,shuffled,insert,5,0.03028959999937797
HashTable,shuffled,find,1,0.00040919999810284935
HashTable,shuffled,find,2,0.00025829999867710285
HashTable,shuffled,find,3,0.000260199998592725
HashTable,shuffled,find,4,0.00024839999969117343
HashTable,shuffled,find,5,0.0002446999969833996
HashTable,shuffled,delete,1,0.0007224000000860542
HashTable,shuffled,delete,2,0.00018980000095325522
HashTable,shuffled,delete,3,0.00014259999807109125
HashTable,shuffled,delete,4,0.00020619999850168824
HashTable,shuffled,delete,5,0.00014730000111740083
HashTable,sorted,insert,1,0.02703069999915897
HashTable,sorted,insert,2,0.0286950000008801
HashTable,sorted,insert,3,0.029971800002385862
HashTable,sorted,insert,4,0.028408000001945766
HashTable,sorted,insert,5,0.028463399998145178
HashTable,sorted,find,1,0.00038550000317627564
HashTable,sorted,find,2,0.00026449999859323725
HashTable,sorted,find,3,0.0002604000001156237
HashTable,sorted,find,4,0.0002567999981692992
HashTable,sorted,find,5,0.0002595000005385373
HashTable,sorted,delete,1,0.00020910000239382498
HashTable,sorted,delete,2,0.0002086000022245571
HashTable,sorted,delete,3,0.00015020000137155876
HashTable,sorted,delete,4,0.0001517000018793624
HashTable,sorted,delete,5,0.00015150000035646372
BST,shuffled,insert,1,0.026569400000880705
BST,shuffled,insert,2,0.028130499998951564
BST,shuffled,insert,3,0.02583809999850928
BST,shuffled,insert,4,0.02573110000230372
BST,shuffled,insert,5,0.02615979999973206
BST,shuffled,find,1,0.00020509999740170315
BST,shuffled,find,2,0.00017859999934444204
BST,shuffled,find,3,0.00017999999909079634
BST,shuffled,find,4,0.00017889999799081124
BST,shuffled,find,5,0.00017719999959808774
BST,shuffled,delete,1,0.00014940000255592167
BST,shuffled,delete,2,0.0010156000025745016
BST,shuffled,delete,3,0.000994199999695411
BST,shuffled,delete,4,0.0011020999991160352
BST,shuffled,delete,5,0.0011912000009033363
BST,sorted,insert,1,10.031728599999042
BST,sorted,insert,2,9.260749099998066
BST,sorted,insert,3,9.739691700000549
BST,sorted,insert,4,8.961757199998829
BST,sorted,insert,5,9.583165900003223
BST,sorted,find,1,0.041536599997925805
BST,sorted,find,2,0.04151529999944614
BST,sorted,find,3,0.04165329999887035
BST,sorted,find,4,0.04157439999835333
BST,sorted,find,5,0.0415880999971705
BST,sorted,delete,1,0.04558349999933853
BST,sorted,delete,2,0.041408099998079706
BST,sorted,delete,3,0.041001800000231015
BST,sorted,delete,4,0.041335800000524614
BST,sorted,delete,5,0.041272599999501836
1 structure mode operation run time_sec
2 LinkedList shuffled insert 1 3.294921400000021
3 LinkedList shuffled insert 2 2.92912730000171
4 LinkedList shuffled insert 3 2.8146583999987342
5 LinkedList shuffled insert 4 2.7935691000020597
6 LinkedList shuffled insert 5 2.8566659999996773
7 LinkedList shuffled find 1 0.03453739999895333
8 LinkedList shuffled find 2 0.03489120000085677
9 LinkedList shuffled find 3 0.034232199999678414
10 LinkedList shuffled find 4 0.03294129999994766
11 LinkedList shuffled find 5 0.03249359999972512
12 LinkedList shuffled delete 1 0.016195199998037424
13 LinkedList shuffled delete 2 0.016463700001622783
14 LinkedList shuffled delete 3 0.016346699998393888
15 LinkedList shuffled delete 4 0.016296699999656994
16 LinkedList shuffled delete 5 0.016424599998572376
17 LinkedList sorted insert 1 2.383058199997322
18 LinkedList sorted insert 2 2.375423099998443
19 LinkedList sorted insert 3 2.34873769999831
20 LinkedList sorted insert 4 2.3596142000023974
21 LinkedList sorted insert 5 2.3823104000002786
22 LinkedList sorted find 1 0.027813299999252195
23 LinkedList sorted find 2 0.02766450000126497
24 LinkedList sorted find 3 0.027582700000493787
25 LinkedList sorted find 4 0.02761159999863594
26 LinkedList sorted find 5 0.02766390000033425
27 LinkedList sorted delete 1 0.015935499999613967
28 LinkedList sorted delete 2 0.01771329999974114
29 LinkedList sorted delete 3 0.016032899999117944
30 LinkedList sorted delete 4 0.01585219999833498
31 LinkedList sorted delete 5 0.016385800001444295
32 HashTable shuffled insert 1 0.06008769999971264
33 HashTable shuffled insert 2 0.02979799999957322
34 HashTable shuffled insert 3 0.02958039999793982
35 HashTable shuffled insert 4 0.03261639999982435
36 HashTable shuffled insert 5 0.03028959999937797
37 HashTable shuffled find 1 0.00040919999810284935
38 HashTable shuffled find 2 0.00025829999867710285
39 HashTable shuffled find 3 0.000260199998592725
40 HashTable shuffled find 4 0.00024839999969117343
41 HashTable shuffled find 5 0.0002446999969833996
42 HashTable shuffled delete 1 0.0007224000000860542
43 HashTable shuffled delete 2 0.00018980000095325522
44 HashTable shuffled delete 3 0.00014259999807109125
45 HashTable shuffled delete 4 0.00020619999850168824
46 HashTable shuffled delete 5 0.00014730000111740083
47 HashTable sorted insert 1 0.02703069999915897
48 HashTable sorted insert 2 0.0286950000008801
49 HashTable sorted insert 3 0.029971800002385862
50 HashTable sorted insert 4 0.028408000001945766
51 HashTable sorted insert 5 0.028463399998145178
52 HashTable sorted find 1 0.00038550000317627564
53 HashTable sorted find 2 0.00026449999859323725
54 HashTable sorted find 3 0.0002604000001156237
55 HashTable sorted find 4 0.0002567999981692992
56 HashTable sorted find 5 0.0002595000005385373
57 HashTable sorted delete 1 0.00020910000239382498
58 HashTable sorted delete 2 0.0002086000022245571
59 HashTable sorted delete 3 0.00015020000137155876
60 HashTable sorted delete 4 0.0001517000018793624
61 HashTable sorted delete 5 0.00015150000035646372
62 BST shuffled insert 1 0.026569400000880705
63 BST shuffled insert 2 0.028130499998951564
64 BST shuffled insert 3 0.02583809999850928
65 BST shuffled insert 4 0.02573110000230372
66 BST shuffled insert 5 0.02615979999973206
67 BST shuffled find 1 0.00020509999740170315
68 BST shuffled find 2 0.00017859999934444204
69 BST shuffled find 3 0.00017999999909079634
70 BST shuffled find 4 0.00017889999799081124
71 BST shuffled find 5 0.00017719999959808774
72 BST shuffled delete 1 0.00014940000255592167
73 BST shuffled delete 2 0.0010156000025745016
74 BST shuffled delete 3 0.000994199999695411
75 BST shuffled delete 4 0.0011020999991160352
76 BST shuffled delete 5 0.0011912000009033363
77 BST sorted insert 1 10.031728599999042
78 BST sorted insert 2 9.260749099998066
79 BST sorted insert 3 9.739691700000549
80 BST sorted insert 4 8.961757199998829
81 BST sorted insert 5 9.583165900003223
82 BST sorted find 1 0.041536599997925805
83 BST sorted find 2 0.04151529999944614
84 BST sorted find 3 0.04165329999887035
85 BST sorted find 4 0.04157439999835333
86 BST sorted find 5 0.0415880999971705
87 BST sorted delete 1 0.04558349999933853
88 BST sorted delete 2 0.041408099998079706
89 BST sorted delete 3 0.041001800000231015
90 BST sorted delete 4 0.041335800000524614
91 BST sorted delete 5 0.041272599999501836

View File

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

View File

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

View File

@ -0,0 +1,611 @@
"""
maze_solver.py Поиск выхода из лабиринта.
Паттерны GoF: Builder, Strategy, Observer, Command.
Алгоритмы: BFS, DFS, A*, Dijkstra.
Запуск:
python maze_solver.py --solve mazes/small_10x10.txt --algo bfs
python maze_solver.py --walk mazes/small_10x10.txt
python maze_solver.py --experiment
"""
from __future__ import annotations
import argparse
import csv
import heapq
import os
import statistics
import sys
import time
from abc import ABC, abstractmethod
from collections import deque
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Optional, Tuple
sys.setrecursionlimit(100_000)
# ЭТАП 1. МОДЕЛЬ ЛАБИРИНТА — Cell, Maze
@dataclass
class Cell:
"""Одна клетка лабиринта."""
x: int
y: int
is_wall: bool = False
is_start: bool = False
is_exit: bool = False
weight: int = 1 # 1=асфальт 2=песок 3=болото
def is_passable(self) -> bool:
return not self.is_wall
def __repr__(self) -> str:
if self.is_wall: return "#"
if self.is_start: return "S"
if self.is_exit: return "E"
return " "
# heapq требует сравнения объектов
def __lt__(self, other: Cell) -> bool:
return (self.x, self.y) < (other.x, other.y)
def __hash__(self):
return hash((self.x, self.y))
def __eq__(self, other):
return isinstance(other, Cell) and self.x == other.x and self.y == other.y
class Maze:
"""Двумерная сетка клеток."""
def __init__(self, width: int, height: int):
self.width = width
self.height = height
self._cells: List[List[Cell]] = [
[Cell(x, y) for x in range(width)] for y in range(height)
]
self.start: Optional[Cell] = None
self.exit: Optional[Cell] = None
def get_cell(self, x: int, y: int) -> Optional[Cell]:
if 0 <= x < self.width and 0 <= y < self.height:
return self._cells[y][x]
return None
def get_neighbors(self, cell: Cell) -> List[Cell]:
"""Четыре соседа — только проходимые, в пределах границ."""
result = []
for dx, dy in ((0, -1), (0, 1), (-1, 0), (1, 0)):
nb = self.get_cell(cell.x + dx, cell.y + dy)
if nb and nb.is_passable():
result.append(nb)
return result
# ЭТАП 2. ПАТТЕРН BUILDER — загрузка лабиринта из файла
_WEIGHT_MAP = {' ': 1, '.': 2, '~': 3}
class MazeBuilder(ABC):
"""Интерфейс строителя лабиринта."""
@abstractmethod
def build_from_file(self, filename: str) -> Maze: ...
@abstractmethod
def build_from_string(self, text: str) -> Maze: ...
class TextFileMazeBuilder(MazeBuilder):
"""
Строит Maze из текстового файла:
# — стена S — старт E — выход
' ' путь (w=1) . песок (w=2) ~ болото (w=3)
"""
def build_from_file(self, filename: str) -> Maze:
return self.build_from_string(Path(filename).read_text(encoding="utf-8"))
def build_from_string(self, text: str) -> Maze:
lines = text.splitlines()
while lines and not lines[-1].strip():
lines.pop()
if not lines:
raise ValueError("Пустой лабиринт")
height = len(lines)
width = max(len(l) for l in lines)
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
cell = maze.get_cell(x, y)
if cell is None:
continue
if ch == '#':
cell.is_wall = True
elif ch == 'S':
cell.is_start = True
maze.start = cell
elif ch == 'E':
cell.is_exit = True
maze.exit = cell
else:
cell.weight = _WEIGHT_MAP.get(ch, 1)
for x in range(len(line), width): # дополнить стенами
c = maze.get_cell(x, y)
if c:
c.is_wall = True
if maze.start is None:
raise ValueError("Нет стартовой клетки 'S'")
if maze.exit is None:
raise ValueError("Нет выходной клетки 'E'")
return maze
# ЭТАП 3. ПАТТЕРН STRATEGY — алгоритмы поиска пути
def _reconstruct(parent: Dict[Cell, Optional[Cell]], end: Cell) -> List[Cell]:
"""Восстановить путь от старта до end по словарю предшественников."""
path, node = [], end
while node is not None:
path.append(node)
node = parent.get(node)
path.reverse()
return path
class PathFindingStrategy(ABC):
"""Интерфейс стратегии поиска пути."""
@property
@abstractmethod
def name(self) -> str: ...
@abstractmethod
def find_path(
self, maze: Maze, start: Cell, exit_cell: Cell
) -> Tuple[List[Cell], int]:
"""Возвращает (path, visited_count). path пуст если пути нет."""
...
class BFSStrategy(PathFindingStrategy):
"""Поиск в ширину — гарантирует кратчайший путь (по числу шагов)."""
@property
def name(self) -> str:
return "BFS"
def find_path(self, maze, start, exit_cell):
queue = deque([start])
parent: Dict[Cell, Optional[Cell]] = {start: None}
visited = 0
while queue:
cur = queue.popleft()
visited += 1
if cur == exit_cell:
return _reconstruct(parent, exit_cell), visited
for nb in maze.get_neighbors(cur):
if nb not in parent:
parent[nb] = cur
queue.append(nb)
return [], visited
class DFSStrategy(PathFindingStrategy):
"""Поиск в глубину — не гарантирует кратчайший путь."""
@property
def name(self) -> str:
return "DFS"
def find_path(self, maze, start, exit_cell):
stack = [start]
parent: Dict[Cell, Optional[Cell]] = {start: None}
visited = 0
while stack:
cur = stack.pop()
visited += 1
if cur == exit_cell:
return _reconstruct(parent, exit_cell), visited
for nb in maze.get_neighbors(cur):
if nb not in parent:
parent[nb] = cur
stack.append(nb)
return [], visited
class AStarStrategy(PathFindingStrategy):
"""A* с манхэттенской эвристикой — направленный поиск, учитывает веса."""
@property
def name(self) -> str:
return "A*"
@staticmethod
def _h(a: Cell, b: Cell) -> int:
return abs(a.x - b.x) + abs(a.y - b.y)
def find_path(self, maze, start, exit_cell):
heap: List[Tuple[int, int, Cell]] = [(0, 0, start)]
g: Dict[Cell, int] = {start: 0}
parent: Dict[Cell, Optional[Cell]] = {start: None}
visited = 0
while heap:
_, g_cur, cur = heapq.heappop(heap)
visited += 1
if cur == exit_cell:
return _reconstruct(parent, exit_cell), visited
if g_cur > g.get(cur, float('inf')):
continue
for nb in maze.get_neighbors(cur):
new_g = g_cur + nb.weight
if new_g < g.get(nb, float('inf')):
g[nb] = new_g
parent[nb] = cur
heapq.heappush(heap, (new_g + self._h(nb, exit_cell), new_g, nb))
return [], visited
class DijkstraStrategy(PathFindingStrategy):
"""Дейкстра — оптимален для взвешенных графов, без эвристики."""
@property
def name(self) -> str:
return "Dijkstra"
def find_path(self, maze, start, exit_cell):
dist: Dict[Cell, int] = {start: 0}
parent: Dict[Cell, Optional[Cell]] = {start: None}
heap: List[Tuple[int, Cell]] = [(0, start)]
visited = 0
while heap:
d, cur = heapq.heappop(heap)
visited += 1
if cur == exit_cell:
return _reconstruct(parent, exit_cell), visited
if d > dist.get(cur, float('inf')):
continue
for nb in maze.get_neighbors(cur):
new_d = d + nb.weight
if new_d < dist.get(nb, float('inf')):
dist[nb] = new_d
parent[nb] = cur
heapq.heappush(heap, (new_d, nb))
return [], visited
# ПАТТЕРН OBSERVER
@dataclass
class SearchStats:
time_ms: float
visited_cells: int
path_length: int
strategy_name: str
def __str__(self) -> str:
return (f"[{self.strategy_name}] "
f"время={self.time_ms:.2f} мс "
f"посещено={self.visited_cells} "
f"длина пути={self.path_length}")
class Observer(ABC):
@abstractmethod
def update(self, event: dict) -> None: ...
class ConsoleView(Observer):
"""Выводит события и рисует лабиринт в консоли."""
def update(self, event: dict) -> None:
kind = event.get("type")
if kind == "maze_loaded":
print(f"[Лабиринт загружен] {event['width']}×{event['height']}")
elif kind == "search_start":
print(f"[Поиск] алгоритм={event['strategy']}")
elif kind == "path_found":
print(f"[Готово] {event['stats']}")
elif kind == "no_path":
print("[Результат] Путь не найден!")
def render(
self,
maze: Maze,
player_pos: Optional[Cell] = None,
path: Optional[List[Cell]] = None,
) -> None:
path_set = set(path) if path else set()
for y in range(maze.height):
row = []
for x in range(maze.width):
cell = maze.get_cell(x, y)
if cell is None:
row.append("?")
elif player_pos and cell == player_pos:
row.append("@")
elif cell.is_wall:
row.append("")
elif cell.is_start:
row.append("S")
elif cell.is_exit:
row.append("E")
elif cell in path_set:
row.append("*")
elif cell.weight == 3:
row.append("~")
elif cell.weight == 2:
row.append(".")
else:
row.append(" ")
print("".join(row))
print()
# ЭТАП 4. ОРКЕСТРАТОР MazeSolver
class MazeSolver:
"""
Связывает Maze + PathFindingStrategy + список Observer.
Паттерны: Strategy (алгоритм подключается снаружи),
Observer (уведомления при завершении).
"""
def __init__(self, maze: Maze, strategy: PathFindingStrategy):
self.maze = maze
self._strategy = strategy
self._observers: List[Observer] = []
self._last_path: List[Cell] = []
def add_observer(self, obs: Observer) -> None:
self._observers.append(obs)
def set_strategy(self, strategy: PathFindingStrategy) -> None:
self._strategy = strategy
def _notify(self, event: dict) -> None:
for obs in self._observers:
obs.update(event)
def solve(self) -> SearchStats:
if not self.maze.start or not self.maze.exit:
raise ValueError("Лабиринт не содержит старт или выход")
self._notify({"type": "search_start", "strategy": self._strategy.name})
t0 = time.perf_counter()
path, visited = self._strategy.find_path(
self.maze, self.maze.start, self.maze.exit
)
elapsed_ms = (time.perf_counter() - t0) * 1000
self._last_path = path
stats = SearchStats(elapsed_ms, visited, len(path), self._strategy.name)
self._notify({"type": "path_found" if path else "no_path", "stats": stats})
return stats
@property
def last_path(self) -> List[Cell]:
return self._last_path
# ЭТАП 5. ПАТТЕРН COMMAND — пошаговое управление игроком
_DIRECTIONS = {'W': (0, -1), 'S': (0, 1), 'A': (-1, 0), 'D': (1, 0)}
class Command(ABC):
@abstractmethod
def execute(self) -> bool: ...
@abstractmethod
def undo(self) -> None: ...
class Player:
def __init__(self, cell: Cell):
self.current_cell = cell
def move_to(self, cell: Cell) -> None:
self.current_cell = cell
class MoveCommand(Command):
"""Перемещение игрока в направлении direction (W/A/S/D)."""
def __init__(self, player: Player, direction: str, maze: Maze,
observers: Optional[List[Observer]] = None):
self._player = player
self._direction = direction.upper()
self._maze = maze
self._observers = observers or []
self._prev: Optional[Cell] = None
def execute(self) -> bool:
dx, dy = _DIRECTIONS.get(self._direction, (0, 0))
target = self._maze.get_cell(
self._player.current_cell.x + dx,
self._player.current_cell.y + dy,
)
if target and target.is_passable():
self._prev = self._player.current_cell
self._player.move_to(target)
for obs in self._observers:
obs.update({"type": "move", "cell": target})
return True
return False
def undo(self) -> None:
if self._prev:
self._player.move_to(self._prev)
self._prev = None
class CommandHistory:
"""Стек выполненных команд (undo/redo)."""
def __init__(self):
self._stack: List[Command] = []
def execute(self, cmd: Command) -> bool:
ok = cmd.execute()
if ok:
self._stack.append(cmd)
return ok
def undo(self) -> bool:
if self._stack:
self._stack.pop().undo()
return True
return False
# ЭТАП 6. ЭКСПЕРИМЕНТАЛЬНАЯ ЧАСТЬ
def run_experiment(
maze_files: List[Tuple[str, str]],
strategies: List[PathFindingStrategy],
repeats: int = 7,
out_csv: str = "results.csv",
) -> None:
builder = TextFileMazeBuilder()
rows = [["maze", "strategy", "run", "time_ms", "visited_cells", "path_length"]]
for maze_name, maze_file in maze_files:
print(f"\n=== {maze_name} ===")
maze = builder.build_from_file(maze_file)
print(f" Размер: {maze.width}×{maze.height}")
for strategy in strategies:
solver = MazeSolver(maze, strategy)
times, visits, lengths = [], [], []
for run in range(1, repeats + 1):
stats = solver.solve()
times.append(stats.time_ms)
visits.append(stats.visited_cells)
lengths.append(stats.path_length)
rows.append([maze_name, strategy.name, run,
round(stats.time_ms, 4),
stats.visited_cells,
stats.path_length])
print(f" {strategy.name:10} | "
f"t={statistics.mean(times):.3f} мс | "
f"посещено={statistics.mean(visits):.0f} | "
f"путь={statistics.mean(lengths):.0f}")
os.makedirs(os.path.dirname(os.path.abspath(out_csv)), exist_ok=True)
with open(out_csv, "w", newline="", encoding="utf-8") as f:
csv.writer(f).writerows(rows)
print(f"\nСохранено: {out_csv}")
# CLI
_ALGO_MAP = {
"bfs": BFSStrategy(),
"dfs": DFSStrategy(),
"astar": AStarStrategy(),
"dijkstra": DijkstraStrategy(),
}
_MAZES = [
("small_10x10", "small_10x10.txt"),
("medium_50x50", "medium_50x50.txt"),
("large_100x100", "large_100x100.txt"),
("empty_30x30", "empty_30x30.txt"),
("no_exit_20x20", "no_exit_20x20.txt"),
("weighted_40x40", "weighted_40x40.txt"),
]
def cmd_solve(maze_file: str, algo: str) -> None:
view = ConsoleView()
maze = TextFileMazeBuilder().build_from_file(maze_file)
view.update({"type": "maze_loaded", "width": maze.width, "height": maze.height})
strategy = _ALGO_MAP.get(algo.lower())
if not strategy:
print(f"Неизвестный алгоритм '{algo}'. Доступны: {', '.join(_ALGO_MAP)}")
return
solver = MazeSolver(maze, strategy)
solver.add_observer(view)
solver.solve()
view.render(maze, path=solver.last_path)
def cmd_walk(maze_file: str) -> None:
view = ConsoleView()
maze = TextFileMazeBuilder().build_from_file(maze_file)
player = Player(maze.start)
history = CommandHistory()
solver = MazeSolver(maze, BFSStrategy())
solver.add_observer(view)
solver.solve()
path = solver.last_path
print("W=вверх S=вниз A=влево D=вправо Z=отмена Q=выход")
while True:
view.render(maze, player_pos=player.current_cell, path=path)
if player.current_cell == maze.exit:
print("Вы достигли выхода!")
break
key = input("Ход: ").strip().upper()
if key == "Q":
break
elif key == "Z":
if not history.undo():
print("Нечего отменять.")
elif key in _DIRECTIONS:
if not history.execute(MoveCommand(player, key, maze, [view])):
print("Туда нельзя — стена.")
else:
print("Неизвестная клавиша.")
def cmd_experiment() -> None:
run_experiment(_MAZES, list(_ALGO_MAP.values()))
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Maze solver")
parser.add_argument("--solve", metavar="FILE", help="Решить лабиринт")
parser.add_argument("--algo", metavar="ALGO", default="bfs",
help="bfs | dfs | astar | dijkstra")
parser.add_argument("--walk", metavar="FILE", help="Ручное управление (WASD)")
parser.add_argument("--experiment", action="store_true",
help="Запустить замеры и сохранить CSV")
args = parser.parse_args()
if args.solve:
cmd_solve(args.solve, args.algo)
elif args.walk:
cmd_walk(args.walk)
elif args.experiment:
cmd_experiment()
else:
parser.print_help()

View File

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

View File

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

View File

@ -0,0 +1,63 @@
import csv
import statistics
import matplotlib.pyplot as plt
import numpy as np
rows = []
with open("results.csv", encoding="utf-8") as f:
for r in csv.DictReader(f):
rows.append(r)
MAZES = ["small_10x10", "medium_50x50", "large_100x100",
"empty_30x30", "no_exit_20x20", "weighted_40x40"]
STRATS = ["BFS", "DFS", "A*", "Dijkstra"]
MAZE_RU = {
"small_10x10": "10×10",
"medium_50x50": "50×50",
"large_100x100": "100×100",
"empty_30x30": "30×30 пустой",
"no_exit_20x20": "20×20 без выхода",
"weighted_40x40":"40×40 взвешенный",
}
def avg(maze, strat, metric):
vals = [float(r[metric]) for r in rows
if r["maze"] == maze and r["strategy"] == strat]
return statistics.mean(vals) if vals else 0.0
def std(maze, strat, metric):
vals = [float(r[metric]) for r in rows
if r["maze"] == maze and r["strategy"] == strat]
return statistics.stdev(vals) if len(vals) > 1 else 0.0
fig, axes = plt.subplots(1, 3, figsize=(16, 5))
fig.suptitle("Сравнение алгоритмов (среднее, 7 запусков)")
x = np.arange(len(MAZES))
W = 0.18
offsets = np.linspace(-(len(STRATS)-1)/2, (len(STRATS)-1)/2, len(STRATS)) * W
for ax, (metric, ylabel, title) in zip(axes, [
("time_ms", "Время (мс)", "Время выполнения"),
("visited_cells", "Посещено клеток", "Посещённые клетки"),
("path_length", "Длина пути", "Длина найденного пути"),
]):
for i, strat in enumerate(STRATS):
vals = [avg(m, strat, metric) for m in MAZES]
errs = [std(m, strat, metric) for m in MAZES]
ax.bar(x + offsets[i], vals, W * 0.95, label=strat, yerr=errs, capsize=3)
ax.set_title(title)
ax.set_xticks(x)
ax.set_xticklabels([MAZE_RU[m] for m in MAZES], fontsize=7, rotation=15)
ax.set_ylabel(ylabel)
ax.legend(fontsize=8)
ax.yaxis.grid(True, linestyle="--", alpha=0.5)
ax.set_axisbelow(True)
if metric == "time_ms":
ax.set_yscale("log")
plt.tight_layout()
plt.savefig("../../performance_plot.png", dpi=150)
plt.close()

View File

@ -0,0 +1,169 @@
maze,strategy,run,time_ms,visited_cells,path_length
small_10x10,BFS,1,0.0844,28,21
small_10x10,BFS,2,0.0664,28,21
small_10x10,BFS,3,0.0644,28,21
small_10x10,BFS,4,0.0632,28,21
small_10x10,BFS,5,0.7441,28,21
small_10x10,BFS,6,0.0654,28,21
small_10x10,BFS,7,0.0643,28,21
small_10x10,DFS,1,0.0597,22,21
small_10x10,DFS,2,0.0523,22,21
small_10x10,DFS,3,0.0512,22,21
small_10x10,DFS,4,0.0518,22,21
small_10x10,DFS,5,0.0511,22,21
small_10x10,DFS,6,0.0508,22,21
small_10x10,DFS,7,0.1135,22,21
small_10x10,A*,1,0.1172,24,21
small_10x10,A*,2,0.0951,24,21
small_10x10,A*,3,0.0935,24,21
small_10x10,A*,4,0.0926,24,21
small_10x10,A*,5,0.0935,24,21
small_10x10,A*,6,0.0922,24,21
small_10x10,A*,7,0.0945,24,21
small_10x10,Dijkstra,1,0.1149,28,21
small_10x10,Dijkstra,2,0.1066,28,21
small_10x10,Dijkstra,3,0.1046,28,21
small_10x10,Dijkstra,4,0.1036,28,21
small_10x10,Dijkstra,5,0.1034,28,21
small_10x10,Dijkstra,6,0.1031,28,21
small_10x10,Dijkstra,7,0.1038,28,21
medium_50x50,BFS,1,1.1695,493,257
medium_50x50,BFS,2,1.1107,493,257
medium_50x50,BFS,3,1.0981,493,257
medium_50x50,BFS,4,1.2213,493,257
medium_50x50,BFS,5,1.1256,493,257
medium_50x50,BFS,6,1.0916,493,257
medium_50x50,BFS,7,1.0941,493,257
medium_50x50,DFS,1,0.7161,263,257
medium_50x50,DFS,2,0.6265,263,257
medium_50x50,DFS,3,0.6072,263,257
medium_50x50,DFS,4,0.6024,263,257
medium_50x50,DFS,5,0.6033,263,257
medium_50x50,DFS,6,0.6594,263,257
medium_50x50,DFS,7,0.654,263,257
medium_50x50,A*,1,1.4393,357,257
medium_50x50,A*,2,1.4165,357,257
medium_50x50,A*,3,1.4844,357,257
medium_50x50,A*,4,1.3735,357,257
medium_50x50,A*,5,1.3595,357,257
medium_50x50,A*,6,1.4585,357,257
medium_50x50,A*,7,1.3453,357,257
medium_50x50,Dijkstra,1,2.0358,493,257
medium_50x50,Dijkstra,2,2.0877,493,257
medium_50x50,Dijkstra,3,2.2691,493,257
medium_50x50,Dijkstra,4,2.0743,493,257
medium_50x50,Dijkstra,5,2.0684,493,257
medium_50x50,Dijkstra,6,2.013,493,257
medium_50x50,Dijkstra,7,2.04,493,257
large_100x100,BFS,1,11.4561,4783,1953
large_100x100,BFS,2,11.1618,4783,1953
large_100x100,BFS,3,11.3113,4783,1953
large_100x100,BFS,4,11.0404,4783,1953
large_100x100,BFS,5,10.9312,4783,1953
large_100x100,BFS,6,11.1477,4783,1953
large_100x100,BFS,7,11.1166,4783,1953
large_100x100,DFS,1,5.0315,2161,1953
large_100x100,DFS,2,4.9678,2161,1953
large_100x100,DFS,3,5.1106,2161,1953
large_100x100,DFS,4,5.5327,2161,1953
large_100x100,DFS,5,5.0265,2161,1953
large_100x100,DFS,6,5.0804,2161,1953
large_100x100,DFS,7,5.0136,2161,1953
large_100x100,A*,1,19.9319,4741,1953
large_100x100,A*,2,19.4914,4741,1953
large_100x100,A*,3,19.39,4741,1953
large_100x100,A*,4,19.4556,4741,1953
large_100x100,A*,5,19.5936,4741,1953
large_100x100,A*,6,19.3358,4741,1953
large_100x100,A*,7,19.1552,4741,1953
large_100x100,Dijkstra,1,20.4017,4783,1953
large_100x100,Dijkstra,2,20.3607,4783,1953
large_100x100,Dijkstra,3,20.1817,4783,1953
large_100x100,Dijkstra,4,20.1812,4783,1953
large_100x100,Dijkstra,5,20.1135,4783,1953
large_100x100,Dijkstra,6,19.9753,4783,1953
large_100x100,Dijkstra,7,20.1115,4783,1953
empty_30x30,BFS,1,1.986,784,55
empty_30x30,BFS,2,1.967,784,55
empty_30x30,BFS,3,1.9533,784,55
empty_30x30,BFS,4,1.9549,784,55
empty_30x30,BFS,5,1.9965,784,55
empty_30x30,BFS,6,2.0811,784,55
empty_30x30,BFS,7,2.0084,784,55
empty_30x30,DFS,1,1.2944,433,379
empty_30x30,DFS,2,1.3148,433,379
empty_30x30,DFS,3,1.2713,433,379
empty_30x30,DFS,4,1.2671,433,379
empty_30x30,DFS,5,1.3947,433,379
empty_30x30,DFS,6,1.2743,433,379
empty_30x30,DFS,7,1.2843,433,379
empty_30x30,A*,1,4.9961,784,55
empty_30x30,A*,2,4.9058,784,55
empty_30x30,A*,3,4.8649,784,55
empty_30x30,A*,4,4.8501,784,55
empty_30x30,A*,5,4.8164,784,55
empty_30x30,A*,6,4.8326,784,55
empty_30x30,A*,7,4.7652,784,55
empty_30x30,Dijkstra,1,4.5931,784,55
empty_30x30,Dijkstra,2,4.5417,784,55
empty_30x30,Dijkstra,3,4.648,784,55
empty_30x30,Dijkstra,4,4.6928,784,55
empty_30x30,Dijkstra,5,4.612,784,55
empty_30x30,Dijkstra,6,4.597,784,55
empty_30x30,Dijkstra,7,4.6834,784,55
no_exit_20x20,BFS,1,0.3933,162,0
no_exit_20x20,BFS,2,0.386,162,0
no_exit_20x20,BFS,3,0.3831,162,0
no_exit_20x20,BFS,4,0.3843,162,0
no_exit_20x20,BFS,5,0.3814,162,0
no_exit_20x20,BFS,6,0.3824,162,0
no_exit_20x20,BFS,7,0.3838,162,0
no_exit_20x20,DFS,1,0.3912,162,0
no_exit_20x20,DFS,2,0.3901,162,0
no_exit_20x20,DFS,3,0.3842,162,0
no_exit_20x20,DFS,4,0.3855,162,0
no_exit_20x20,DFS,5,0.3851,162,0
no_exit_20x20,DFS,6,0.3844,162,0
no_exit_20x20,DFS,7,0.3862,162,0
no_exit_20x20,A*,1,0.8838,162,0
no_exit_20x20,A*,2,0.8866,162,0
no_exit_20x20,A*,3,0.8986,162,0
no_exit_20x20,A*,4,0.8769,162,0
no_exit_20x20,A*,5,0.9976,162,0
no_exit_20x20,A*,6,0.8757,162,0
no_exit_20x20,A*,7,1.003,162,0
no_exit_20x20,Dijkstra,1,0.8448,162,0
no_exit_20x20,Dijkstra,2,0.8276,162,0
no_exit_20x20,Dijkstra,3,0.8252,162,0
no_exit_20x20,Dijkstra,4,0.8274,162,0
no_exit_20x20,Dijkstra,5,0.9752,162,0
no_exit_20x20,Dijkstra,6,0.8365,162,0
no_exit_20x20,Dijkstra,7,0.83,162,0
weighted_40x40,BFS,1,1.2317,533,321
weighted_40x40,BFS,2,1.2659,533,321
weighted_40x40,BFS,3,1.1845,533,321
weighted_40x40,BFS,4,1.5564,533,321
weighted_40x40,BFS,5,1.2026,533,321
weighted_40x40,BFS,6,1.169,533,321
weighted_40x40,BFS,7,1.3397,533,321
weighted_40x40,DFS,1,0.8644,361,321
weighted_40x40,DFS,2,0.827,361,321
weighted_40x40,DFS,3,0.8941,361,321
weighted_40x40,DFS,4,0.9692,361,321
weighted_40x40,DFS,5,0.8452,361,321
weighted_40x40,DFS,6,0.8235,361,321
weighted_40x40,DFS,7,0.8164,361,321
weighted_40x40,A*,1,1.8278,452,321
weighted_40x40,A*,2,1.7486,452,321
weighted_40x40,A*,3,1.8236,452,321
weighted_40x40,A*,4,1.9749,452,321
weighted_40x40,A*,5,1.7385,452,321
weighted_40x40,A*,6,1.7864,452,321
weighted_40x40,A*,7,1.7326,452,321
weighted_40x40,Dijkstra,1,2.0444,533,321
weighted_40x40,Dijkstra,2,2.0199,533,321
weighted_40x40,Dijkstra,3,2.0213,533,321
weighted_40x40,Dijkstra,4,2.0246,533,321
weighted_40x40,Dijkstra,5,2.1628,533,321
weighted_40x40,Dijkstra,6,2.0323,533,321
weighted_40x40,Dijkstra,7,2.1926,533,321
1 maze strategy run time_ms visited_cells path_length
2 small_10x10 BFS 1 0.0844 28 21
3 small_10x10 BFS 2 0.0664 28 21
4 small_10x10 BFS 3 0.0644 28 21
5 small_10x10 BFS 4 0.0632 28 21
6 small_10x10 BFS 5 0.7441 28 21
7 small_10x10 BFS 6 0.0654 28 21
8 small_10x10 BFS 7 0.0643 28 21
9 small_10x10 DFS 1 0.0597 22 21
10 small_10x10 DFS 2 0.0523 22 21
11 small_10x10 DFS 3 0.0512 22 21
12 small_10x10 DFS 4 0.0518 22 21
13 small_10x10 DFS 5 0.0511 22 21
14 small_10x10 DFS 6 0.0508 22 21
15 small_10x10 DFS 7 0.1135 22 21
16 small_10x10 A* 1 0.1172 24 21
17 small_10x10 A* 2 0.0951 24 21
18 small_10x10 A* 3 0.0935 24 21
19 small_10x10 A* 4 0.0926 24 21
20 small_10x10 A* 5 0.0935 24 21
21 small_10x10 A* 6 0.0922 24 21
22 small_10x10 A* 7 0.0945 24 21
23 small_10x10 Dijkstra 1 0.1149 28 21
24 small_10x10 Dijkstra 2 0.1066 28 21
25 small_10x10 Dijkstra 3 0.1046 28 21
26 small_10x10 Dijkstra 4 0.1036 28 21
27 small_10x10 Dijkstra 5 0.1034 28 21
28 small_10x10 Dijkstra 6 0.1031 28 21
29 small_10x10 Dijkstra 7 0.1038 28 21
30 medium_50x50 BFS 1 1.1695 493 257
31 medium_50x50 BFS 2 1.1107 493 257
32 medium_50x50 BFS 3 1.0981 493 257
33 medium_50x50 BFS 4 1.2213 493 257
34 medium_50x50 BFS 5 1.1256 493 257
35 medium_50x50 BFS 6 1.0916 493 257
36 medium_50x50 BFS 7 1.0941 493 257
37 medium_50x50 DFS 1 0.7161 263 257
38 medium_50x50 DFS 2 0.6265 263 257
39 medium_50x50 DFS 3 0.6072 263 257
40 medium_50x50 DFS 4 0.6024 263 257
41 medium_50x50 DFS 5 0.6033 263 257
42 medium_50x50 DFS 6 0.6594 263 257
43 medium_50x50 DFS 7 0.654 263 257
44 medium_50x50 A* 1 1.4393 357 257
45 medium_50x50 A* 2 1.4165 357 257
46 medium_50x50 A* 3 1.4844 357 257
47 medium_50x50 A* 4 1.3735 357 257
48 medium_50x50 A* 5 1.3595 357 257
49 medium_50x50 A* 6 1.4585 357 257
50 medium_50x50 A* 7 1.3453 357 257
51 medium_50x50 Dijkstra 1 2.0358 493 257
52 medium_50x50 Dijkstra 2 2.0877 493 257
53 medium_50x50 Dijkstra 3 2.2691 493 257
54 medium_50x50 Dijkstra 4 2.0743 493 257
55 medium_50x50 Dijkstra 5 2.0684 493 257
56 medium_50x50 Dijkstra 6 2.013 493 257
57 medium_50x50 Dijkstra 7 2.04 493 257
58 large_100x100 BFS 1 11.4561 4783 1953
59 large_100x100 BFS 2 11.1618 4783 1953
60 large_100x100 BFS 3 11.3113 4783 1953
61 large_100x100 BFS 4 11.0404 4783 1953
62 large_100x100 BFS 5 10.9312 4783 1953
63 large_100x100 BFS 6 11.1477 4783 1953
64 large_100x100 BFS 7 11.1166 4783 1953
65 large_100x100 DFS 1 5.0315 2161 1953
66 large_100x100 DFS 2 4.9678 2161 1953
67 large_100x100 DFS 3 5.1106 2161 1953
68 large_100x100 DFS 4 5.5327 2161 1953
69 large_100x100 DFS 5 5.0265 2161 1953
70 large_100x100 DFS 6 5.0804 2161 1953
71 large_100x100 DFS 7 5.0136 2161 1953
72 large_100x100 A* 1 19.9319 4741 1953
73 large_100x100 A* 2 19.4914 4741 1953
74 large_100x100 A* 3 19.39 4741 1953
75 large_100x100 A* 4 19.4556 4741 1953
76 large_100x100 A* 5 19.5936 4741 1953
77 large_100x100 A* 6 19.3358 4741 1953
78 large_100x100 A* 7 19.1552 4741 1953
79 large_100x100 Dijkstra 1 20.4017 4783 1953
80 large_100x100 Dijkstra 2 20.3607 4783 1953
81 large_100x100 Dijkstra 3 20.1817 4783 1953
82 large_100x100 Dijkstra 4 20.1812 4783 1953
83 large_100x100 Dijkstra 5 20.1135 4783 1953
84 large_100x100 Dijkstra 6 19.9753 4783 1953
85 large_100x100 Dijkstra 7 20.1115 4783 1953
86 empty_30x30 BFS 1 1.986 784 55
87 empty_30x30 BFS 2 1.967 784 55
88 empty_30x30 BFS 3 1.9533 784 55
89 empty_30x30 BFS 4 1.9549 784 55
90 empty_30x30 BFS 5 1.9965 784 55
91 empty_30x30 BFS 6 2.0811 784 55
92 empty_30x30 BFS 7 2.0084 784 55
93 empty_30x30 DFS 1 1.2944 433 379
94 empty_30x30 DFS 2 1.3148 433 379
95 empty_30x30 DFS 3 1.2713 433 379
96 empty_30x30 DFS 4 1.2671 433 379
97 empty_30x30 DFS 5 1.3947 433 379
98 empty_30x30 DFS 6 1.2743 433 379
99 empty_30x30 DFS 7 1.2843 433 379
100 empty_30x30 A* 1 4.9961 784 55
101 empty_30x30 A* 2 4.9058 784 55
102 empty_30x30 A* 3 4.8649 784 55
103 empty_30x30 A* 4 4.8501 784 55
104 empty_30x30 A* 5 4.8164 784 55
105 empty_30x30 A* 6 4.8326 784 55
106 empty_30x30 A* 7 4.7652 784 55
107 empty_30x30 Dijkstra 1 4.5931 784 55
108 empty_30x30 Dijkstra 2 4.5417 784 55
109 empty_30x30 Dijkstra 3 4.648 784 55
110 empty_30x30 Dijkstra 4 4.6928 784 55
111 empty_30x30 Dijkstra 5 4.612 784 55
112 empty_30x30 Dijkstra 6 4.597 784 55
113 empty_30x30 Dijkstra 7 4.6834 784 55
114 no_exit_20x20 BFS 1 0.3933 162 0
115 no_exit_20x20 BFS 2 0.386 162 0
116 no_exit_20x20 BFS 3 0.3831 162 0
117 no_exit_20x20 BFS 4 0.3843 162 0
118 no_exit_20x20 BFS 5 0.3814 162 0
119 no_exit_20x20 BFS 6 0.3824 162 0
120 no_exit_20x20 BFS 7 0.3838 162 0
121 no_exit_20x20 DFS 1 0.3912 162 0
122 no_exit_20x20 DFS 2 0.3901 162 0
123 no_exit_20x20 DFS 3 0.3842 162 0
124 no_exit_20x20 DFS 4 0.3855 162 0
125 no_exit_20x20 DFS 5 0.3851 162 0
126 no_exit_20x20 DFS 6 0.3844 162 0
127 no_exit_20x20 DFS 7 0.3862 162 0
128 no_exit_20x20 A* 1 0.8838 162 0
129 no_exit_20x20 A* 2 0.8866 162 0
130 no_exit_20x20 A* 3 0.8986 162 0
131 no_exit_20x20 A* 4 0.8769 162 0
132 no_exit_20x20 A* 5 0.9976 162 0
133 no_exit_20x20 A* 6 0.8757 162 0
134 no_exit_20x20 A* 7 1.003 162 0
135 no_exit_20x20 Dijkstra 1 0.8448 162 0
136 no_exit_20x20 Dijkstra 2 0.8276 162 0
137 no_exit_20x20 Dijkstra 3 0.8252 162 0
138 no_exit_20x20 Dijkstra 4 0.8274 162 0
139 no_exit_20x20 Dijkstra 5 0.9752 162 0
140 no_exit_20x20 Dijkstra 6 0.8365 162 0
141 no_exit_20x20 Dijkstra 7 0.83 162 0
142 weighted_40x40 BFS 1 1.2317 533 321
143 weighted_40x40 BFS 2 1.2659 533 321
144 weighted_40x40 BFS 3 1.1845 533 321
145 weighted_40x40 BFS 4 1.5564 533 321
146 weighted_40x40 BFS 5 1.2026 533 321
147 weighted_40x40 BFS 6 1.169 533 321
148 weighted_40x40 BFS 7 1.3397 533 321
149 weighted_40x40 DFS 1 0.8644 361 321
150 weighted_40x40 DFS 2 0.827 361 321
151 weighted_40x40 DFS 3 0.8941 361 321
152 weighted_40x40 DFS 4 0.9692 361 321
153 weighted_40x40 DFS 5 0.8452 361 321
154 weighted_40x40 DFS 6 0.8235 361 321
155 weighted_40x40 DFS 7 0.8164 361 321
156 weighted_40x40 A* 1 1.8278 452 321
157 weighted_40x40 A* 2 1.7486 452 321
158 weighted_40x40 A* 3 1.8236 452 321
159 weighted_40x40 A* 4 1.9749 452 321
160 weighted_40x40 A* 5 1.7385 452 321
161 weighted_40x40 A* 6 1.7864 452 321
162 weighted_40x40 A* 7 1.7326 452 321
163 weighted_40x40 Dijkstra 1 2.0444 533 321
164 weighted_40x40 Dijkstra 2 2.0199 533 321
165 weighted_40x40 Dijkstra 3 2.0213 533 321
166 weighted_40x40 Dijkstra 4 2.0246 533 321
167 weighted_40x40 Dijkstra 5 2.1628 533 321
168 weighted_40x40 Dijkstra 6 2.0323 533 321
169 weighted_40x40 Dijkstra 7 2.1926 533 321

View File

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

View File

@ -0,0 +1,40 @@
########################################
#S # #~~ # # ~~ # ..##
### # ### # ### # ### # #.# ##### # # ##
# # # # #~~ # # # # #.# # .. # # ##
# # # # ##### #.### ### ### ### ### # ##
# # #~ #. #~# # ~~#.. # # ##
#~#########~##### #~# ### ### #######.##
#~ # # # ..# # ~~ #.. #.##
######### # # # ##### # ######### ### ##
# # # # #~~ # # ~~... # ##
# ##### #.# ####### # #######.###.# ####
# # .# #.#.#. #.# #.. # #.# # ##
# #.#.# # #.#.### #.# ##### ### # # # ##
# #.# # # ~# ~~# #. # # ..##
# #############~# ###.##### #.###.### ##
# # # # .# # # # .# ##
# ######### # # #~#.### #.# # #####.####
# # # # #~#. # #.# # #...##
# # # #########.### ### # ####### ###~##
# # #~~~~ # .# # # #~##
#~### # ### # ### ### ############### ##
#~#. # ..# # #~~ .. # .# ##
###.# #####~#######.####### # #.# ######
#~ # # #~..#~~ .#.....#.. #.# # ..##
#~##### # #~### #####.#########.# # # ##
# # # #~# #.. ..... # # # # ##
# # #.### # # ###.#########.### # # ####
# #~~.#~~~# # ~~#. # # # # .##
#.#####~### ###.###~# ####### #######.##
#. # # # #.# ~ #. # # ##
#####~### # # # # #####.# # #~### ###~##
# ..~# # # # # # ~~..# #~.. # #~##
# ##### # ##### #.# ############### #~##
# ..# # ~# #.# #~# ..~~# ..~##
##### # ###~#~##### #~#.### #####~######
#...# # #.#~#. # .# # #..~#~ # ##
#.#####.# #.# #.##### ### # #~#~### # ##
# . #~~ # # ~# E##
########################################
########################################

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

172
soninrv/docs/report1.md Normal file
View File

@ -0,0 +1,172 @@
# Отчёт по лабораторной работе "Структуры данных"
## 1. Цель работы
Реализовать три структуры данных «с нуля» в процедурной парадигме (без классов), применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций: вставки, поиска и удаления.
Структуры данных:
Связный список (LinkedList) — узлы-словари, соединённые ссылками.
Хеш-таблица (HashTable) — массив корзин (1024 элемента) с цепочками через связный список.
Двоичное дерево поиска (BST) — рекурсивная / итеративная реализация через словари.
N = 10 000 записей вида User_00001, +7-000-0000001.
Два режима: случайный порядок (records_shuffled) и отсортированный (records_sorted).
Поиск: 100 гарантированно существующих имён + 10 несуществующих = 110 запросов.
Удаление: 50 случайных имён из набора.
Каждый замер повторяется 5 раз; записываются все замеры и среднее.
## 2. Результаты экспериментов
| structure | mode | operation | run | time_sec |
|---|---|---|---|---|
| LinkedList | shuffled | insert | 1 | 3.688133922999995 |
| LinkedList | shuffled | insert | 2 | 3.642716359000005 |
| LinkedList | shuffled | insert | 3 | 3.6362029409999934 |
| LinkedList | shuffled | insert | 4 | 3.5635424559999933 |
| LinkedList | shuffled | insert | 5 | 3.6936824539999975 |
| LinkedList | shuffled | find | 1 | 0.0404481799999985 |
| LinkedList | shuffled | find | 2 | 0.0415632419999951 |
| LinkedList | shuffled | find | 3 | 0.0408364839999961 |
| LinkedList | shuffled | find | 4 | 0.0409441910000083 |
| LinkedList | shuffled | find | 5 | 0.0409490519999877 |
| LinkedList | shuffled | delete | 1 | 0.020429828999994 |
| LinkedList | shuffled | delete | 2 | 0.0203125029999995 |
| LinkedList | shuffled | delete | 3 | 0.0205162980000039 |
| LinkedList | shuffled | delete | 4 | 0.0204522580000059 |
| LinkedList | shuffled | delete | 5 | 0.0204940820000132 |
| LinkedList | sorted | insert | 1 | 2.807388945 |
| LinkedList | sorted | insert | 2 | 2.6681887550000027 |
| LinkedList | sorted | insert | 3 | 2.7149360570000027 |
| LinkedList | sorted | insert | 4 | 2.586755936000003 |
| LinkedList | sorted | insert | 5 | 2.858489943000009 |
| LinkedList | sorted | find | 1 | 0.0301240860000007 |
| LinkedList | sorted | find | 2 | 0.0300124050000079 |
| LinkedList | sorted | find | 3 | 0.0301267250000023 |
| LinkedList | sorted | find | 4 | 0.0300742670000033 |
| LinkedList | sorted | find | 5 | 0.0304795409999769 |
| LinkedList | sorted | delete | 1 | 0.0176948809999828 |
| LinkedList | sorted | delete | 2 | 0.0186108259999855 |
| LinkedList | sorted | delete | 3 | 0.0183917109999924 |
| LinkedList | sorted | delete | 4 | 0.0183299800000042 |
| LinkedList | sorted | delete | 5 | 0.0202586389999908 |
| HashTable | shuffled | insert | 1 | 0.040671551999992 |
| HashTable | shuffled | insert | 2 | 0.0356988590000071 |
| HashTable | shuffled | insert | 3 | 0.034698187999993 |
| HashTable | shuffled | insert | 4 | 0.034897758999989 |
| HashTable | shuffled | insert | 5 | 0.0436747020000041 |
| HashTable | shuffled | find | 1 | 0.0003306420000228 |
| HashTable | shuffled | find | 2 | 0.0002776770000139 |
| HashTable | shuffled | find | 3 | 0.0002387590000125 |
| HashTable | shuffled | find | 4 | 0.0002413439999884 |
| HashTable | shuffled | find | 5 | 0.0002350800000101 |
| HashTable | shuffled | delete | 1 | 0.0009653390000039 |
| HashTable | shuffled | delete | 2 | 0.000182843999994 |
| HashTable | shuffled | delete | 3 | 0.000187277000009 |
| HashTable | shuffled | delete | 4 | 0.0001825169999847 |
| HashTable | shuffled | delete | 5 | 0.000182102999986 |
| HashTable | sorted | insert | 1 | 0.031514957000013 |
| HashTable | sorted | insert | 2 | 0.0317737780000015 |
| HashTable | sorted | insert | 3 | 0.0332209919999968 |
| HashTable | sorted | insert | 4 | 0.0438333349999879 |
| HashTable | sorted | insert | 5 | 0.0344081210000126 |
| HashTable | sorted | find | 1 | 0.0004218560000026 |
| HashTable | sorted | find | 2 | 0.0003256969999938 |
| HashTable | sorted | find | 3 | 0.0003048350000085 |
| HashTable | sorted | find | 4 | 0.000252023999991 |
| HashTable | sorted | find | 5 | 0.0002450770000166 |
| HashTable | sorted | delete | 1 | 0.0002077629999917 |
| HashTable | sorted | delete | 2 | 0.000197111999995 |
| HashTable | sorted | delete | 3 | 0.000204272000019 |
| HashTable | sorted | delete | 4 | 0.0001966060000029 |
| HashTable | sorted | delete | 5 | 0.0001917250000076 |
| BST | shuffled | insert | 1 | 0.0322367580000104 |
| BST | shuffled | insert | 2 | 0.0445325409999952 |
| BST | shuffled | insert | 3 | 0.0312052750000191 |
| BST | shuffled | insert | 4 | 0.0302206560000115 |
| BST | shuffled | insert | 5 | 0.0304544809999924 |
| BST | shuffled | find | 1 | 0.000256859999979 |
| BST | shuffled | find | 2 | 0.0001786029999948 |
| BST | shuffled | find | 3 | 0.0001869349999878 |
| BST | shuffled | find | 4 | 0.0001727730000027 |
| BST | shuffled | find | 5 | 0.0001574610000147 |
| BST | shuffled | delete | 1 | 0.0001869909999925 |
| BST | shuffled | delete | 2 | 0.0012688459999878 |
| BST | shuffled | delete | 3 | 0.0012691000000017 |
| BST | shuffled | delete | 4 | 0.001258899999982 |
| BST | shuffled | delete | 5 | 0.0013220630000034 |
| BST | sorted | insert | 1 | 12.957382101000007 |
| BST | sorted | insert | 2 | 12.10390555699999 |
| BST | sorted | insert | 3 | 12.698454105999986 |
| BST | sorted | insert | 4 | 12.181134653000017 |
| BST | sorted | insert | 5 | 12.952122806999997 |
| BST | sorted | find | 1 | 0.0432625550000125 |
| BST | sorted | find | 2 | 0.0455909260000169 |
| BST | sorted | find | 3 | 0.0434497109999938 |
| BST | sorted | find | 4 | 0.04326359800001 |
| BST | sorted | find | 5 | 0.0431787990000032 |
| BST | sorted | delete | 1 | 0.0546987289999947 |
| BST | sorted | delete | 2 | 0.0549414869999793 |
| BST | sorted | delete | 3 | 0.0549512879999838 |
| BST | sorted | delete | 4 | 0.0546492089999901 |
| BST | sorted | delete | 5 | 0.0542962790000274 |
Графическое представление результатов приведено на рисунке ниже.
[![Сравнение производительности](performance_comparison.png)]
## 3. Анализ результатов
### 3.1. Вставка
Связный список: проход по всему списку для поиска дубликата перед вставкой даёт O(n) на каждый элемент. При N = 10 000 это ≈50 млн операций сравнения — отсюда 3.6 с.
Хеш-таблица: хеш вычисляется за O(len(name)), поиск в корзине ≈ O(1). Итог — 0.037 с независимо от порядка.
BST на случайных данных: дерево остаётся примерно сбалансированным, высота ≈ log₂(10000) ≈ 13. Итог — 0.034 с. На отсортированных данных каждый новый элемент добавляется в правое поддерево, высота достигает N = 10 000 — полная деградация до O(n²) суммарно. Итог — 12.6 с (×373 замедление).
### 3.2. Поиск
Связный список: в среднем просматривает N/2 узлов. При 110 запросах — ≈550 000 сравнений. Время ≈ 0.04 с.
Хеш-таблица: поиск в корзине из ~10 элементов — практически мгновенно. Время ≈ 0.0003 с — в 130 раз быстрее связного списка.
BST: случайный порядок — log(N) шагов, ≈ 0.0002 с. Отсортированный — линейный поиск O(n), ≈ 0.044 с (сравнимо со связным списком).
### 3.3. Удаление
Связный список: необходим проход до удаляемого элемента — O(n). При 50 удалениях ≈ 0.02 с.
Хеш-таблица: O(1) в среднем — ≈ 0.0003 с.
BST: случайные данные — O(log n) ≈ 0.001 с. Отсортированные — O(n) ≈ 0.055 с.
### 3.4. Получение отсортированного списка
Связный список и хеш-таблица: сбор всех N элементов + Python sort — O(n log n). Практически одинаково для обеих структур.
BST: in-order обход уже возвращает отсортированный список — O(n), без дополнительной сортировки. При случайном вводе BST является наиболее эффективным для list_all.
## 4. Выводы и рекомендации
На основании экспериментов можно дать следующие практические рекомендации:
Частые вставки и поиск без упорядочивания → Хеш-таблица. Константное среднее время O(1) для всех операций, нечувствительность к порядку данных. Практически всегда лучший выбор для справочников, кэшей, индексов.
Данные нужны в отсортированном порядке (range queries, итерация по алфавиту) → Сбалансированное BST (AVL, красно-чёрное дерево) или B-дерево. Простая BST допустима только при случайном порядке вставки. На отсортированных данных деградирует до O(n).
Очень мало элементов или требуется простота реализации → Связный список. При N < 100 разница в скорости незначительна, а код минимальный.
BST (сбалансированный) vs Хеш-таблица: если нужно только find/insert/delete — хеш-таблица быстрее. Если нужны min/max, range-запросы, сортировка — BST предпочтительнее.
Итог: для реального телефонного справочника с операциями insert/find/delete оптимальна хеш-таблица. Если требуется регулярный вывод списка по алфавиту — BST (сбалансированное). Связный список применим только как учебная модель или для очень маленьких N.

105
soninrv/docs/report2.md Normal file
View File

@ -0,0 +1,105 @@
# Отчёт по лабораторной работе «Поиск выхода из лабиринта»
## 1. Цель работы
Разработать гибкую расширяемую программу для загрузки лабиринта из текстового файла, поиска пути от старта до выхода с возможностью выбора алгоритма и экспериментального сравнения алгоритмов. В ходе работы применены паттерны проектирования GoF: **Builder**, **Strategy**, **Observer**, **Command**.
Реализованные алгоритмы поиска пути:
- **BFS** (поиск в ширину) — гарантирует кратчайший путь по числу шагов.
- **DFS** (поиск в глубину) — не гарантирует кратчайший путь, но быстрее при удачном порядке соседей.
- **A\*** (с манхэттенской эвристикой) — направленный поиск, учитывает веса клеток.
- **Dijkstra** — оптимален для взвешенных графов, без эвристики.
Тестовые лабиринты:
- Маленький 10×10 — простой путь.
- Средний 50×50 — с тупиками (алгоритм Прима).
- Большой 100×100 — запутанная структура.
- Пустой 30×30 — без стен, демонстрирует максимальную нагрузку.
- Без выхода 20×20 — старт и выход разделены глухой стеной.
- Взвешенный 40×40 — клетки с разным весом: асфальт (1), песок (2), болото (3).
Каждый эксперимент повторялся 7 раз, результаты усреднены.
## 2. Описание паттернов
| Паттерн | Классы | Назначение |
|---|---|---|
| Builder | `MazeBuilder`, `TextFileMazeBuilder` | Скрывает парсинг файла; новый формат = новый класс |
| Strategy | `PathFindingStrategy`, BFS/DFS/A\*/Dijkstra | Смена алгоритма одной строкой без изменения остального кода |
| Observer | `Observer`, `ConsoleView` | Визуализация отделена от логики поиска |
| Command | `Command`, `MoveCommand`, `CommandHistory` | Пошаговое управление игроком с поддержкой undo |
## 3. Результаты экспериментов
Усреднённые значения (7 повторений) представлены в таблице:
| Лабиринт | Алгоритм | Время, мс | Посещено клеток | Длина пути |
|---|---|---|---|---|
| 10×10 | BFS | 0.081 | 28 | 21 |
| 10×10 | DFS | 0.053 | 22 | 21 |
| 10×10 | A\* | 0.088 | 24 | 21 |
| 10×10 | Dijkstra | 0.672 | 28 | 21 |
| 50×50 | BFS | 1.150 | 493 | 257 |
| 50×50 | DFS | 0.614 | 263 | 257 |
| 50×50 | A\* | 1.220 | 357 | 257 |
| 50×50 | Dijkstra | 1.685 | 493 | 257 |
| 100×100 | BFS | 11.378 | 4783 | 1953 |
| 100×100 | DFS | 5.141 | 2161 | 1953 |
| 100×100 | A\* | 18.019 | 4741 | 1953 |
| 100×100 | Dijkstra | 17.489 | 4783 | 1953 |
| 30×30 пустой | BFS | 1.832 | 784 | 55 |
| 30×30 пустой | DFS | 1.151 | 433 | 379 |
| 30×30 пустой | A\* | 3.748 | 784 | 55 |
| 30×30 пустой | Dijkstra | 3.945 | 784 | 55 |
| 20×20 без выхода | BFS | 0.370 | 162 | — |
| 20×20 без выхода | DFS | 0.373 | 162 | — |
| 20×20 без выхода | A\* | 0.708 | 162 | — |
| 20×20 без выхода | Dijkstra | 0.677 | 162 | — |
| 40×40 взвешенный | BFS | 1.104 | 533 | 321 |
| 40×40 взвешенный | DFS | 0.774 | 361 | 321 |
| 40×40 взвешенный | A\* | 1.516 | 452 | 321 |
| 40×40 взвешенный | Dijkstra | 1.725 | 533 | 321 |
Графическое представление результатов приведено на рисунке ниже.
![Сравнение производительности](performance_comparison.png)
## 4. Анализ результатов
### 4.1. BFS
Гарантирует кратчайший путь по числу шагов. Исследует все клетки на расстоянии d перед переходом к d+1, поэтому число посещённых клеток максимально среди всех алгоритмов — на лабиринте 100×100 это 4783 клетки. На пустом лабиринте 30×30 BFS находит оптимальный путь длиной 55 клеток, тогда как DFS даёт 379. Время растёт линейно с размером: от 0.08 мс (10×10) до 11.4 мс (100×100).
### 4.2. DFS
Самый быстрый алгоритм по времени: на лабиринте 100×100 — 5.1 мс против 11.4 мс у BFS. Посещает вдвое меньше клеток (2161 против 4783), так как уходит глубоко в одном направлении. Однако на пустом лабиринте DFS даёт путь в 7 раз длиннее оптимального (379 против 55) — алгоритм уходит в угол и обходит весь лабиринт по периметру. Оптимальная длина пути при этом совпадает с BFS только в лабиринтах-лабиринтах (50×50 и 100×100), где единственный путь — сам.
### 4.3. A\*
На невзвешенных лабиринтах находит тот же кратчайший путь, что BFS, но исследует на 2030% меньше клеток благодаря манхэттенской эвристике (на 50×50: 357 против 493). Однако на большом лабиринте 100×100 оказывается медленнее BFS (18 мс против 11 мс) из-за накладных расходов на `heapq`. На взвешенном лабиринте A\* корректно учитывает стоимость клеток, в отличие от BFS.
### 4.4. Dijkstra
На невзвешенных лабиринтах полностью совпадает с BFS по посещённым клеткам и длине пути, но медленнее из-за `heapq` вместо `deque`. На взвешенном лабиринте 40×40 корректно минимизирует суммарный вес пути. Практически вытесняется A\* везде, где цель заранее известна.
### 4.5. Лабиринт «без выхода»
Все алгоритмы обходят все 162 доступные клетки левой секции и возвращают пустой путь. Реализация корректно обрабатывает этот случай через событие `no_path` для Observer. A\* и Dijkstra работают медленнее (0.7 мс против 0.37 мс у BFS/DFS) из-за накладных расходов `heapq` при полном обходе без нахождения цели.
## 5. Выводы и рекомендации
На основе полученных результатов можно сформулировать следующие рекомендации:
- **Кратчайший путь в невзвешенном лабиринте → BFS.** Гарантированный результат, простая реализация на `deque`, линейное масштабирование.
- **Максимальная скорость, длина пути не критична → DFS.** В 2 раза быстрее BFS на больших лабиринтах, посещает вдвое меньше клеток. Не использовать на открытых пространствах — путь может быть многократно длиннее оптимального.
- **Взвешенный граф, цель известна → A\*.** Направленный поиск + учёт весов. На небольших и средних лабиринтах быстрее и экономнее BFS. На очень больших (100×100+) накладные расходы `heapq` могут перевесить выигрыш от эвристики.
- **Взвешенный граф, нужны все кратчайшие расстояния → Dijkstra.** Оптимален без целевой точки. С целевой точкой предпочтительнее A\*.
**Итог:** для навигации в лабиринте с одним выходом оптимален BFS (гарантия) или DFS (скорость). A\* предпочтителен при взвешенных клетках. Паттерн Strategy позволяет переключать алгоритмы без изменения остального кода — `solver.set_strategy(AStarStrategy())`.