Merge pull request '[2]Lab-2' (#333) from soninrv/2026-rff_mp:soninrv_lab2 into develop
Reviewed-on: #333
This commit is contained in:
commit
6068b06062
305
soninrv/docs/data/lab1/phonebook.py
Normal file
305
soninrv/docs/data/lab1/phonebook.py
Normal 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")
|
||||||
49
soninrv/docs/data/lab1/plots.py
Normal file
49
soninrv/docs/data/lab1/plots.py
Normal 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()
|
||||||
91
soninrv/docs/data/lab1/results.csv
Normal file
91
soninrv/docs/data/lab1/results.csv
Normal 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
|
||||||
|
30
soninrv/docs/data/lab2/empty_30x30.txt
Normal file
30
soninrv/docs/data/lab2/empty_30x30.txt
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
##############################
|
||||||
|
#S #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
# E#
|
||||||
|
##############################
|
||||||
100
soninrv/docs/data/lab2/large_100x100.txt
Normal file
100
soninrv/docs/data/lab2/large_100x100.txt
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
####################################################################################################
|
||||||
|
#S# # # # # # # # # # # ##
|
||||||
|
# ##### # ### # # ### # ##### # ##### # # ########### # ### ### # ####### ####### # ### ### ##### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
##### # ### # # ### ##### ######### ####### # ### # ##### ### # ### # # ##### # ### ######### ### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ##### # ######### # # # ### # ##### ##### ### ### # ##### ##### ############# # # # # ####### ####
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
### # # # # ######### # ### ####### ### # ####### ##### # # # ##### # ### # # # ##### # # ### # # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### # ########### # ### # # ####### ############# ### # ##### # # # # # ### # # # ######### # # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### # # # # ######### # ##### ####### ### ############# # ####### ### # ##################### # ##
|
||||||
|
# # # # # # # # # # # # # # # ##
|
||||||
|
### ### ### ##### ##### ######### ### ### ##### # # ### ##### ######### ####### ####### # ### ### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### ### ### ####### # # ##### ##### # ### # # ### # ##### ######### # ##### ##### # ### # ##### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ##### # # # # ### # ####### ### # # ##### # ### ########### ##### # ### # ### # # ######### # ####
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ####### # # # # # ####### # # ### ### # ##### ### ### # # ### # # ######### ### ######### ### # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # ### ### ##### ### # # # ##### ### # ### ##### ### ##### # ######### # # ##### # ### # ####### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # # # ####### ####### # # ### # ####### # # ##### # # # ### ####### ################### # # ### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ##### ### # ### ########### ##### ### ### # ### # ##### # # # ### # # # ######### # ######### ####
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
##### # ### ### ### ####### ### # ### ### ##### ####### # ####### ########### # # # ### ### # ### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # ##### ### ### ### ### ####### # # # ### ##### # # # ### # ##### # # ############# ########### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # # # # # # ##### # # ######### ####### ##### # ##### # ### # # ######### # # ### # ### # # ######
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ##### # # ### ##### ################# # ### ##### ##### # ############### # ### ########### # # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # ##### # # ##### ### ##### ### # ####### ### ####### # # # ### # ##### # ### ##### ##### # ### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### ##### # ### ### # # ##### ### # ### # # ####### # # # # # # # ### # ### ######### # ##### # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
### ######### # ### ### # # # ### # ######### # ####### # # # # ##### # ### ### ### # ####### # ####
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### ### ### ### ### ### # # # ####### # # # # ### ##### ### # # # ##### ####### # # ######### # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # ### ### ### ####### ### ##### # ############# ##### ### ####### # # ### ### # # # # ### ##### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### ####### ### # ### ### ### # # # ### ######### # ####### # # # # ####### # ### ##### ##### # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
##### # ##### # # # # ### ### ##### # ######### ########### # # ####### ######### # # # # # ### # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ##### ##### # ### # # ##### ### ### # ##### ##### ##### # ##### ####### ##### ##### ##### # ### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
##### ### # # # ####### ### ### ####### # # ##### ### # # ##### # # ######### ### # # # # # # ### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ##### ### # ### ### # ##### ##### # ### ##### ####### # # # ####### ######### # ### # # ####### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
### ### # # ####### ### # # ### ### # # # ####### ########### ##### # # ### ####### ### ##### # # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### ### # # # ##### ### ### ### # ### # ### # # # ### ### ##### # # ######### # # # ##### # ######
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### # ### # # ### # # # # ### # ######### ### ### # # ########### # # ##### # ######### # # # # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # # ### # # ### # ### # # ##### ####### ### # ##### ### # # # ######### # # ### # ### # # # ### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
### ####### ### # ##### # ### ### # ##### # ### # ##### ### # ### ### ##### ### ##### ### ##### # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # # ### ### ####### ### # # # ### ####### # ### # # ### ####### # ### ####### # # # ########### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # ### ########### ### # # ######### ######### # # # # # # # # ### ############# ##################
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # # ### ### # # # # # # ##### ### ### # ### # # # ##### # ##### # # ### # # # ### # # ### # # # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ##### ### ##### # ### ### ##### # # ### # ##### # # ### ### # ####### ##### # ### # # ### # # # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
##### ### # # # # ### ### ### # ### # ### ##### ### ### ##### ##### # ########### # # # # ### # # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # ####### ####### ### ### ### # # ### ### ##### ### # # # ### # # # ### ### ######### # # ########
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # ####### # ####### # ##### # # # # ### ### # # # ##### ##### # ##### ### # # # ### ### ### ### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### ### ### # # # # ##### # # # # ########### ##### ##### # # # ### ### ##### # ##### ######### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # ### ####### ####### ##### # # ##### ##### ##### # ##### # ##### # # # # # ##### # ######### # ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### ### # # ### # # ##### ### ### ####### ##### ##### # # ##### # # # ### ### # ### ####### ### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
### # # ##### # ### ##### # # ### ### # # # # # ##### ####### # # # # ####### ##### # # # # ### ####
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # ### # # ##### ### # # # ####### # ##### ##### ######### ### ##### # ### ### ##### ### ####### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ### ######### ### # ##### ######### # ### # ##### ### ######### # # # ####### ############# ### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
### ### # ########### # # ##### # ##### # ### # # ####### ### ####### # ##### ### ### ### ##### ####
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# # # ### ### ##### # ##### ####### # ### # ### ### # # ### ### ################# ##### ### ##### ##
|
||||||
|
# # # # # # # # # # # # # # # # # # # # # # # # # # # ##
|
||||||
|
# ##### ### ### ### ### # ################### # # ### ####### ### ### # # # ### ### # # # # # # # ##
|
||||||
|
# # # # # # # # # # # E##
|
||||||
|
####################################################################################################
|
||||||
|
####################################################################################################
|
||||||
611
soninrv/docs/data/lab2/main.py
Normal file
611
soninrv/docs/data/lab2/main.py
Normal 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()
|
||||||
50
soninrv/docs/data/lab2/medium_50x50.txt
Normal file
50
soninrv/docs/data/lab2/medium_50x50.txt
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
##################################################
|
||||||
|
#S# # # # # ##
|
||||||
|
# ### ### ### ##### ##### # # ####### # ##### # ##
|
||||||
|
# # # # # # # # # # # # # ##
|
||||||
|
### # ### # ##### ######### ##### # ### # # ### ##
|
||||||
|
# # # # # # # # # # # # ##
|
||||||
|
# # # # # ####### ### # # # ############# ### # ##
|
||||||
|
# # # # # # # # # # # # # ##
|
||||||
|
# # # # # ##### ### # # # ########### # # # ######
|
||||||
|
# # # # # # # # # # # # # # # ##
|
||||||
|
# # ### ### ########### # # # ### # # # ####### ##
|
||||||
|
# # # # # # # # # # # # # ##
|
||||||
|
# ### ### ####### ### # # # # # ### # ### # # # ##
|
||||||
|
# # # # # # # # # # # # # # # ##
|
||||||
|
# ### # ##### ##### ##### ##### # ##### ### # ####
|
||||||
|
# # # # # # # # # # # ##
|
||||||
|
### ##### # # # # ### # ##### ####### # ####### ##
|
||||||
|
# # # # # # # # # # # # ##
|
||||||
|
# ######### # # ### ##### # ##### ####### # # # ##
|
||||||
|
# # # # # # # # # # # # # # ##
|
||||||
|
# # # # # ##### # ### # # ##### ### ####### # # ##
|
||||||
|
# # # # # # # # # # # # ##
|
||||||
|
# ### ### ######### ### # ### # ### ### # ##### ##
|
||||||
|
# # # # # # # # # # # # ##
|
||||||
|
# ### # ####### # ### ####### ### ##### # # ### ##
|
||||||
|
# # # # # # # # # # # # ##
|
||||||
|
### ##### ### ##### ########### ### # ##### # ####
|
||||||
|
# # # # # # # # # # # ##
|
||||||
|
# ### ##### ##### # # # # ############# ##### # ##
|
||||||
|
# # # # # # # # # ##
|
||||||
|
# # ######### # # ########### ### # ########### ##
|
||||||
|
# # # # # # # # # # # ##
|
||||||
|
### ### # # # # ####### # # ### ########### # # ##
|
||||||
|
# # # # # # # # # # # # # ##
|
||||||
|
# ### ### ### # ### # ##### # ### # ######### # ##
|
||||||
|
# # # # # # # # # # # # ##
|
||||||
|
# # ### # # ### # ####### ### ### ##### # ##### ##
|
||||||
|
# # # # # # # # # # # # # # # ##
|
||||||
|
# ### # # # # # ### ### # ##### ### # # ### # # ##
|
||||||
|
# # # # # # # # # # # # # # ##
|
||||||
|
### # ##### # # # ##### ##### ########### ##### ##
|
||||||
|
# # # # # # # # # # # # ##
|
||||||
|
# ### # ##### ##### ##### # # # ##### # # # # # ##
|
||||||
|
# # # # # # # # # # # # # # ##
|
||||||
|
# ##### ### ### ##### # # # ##### # ##### ##### ##
|
||||||
|
# # # # # # # # # # # # # # ##
|
||||||
|
# ### ### ####### # # # # ##### ### # # # # # # ##
|
||||||
|
# # # # # # # # E##
|
||||||
|
##################################################
|
||||||
|
##################################################
|
||||||
20
soninrv/docs/data/lab2/no_exit_20x20.txt
Normal file
20
soninrv/docs/data/lab2/no_exit_20x20.txt
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
####################
|
||||||
|
#S # #
|
||||||
|
# # #
|
||||||
|
# # #
|
||||||
|
# # #
|
||||||
|
# # #
|
||||||
|
# # #
|
||||||
|
# # #
|
||||||
|
# # #
|
||||||
|
# # #
|
||||||
|
# # #
|
||||||
|
# # #
|
||||||
|
# # #
|
||||||
|
# # #
|
||||||
|
# # #
|
||||||
|
# # #
|
||||||
|
# # #
|
||||||
|
# # #
|
||||||
|
# # E#
|
||||||
|
####################
|
||||||
63
soninrv/docs/data/lab2/plots.py
Normal file
63
soninrv/docs/data/lab2/plots.py
Normal 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()
|
||||||
169
soninrv/docs/data/lab2/results.csv
Normal file
169
soninrv/docs/data/lab2/results.csv
Normal 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
|
||||||
|
10
soninrv/docs/data/lab2/small_10x10.txt
Normal file
10
soninrv/docs/data/lab2/small_10x10.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
##########
|
||||||
|
#S # #
|
||||||
|
# # ### #
|
||||||
|
# # #
|
||||||
|
##### # ##
|
||||||
|
# # #
|
||||||
|
# ### #
|
||||||
|
# #### #
|
||||||
|
## E#
|
||||||
|
##########
|
||||||
40
soninrv/docs/data/lab2/weighted_40x40.txt
Normal file
40
soninrv/docs/data/lab2/weighted_40x40.txt
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
########################################
|
||||||
|
#S # #~~ # # ~~ # ..##
|
||||||
|
### # ### # ### # ### # #.# ##### # # ##
|
||||||
|
# # # # #~~ # # # # #.# # .. # # ##
|
||||||
|
# # # # ##### #.### ### ### ### ### # ##
|
||||||
|
# # #~ #. #~# # ~~#.. # # ##
|
||||||
|
#~#########~##### #~# ### ### #######.##
|
||||||
|
#~ # # # ..# # ~~ #.. #.##
|
||||||
|
######### # # # ##### # ######### ### ##
|
||||||
|
# # # # #~~ # # ~~... # ##
|
||||||
|
# ##### #.# ####### # #######.###.# ####
|
||||||
|
# # .# #.#.#. #.# #.. # #.# # ##
|
||||||
|
# #.#.# # #.#.### #.# ##### ### # # # ##
|
||||||
|
# #.# # # ~# ~~# #. # # ..##
|
||||||
|
# #############~# ###.##### #.###.### ##
|
||||||
|
# # # # .# # # # .# ##
|
||||||
|
# ######### # # #~#.### #.# # #####.####
|
||||||
|
# # # # #~#. # #.# # #...##
|
||||||
|
# # # #########.### ### # ####### ###~##
|
||||||
|
# # #~~~~ # .# # # #~##
|
||||||
|
#~### # ### # ### ### ############### ##
|
||||||
|
#~#. # ..# # #~~ .. # .# ##
|
||||||
|
###.# #####~#######.####### # #.# ######
|
||||||
|
#~ # # #~..#~~ .#.....#.. #.# # ..##
|
||||||
|
#~##### # #~### #####.#########.# # # ##
|
||||||
|
# # # #~# #.. ..... # # # # ##
|
||||||
|
# # #.### # # ###.#########.### # # ####
|
||||||
|
# #~~.#~~~# # ~~#. # # # # .##
|
||||||
|
#.#####~### ###.###~# ####### #######.##
|
||||||
|
#. # # # #.# ~ #. # # ##
|
||||||
|
#####~### # # # # #####.# # #~### ###~##
|
||||||
|
# ..~# # # # # # ~~..# #~.. # #~##
|
||||||
|
# ##### # ##### #.# ############### #~##
|
||||||
|
# ..# # ~# #.# #~# ..~~# ..~##
|
||||||
|
##### # ###~#~##### #~#.### #####~######
|
||||||
|
#...# # #.#~#. # .# # #..~#~ # ##
|
||||||
|
#.#####.# #.# #.##### ### # #~#~### # ##
|
||||||
|
# . #~~ # # ~# E##
|
||||||
|
########################################
|
||||||
|
########################################
|
||||||
BIN
soninrv/docs/performance_comparison.png
Normal file
BIN
soninrv/docs/performance_comparison.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
BIN
soninrv/docs/performance_plot.png
Normal file
BIN
soninrv/docs/performance_plot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
172
soninrv/docs/report1.md
Normal file
172
soninrv/docs/report1.md
Normal 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 |
|
||||||
|
Графическое представление результатов приведено на рисунке ниже.
|
||||||
|
[]
|
||||||
|
## 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
105
soninrv/docs/report2.md
Normal 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 |
|
||||||
|
|
||||||
|
Графическое представление результатов приведено на рисунке ниже.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 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, но исследует на 20–30% меньше клеток благодаря манхэттенской эвристике (на 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())`.
|
||||||
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user