[1] 1 exercise complete #358

Closed
KolbasovPD wants to merge 2 commits from KolbasovPD/2026-rff_mp:KolbasovPD into develop
8 changed files with 1071 additions and 0 deletions

View File

@ -0,0 +1,395 @@
import time
import random
import csv
import sys
sys.setrecursionlimit(100000)
def ll_insert(head, name, phone):
new_node = {'name': name, 'phone': phone, 'next': None}
if head is None:
return new_node
curr = head
prev = None
while curr:
if curr['name'] == name:
curr['phone'] = phone
return head
prev = curr
curr = curr['next']
prev['next'] = new_node
return head
def ll_find(head, name):
curr = head
while curr:
if curr['name'] == name:
return curr['phone']
curr = curr['next']
return None
def ll_delete(head, name):
if head is None:
return None
if head['name'] == name:
return head['next']
curr = head
while curr['next']:
if curr['next']['name'] == name:
curr['next'] = curr['next']['next']
return head
curr = curr['next']
return head
def ll_list_all(head):
records = []
curr = head
while curr:
records.append((curr['name'], curr['phone']))
curr = curr['next']
records.sort(key=lambda x: x[0])
return records
def hash_function(name, table_size):
return sum(ord(c) for c in name) % table_size
def ht_create(size=1000):
return [None] * size
def ht_insert(buckets, name, phone):
index = hash_function(name, len(buckets))
buckets[index] = ll_insert(buckets[index], name, phone)
def ht_find(buckets, name):
index = hash_function(name, len(buckets))
return ll_find(buckets[index], name)
def ht_delete(buckets, name):
index = hash_function(name, len(buckets))
buckets[index] = ll_delete(buckets[index], name)
def ht_list_all(buckets):
records = []
for head in buckets:
curr = head
while curr:
records.append((curr['name'], curr['phone']))
curr = curr['next']
records.sort(key=lambda x: x[0])
return records
def bst_insert_iterative(root, name, phone):
new_node = {'name': name, 'phone': phone, 'left': None, 'right': None}
if root is None:
return new_node
curr = root
while True:
if name < curr['name']:
if curr['left'] is None:
curr['left'] = new_node
break
curr = curr['left']
elif name > curr['name']:
if curr['right'] is None:
curr['right'] = new_node
break
curr = curr['right']
else:
curr['phone'] = phone
break
return root
def bst_find_iterative(root, name):
curr = root
while curr:
if name == curr['name']:
return curr['phone']
elif name < curr['name']:
curr = curr['left']
else:
curr = curr['right']
return None
def bst_find_min(node):
while node and node['left']:
node = node['left']
return node
def bst_delete_iterative(root, name):
if root is None:
return None
if name < root['name']:
root['left'] = bst_delete_iterative(root['left'], name)
elif name > root['name']:
root['right'] = bst_delete_iterative(root['right'], name)
else:
if root['left'] is None:
return root['right']
elif root['right'] is None:
return root['left']
parent = root
successor = root['right']
while successor['left']:
parent = successor
successor = successor['left']
root['name'] = successor['name']
root['phone'] = successor['phone']
if parent == root:
parent['right'] = successor['right']
else:
parent['left'] = successor['right']
return root
def bst_list_all(root):
result = []
stack = []
curr = root
while stack or curr:
while curr:
stack.append(curr)
curr = curr['left']
curr = stack.pop()
result.append((curr['name'], curr['phone']))
curr = curr['right']
return result
def generate_test_data(N=10000):
names = [f"User_{i:05d}" for i in range(N)]
phones = [f"+7-999-{random.randint(1000000, 9999999)}" for _ in range(N)]
records = list(zip(names, phones))
records_shuffled = records.copy()
random.shuffle(records_shuffled)
records_sorted = sorted(records, key=lambda x: x[0])
return records_shuffled, records_sorted
def measure_insertion(structure_type, records, ht_size=1000):
if structure_type == "LinkedList":
head = None
start = time.perf_counter()
for name, phone in records:
head = ll_insert(head, name, phone)
end = time.perf_counter()
return head, (end - start)
elif structure_type == "HashTable":
buckets = ht_create(ht_size)
start = time.perf_counter()
for name, phone in records:
ht_insert(buckets, name, phone)
end = time.perf_counter()
return buckets, (end - start)
elif structure_type == "BST":
root = None
start = time.perf_counter()
for name, phone in records:
root = bst_insert_iterative(root, name, phone)
end = time.perf_counter()
return root, (end - start)
def measure_search(data_structure, structure_type, existing_names, non_existing_names):
start = time.perf_counter()
for name in existing_names:
if structure_type == "LinkedList":
ll_find(data_structure, name)
elif structure_type == "HashTable":
ht_find(data_structure, name)
elif structure_type == "BST":
bst_find_iterative(data_structure, name)
for name in non_existing_names:
if structure_type == "LinkedList":
ll_find(data_structure, name)
elif structure_type == "HashTable":
ht_find(data_structure, name)
elif structure_type == "BST":
bst_find_iterative(data_structure, name)
end = time.perf_counter()
return end - start
def measure_deletion(data_structure, structure_type, names_to_delete):
start = time.perf_counter()
for name in names_to_delete:
if structure_type == "LinkedList":
data_structure = ll_delete(data_structure, name)
elif structure_type == "HashTable":
ht_delete(data_structure, name)
elif structure_type == "BST":
data_structure = bst_delete_iterative(data_structure, name)
end = time.perf_counter()
return data_structure, (end - start)
def run_experiment(N=5000, repeats=5):
print(f"Генерация тестовых данных (N={N})...")
records_shuffled, records_sorted = generate_test_data(N)
existing_names = [name for name, _ in random.sample(records_shuffled, min(100, N))]
non_existing_names = [f"None_{i}" for i in range(10)]
delete_names = [name for name, _ in random.sample(records_shuffled, min(50, N))]
results = []
structures = ["LinkedList", "HashTable", "BST"]
modes = ["случайный", "отсортированный"]
for struct in structures:
for mode in modes:
records = records_shuffled if mode == "случайный" else records_sorted
print(f"\nТестирование: {struct}, режим: {mode}")
insertion_times = []
search_times = []
deletion_times = []
for rep in range(repeats):
print(f" Повторение {rep+1}/{repeats}...")
data_structure, insert_time = measure_insertion(struct, records)
insertion_times.append(insert_time)
search_time = measure_search(data_structure, struct, existing_names, non_existing_names)
search_times.append(search_time)
data_structure, delete_time = measure_deletion(data_structure, struct, delete_names)
deletion_times.append(delete_time)
avg_insert = sum(insertion_times) / repeats
avg_search = sum(search_times) / repeats
avg_delete = sum(deletion_times) / repeats
results.append({
"structure": struct,
"mode": mode,
"insertion_avg": avg_insert,
"insertion_all": insertion_times,
"search_avg": avg_search,
"search_all": search_times,
"deletion_avg": avg_delete,
"deletion_all": deletion_times
})
print(f" Вставка: {avg_insert:.6f} сек (замеры: {[f'{t:.6f}' for t in insertion_times]})")
print(f" Поиск: {avg_search:.6f} сек (замеры: {[f'{t:.6f}' for t in search_times]})")
print(f" Удаление: {avg_delete:.6f} сек (замеры: {[f'{t:.6f}' for t in deletion_times]})")
return results
def save_results_to_csv(results, filename="results.csv"):
with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(["Структура", "Режим", "Операция", "Повторение", "Время (сек)"])
for res in results:
struct = res["structure"]
mode = res["mode"]
for i, t in enumerate(res["insertion_all"]):
writer.writerow([struct, mode, "вставка", i+1, t])
writer.writerow([struct, mode, "вставка", "СРЕДНЕЕ", res["insertion_avg"]])
for i, t in enumerate(res["search_all"]):
writer.writerow([struct, mode, "поиск", i+1, t])
writer.writerow([struct, mode, "поиск", "СРЕДНЕЕ", res["search_avg"]])
for i, t in enumerate(res["deletion_all"]):
writer.writerow([struct, mode, "удаление", i+1, t])
writer.writerow([struct, mode, "удаление", "СРЕДНЕЕ", res["deletion_avg"]])
print(f"\nРезультаты сохранены в {filename}")
def print_summary_table(results):
print("\n" + "="*80)
print("СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ (среднее время в секундах)")
print("="*80)
print(f"{'Структура':<15} {'Режим':<12} {'Вставка':<12} {'Поиск (110)':<12} {'Удаление (50)':<12}")
print("-"*80)
for res in results:
print(f"{res['structure']:<15} {res['mode']:<12} {res['insertion_avg']:<12.6f} "
f"{res['search_avg']:<12.6f} {res['deletion_avg']:<12.6f}")
print("\n" + "="*80)
print("АНАЛИЗ ДЕГРАДАЦИИ BST")
print("="*80)
bst_random = next(r for r in results if r['structure'] == "BST" and r['mode'] == "случайный")
bst_sorted = next(r for r in results if r['structure'] == "BST" and r['mode'] == "отсортированный")
degradation = bst_sorted['insertion_avg'] / bst_random['insertion_avg']
print(f"BST: отсортированные данные в {degradation:.1f} раз медленнее случайных")
print("Причина: вырождение дерева в линейный связный список (O(n) вместо O(log n))")
if __name__ == "__main__":
print("="*80)
print("ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ СТРУКТУР ДАННЫХ ДЛЯ ТЕЛЕФОННОГО СПРАВОЧНИКА")
print("="*80)
results = run_experiment(N=5000, repeats=5)
save_results_to_csv(results)
print_summary_table(results)
print("\n" + "="*80)
print("ВЫВОДЫ И РЕКОМЕНДАЦИИ")
print("="*80)
print("""
1. Хеш-таблица:
Лучшая производительность для операций поиска и вставки (O(1) в среднем)
Не чувствительна к порядку входных данных
Требует память под массив бакетов
Не поддерживает естественный порядок (нужна сортировка)
Идеально для справочников с частым поиском
2. Двоичное дерево поиска:
Естественная сортировка (in-order обход)
Хорошая производительность на случайных данных (O(log n))
Сильная деградация на отсортированных данных (O(n))
Рекурсивные операции требуют больше памяти
Хорошо для задач, где нужен отсортированный вывод
3. Связный список:
Простота реализации
Медленный поиск и удаление (O(n))
Неэффективен для больших объёмов данных
Применим только для очень маленьких справочников
РЕКОМЕНДАЦИИ ДЛЯ РЕАЛЬНЫХ ЗАДАЧ:
Частый поиск, редкие вставки -> ХЕШ-ТАБЛИЦА
Нужен отсортированный вывод -> ДЕРЕВО (с балансировкой)
Очень маленький справочник (<100 записей) -> СПИСОК
В реальных БД -> хеш-таблица + B-деревья
""")
print("\n" + "="*80)
print("ДОПОЛНИТЕЛЬНЫЙ АНАЛИЗ")
print("="*80)
for struct in ["LinkedList", "HashTable", "BST"]:
res_random = next(r for r in results if r['structure'] == struct and r['mode'] == "случайный")
print(f"{struct:12} поиск 110 записей: {res_random['search_avg']:.6f} сек")
ll_random = next(r for r in results if r['structure'] == "LinkedList" and r['mode'] == "случайный")
ll_sorted = next(r for r in results if r['structure'] == "LinkedList" and r['mode'] == "отсортированный")
print(f"\nСвязный список: деградация {ll_sorted['insertion_avg'] / ll_random['insertion_avg']:.2f}х")

View File

@ -0,0 +1,109 @@
Структура,Режим,Операция,Повторение,Время (сек)
LinkedList,случайный,вставка,1,0.7588584999975865
LinkedList,случайный,вставка,2,0.7769573999976274
LinkedList,случайный,вставка,3,0.7729451000013796
LinkedList,случайный,вставка,4,0.7535389000004216
LinkedList,случайный,вставка,5,0.758188899999368
LinkedList,случайный,вставка,СРЕДНЕЕ,0.7640977599992766
LinkedList,случайный,поиск,1,0.023368899997876724
LinkedList,случайный,поиск,2,0.023365799999737646
LinkedList,случайный,поиск,3,0.02325380000183941
LinkedList,случайный,поиск,4,0.02314950000072713
LinkedList,случайный,поиск,5,0.023074000000633532
LinkedList,случайный,поиск,СРЕДНЕЕ,0.023242400000162887
LinkedList,случайный,удаление,1,0.014257400001952192
LinkedList,случайный,удаление,2,0.01445149999926798
LinkedList,случайный,удаление,3,0.014200800000253366
LinkedList,случайный,удаление,4,0.013934900001913775
LinkedList,случайный,удаление,5,0.013907599997764919
LinkedList,случайный,удаление,СРЕДНЕЕ,0.014150440000230446
LinkedList,отсортированный,вставка,1,0.7295501999979024
LinkedList,отсортированный,вставка,2,0.733855800001038
LinkedList,отсортированный,вставка,3,0.9446520999990753
LinkedList,отсортированный,вставка,4,0.8595158000025549
LinkedList,отсортированный,вставка,5,0.7685707000018738
LinkedList,отсортированный,вставка,СРЕДНЕЕ,0.8072289200004888
LinkedList,отсортированный,поиск,1,0.022782500000175787
LinkedList,отсортированный,поиск,2,0.022431400000641588
LinkedList,отсортированный,поиск,3,0.029615100000228267
LinkedList,отсортированный,поиск,4,0.026672600000893
LinkedList,отсортированный,поиск,5,0.02327099999820348
LinkedList,отсортированный,поиск,СРЕДНЕЕ,0.024954520000028423
LinkedList,отсортированный,удаление,1,0.015000699997472111
LinkedList,отсортированный,удаление,2,0.014776500000152737
LinkedList,отсортированный,удаление,3,0.019435000001976732
LinkedList,отсортированный,удаление,4,0.0165975999989314
LinkedList,отсортированный,удаление,5,0.015432599997438956
LinkedList,отсортированный,удаление,СРЕДНЕЕ,0.016248479999194387
HashTable,случайный,вставка,1,0.06989740000062739
HashTable,случайный,вставка,2,0.06307899999956135
HashTable,случайный,вставка,3,0.06795570000031148
HashTable,случайный,вставка,4,0.0703618000006827
HashTable,случайный,вставка,5,0.06456320000143023
HashTable,случайный,вставка,СРЕДНЕЕ,0.06717142000052263
HashTable,случайный,поиск,1,0.0011812000011559576
HashTable,случайный,поиск,2,0.0011990000020887237
HashTable,случайный,поиск,3,0.0014205000006768387
HashTable,случайный,поиск,4,0.0018674999992072117
HashTable,случайный,поиск,5,0.0012002000003121793
HashTable,случайный,поиск,СРЕДНЕЕ,0.001373680000688182
HashTable,случайный,удаление,1,0.0008298000029753894
HashTable,случайный,удаление,2,0.0008296999985759612
HashTable,случайный,удаление,3,0.0010115000004589092
HashTable,случайный,удаление,4,0.0009790000003704336
HashTable,случайный,удаление,5,0.0008373999989998993
HashTable,случайный,удаление,СРЕДНЕЕ,0.0008974800002761185
HashTable,отсортированный,вставка,1,0.06598000000303728
HashTable,отсортированный,вставка,2,0.06413769999926444
HashTable,отсортированный,вставка,3,0.14105009999912
HashTable,отсортированный,вставка,4,0.07664300000033109
HashTable,отсортированный,вставка,5,0.06128300000273157
HashTable,отсортированный,вставка,СРЕДНЕЕ,0.08181876000089687
HashTable,отсортированный,поиск,1,0.001160599997092504
HashTable,отсортированный,поиск,2,0.004666700002417201
HashTable,отсортированный,поиск,3,0.00214869999763323
HashTable,отсортированный,поиск,4,0.0016174000011233147
HashTable,отсортированный,поиск,5,0.0013016000011703
HashTable,отсортированный,поиск,СРЕДНЕЕ,0.00217899999988731
HashTable,отсортированный,удаление,1,0.0008883999980753288
HashTable,отсортированный,удаление,2,0.002087799999571871
HashTable,отсортированный,удаление,3,0.001078500001312932
HashTable,отсортированный,удаление,4,0.0009227000009559561
HashTable,отсортированный,удаление,5,0.000898300000699237
HashTable,отсортированный,удаление,СРЕДНЕЕ,0.001175140000123065
BST,случайный,вставка,1,0.01702110000042012
BST,случайный,вставка,2,0.014504000002489192
BST,случайный,вставка,3,0.013862499999959255
BST,случайный,вставка,4,0.01633149999906891
BST,случайный,вставка,5,0.015226100000290899
BST,случайный,вставка,СРЕДНЕЕ,0.015389040000445674
BST,случайный,поиск,1,0.000405700000555953
BST,случайный,поиск,2,0.0002680999969015829
BST,случайный,поиск,3,0.00024119999943650328
BST,случайный,поиск,4,0.00027720000070985407
BST,случайный,поиск,5,0.00030730000071343966
BST,случайный,поиск,СРЕДНЕЕ,0.0002998999996634666
BST,случайный,удаление,1,0.00024620000112918206
BST,случайный,удаление,2,0.00016830000095069408
BST,случайный,удаление,3,0.0001548000000184402
BST,случайный,удаление,4,0.00018679999993764795
BST,случайный,удаление,5,0.00022140000146464445
BST,случайный,удаление,СРЕДНЕЕ,0.00019550000070012175
BST,отсортированный,вставка,1,1.875409899999795
BST,отсортированный,вставка,2,1.8619785999981104
BST,отсортированный,вставка,3,1.8126238000004378
BST,отсортированный,вставка,4,1.8052959000015107
BST,отсортированный,вставка,5,1.7978389999989304
BST,отсортированный,вставка,СРЕДНЕЕ,1.830629439999757
BST,отсортированный,поиск,1,0.03237050000097952
BST,отсортированный,поиск,2,0.03320049999820185
BST,отсортированный,поиск,3,0.033564300003490644
BST,отсортированный,поиск,4,0.031019200003356673
BST,отсортированный,поиск,5,0.03037219999896479
BST,отсортированный,поиск,СРЕДНЕЕ,0.0321053400009987
BST,отсортированный,удаление,1,0.03479439999864553
BST,отсортированный,удаление,2,0.0337948000014876
BST,отсортированный,удаление,3,0.034445099998265505
BST,отсортированный,удаление,4,0.03366980000282638
BST,отсортированный,удаление,5,0.03821500000049127
BST,отсортированный,удаление,СРЕДНЕЕ,0.03498382000034326
1 Структура Режим Операция Повторение Время (сек)
2 LinkedList случайный вставка 1 0.7588584999975865
3 LinkedList случайный вставка 2 0.7769573999976274
4 LinkedList случайный вставка 3 0.7729451000013796
5 LinkedList случайный вставка 4 0.7535389000004216
6 LinkedList случайный вставка 5 0.758188899999368
7 LinkedList случайный вставка СРЕДНЕЕ 0.7640977599992766
8 LinkedList случайный поиск 1 0.023368899997876724
9 LinkedList случайный поиск 2 0.023365799999737646
10 LinkedList случайный поиск 3 0.02325380000183941
11 LinkedList случайный поиск 4 0.02314950000072713
12 LinkedList случайный поиск 5 0.023074000000633532
13 LinkedList случайный поиск СРЕДНЕЕ 0.023242400000162887
14 LinkedList случайный удаление 1 0.014257400001952192
15 LinkedList случайный удаление 2 0.01445149999926798
16 LinkedList случайный удаление 3 0.014200800000253366
17 LinkedList случайный удаление 4 0.013934900001913775
18 LinkedList случайный удаление 5 0.013907599997764919
19 LinkedList случайный удаление СРЕДНЕЕ 0.014150440000230446
20 LinkedList отсортированный вставка 1 0.7295501999979024
21 LinkedList отсортированный вставка 2 0.733855800001038
22 LinkedList отсортированный вставка 3 0.9446520999990753
23 LinkedList отсортированный вставка 4 0.8595158000025549
24 LinkedList отсортированный вставка 5 0.7685707000018738
25 LinkedList отсортированный вставка СРЕДНЕЕ 0.8072289200004888
26 LinkedList отсортированный поиск 1 0.022782500000175787
27 LinkedList отсортированный поиск 2 0.022431400000641588
28 LinkedList отсортированный поиск 3 0.029615100000228267
29 LinkedList отсортированный поиск 4 0.026672600000893
30 LinkedList отсортированный поиск 5 0.02327099999820348
31 LinkedList отсортированный поиск СРЕДНЕЕ 0.024954520000028423
32 LinkedList отсортированный удаление 1 0.015000699997472111
33 LinkedList отсортированный удаление 2 0.014776500000152737
34 LinkedList отсортированный удаление 3 0.019435000001976732
35 LinkedList отсортированный удаление 4 0.0165975999989314
36 LinkedList отсортированный удаление 5 0.015432599997438956
37 LinkedList отсортированный удаление СРЕДНЕЕ 0.016248479999194387
38 HashTable случайный вставка 1 0.06989740000062739
39 HashTable случайный вставка 2 0.06307899999956135
40 HashTable случайный вставка 3 0.06795570000031148
41 HashTable случайный вставка 4 0.0703618000006827
42 HashTable случайный вставка 5 0.06456320000143023
43 HashTable случайный вставка СРЕДНЕЕ 0.06717142000052263
44 HashTable случайный поиск 1 0.0011812000011559576
45 HashTable случайный поиск 2 0.0011990000020887237
46 HashTable случайный поиск 3 0.0014205000006768387
47 HashTable случайный поиск 4 0.0018674999992072117
48 HashTable случайный поиск 5 0.0012002000003121793
49 HashTable случайный поиск СРЕДНЕЕ 0.001373680000688182
50 HashTable случайный удаление 1 0.0008298000029753894
51 HashTable случайный удаление 2 0.0008296999985759612
52 HashTable случайный удаление 3 0.0010115000004589092
53 HashTable случайный удаление 4 0.0009790000003704336
54 HashTable случайный удаление 5 0.0008373999989998993
55 HashTable случайный удаление СРЕДНЕЕ 0.0008974800002761185
56 HashTable отсортированный вставка 1 0.06598000000303728
57 HashTable отсортированный вставка 2 0.06413769999926444
58 HashTable отсортированный вставка 3 0.14105009999912
59 HashTable отсортированный вставка 4 0.07664300000033109
60 HashTable отсортированный вставка 5 0.06128300000273157
61 HashTable отсортированный вставка СРЕДНЕЕ 0.08181876000089687
62 HashTable отсортированный поиск 1 0.001160599997092504
63 HashTable отсортированный поиск 2 0.004666700002417201
64 HashTable отсортированный поиск 3 0.00214869999763323
65 HashTable отсортированный поиск 4 0.0016174000011233147
66 HashTable отсортированный поиск 5 0.0013016000011703
67 HashTable отсортированный поиск СРЕДНЕЕ 0.00217899999988731
68 HashTable отсортированный удаление 1 0.0008883999980753288
69 HashTable отсортированный удаление 2 0.002087799999571871
70 HashTable отсортированный удаление 3 0.001078500001312932
71 HashTable отсортированный удаление 4 0.0009227000009559561
72 HashTable отсортированный удаление 5 0.000898300000699237
73 HashTable отсортированный удаление СРЕДНЕЕ 0.001175140000123065
74 BST случайный вставка 1 0.01702110000042012
75 BST случайный вставка 2 0.014504000002489192
76 BST случайный вставка 3 0.013862499999959255
77 BST случайный вставка 4 0.01633149999906891
78 BST случайный вставка 5 0.015226100000290899
79 BST случайный вставка СРЕДНЕЕ 0.015389040000445674
80 BST случайный поиск 1 0.000405700000555953
81 BST случайный поиск 2 0.0002680999969015829
82 BST случайный поиск 3 0.00024119999943650328
83 BST случайный поиск 4 0.00027720000070985407
84 BST случайный поиск 5 0.00030730000071343966
85 BST случайный поиск СРЕДНЕЕ 0.0002998999996634666
86 BST случайный удаление 1 0.00024620000112918206
87 BST случайный удаление 2 0.00016830000095069408
88 BST случайный удаление 3 0.0001548000000184402
89 BST случайный удаление 4 0.00018679999993764795
90 BST случайный удаление 5 0.00022140000146464445
91 BST случайный удаление СРЕДНЕЕ 0.00019550000070012175
92 BST отсортированный вставка 1 1.875409899999795
93 BST отсортированный вставка 2 1.8619785999981104
94 BST отсортированный вставка 3 1.8126238000004378
95 BST отсортированный вставка 4 1.8052959000015107
96 BST отсортированный вставка 5 1.7978389999989304
97 BST отсортированный вставка СРЕДНЕЕ 1.830629439999757
98 BST отсортированный поиск 1 0.03237050000097952
99 BST отсортированный поиск 2 0.03320049999820185
100 BST отсортированный поиск 3 0.033564300003490644
101 BST отсортированный поиск 4 0.031019200003356673
102 BST отсортированный поиск 5 0.03037219999896479
103 BST отсортированный поиск СРЕДНЕЕ 0.0321053400009987
104 BST отсортированный удаление 1 0.03479439999864553
105 BST отсортированный удаление 2 0.0337948000014876
106 BST отсортированный удаление 3 0.034445099998265505
107 BST отсортированный удаление 4 0.03366980000282638
108 BST отсортированный удаление 5 0.03821500000049127
109 BST отсортированный удаление СРЕДНЕЕ 0.03498382000034326

View File

@ -0,0 +1,546 @@
import heapq
from collections import deque
from abc import ABC, abstractmethod
import time
import csv
import os
from typing import List, Tuple, Optional, Dict, Set
class Cell:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
self.is_wall = False
self.is_start = False
self.is_exit = False
self.weight = 1
def is_passable(self) -> bool:
return not self.is_wall
class Maze:
def __init__(self, width: int = 0, height: int = 0):
self.width = width
self.height = height
self.cells: List[List[Cell]] = []
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]:
neighbors = []
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
for dx, dy in directions:
nx, ny = cell.x + dx, cell.y + dy
neighbor = self.get_cell(nx, ny)
if neighbor and neighbor.is_passable():
neighbors.append(neighbor)
return neighbors
class MazeBuilder(ABC):
@abstractmethod
def build_from_file(self, filename: str) -> Maze:
pass
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename: str) -> Maze:
with open(filename, 'r', encoding='utf-8') as f:
lines = f.readlines()
height = len(lines)
width = len(lines[0].strip()) if height > 0 else 0
maze = Maze(width, height)
maze.cells = [[Cell(x, y) for x in range(width)] for y in range(height)]
for y, line in enumerate(lines):
line = line.rstrip('\n')
for x, ch in enumerate(line):
if x < width:
cell = maze.cells[y][x]
if ch == '#':
cell.is_wall = True
elif ch == 'S':
cell.is_start = True
maze.start = cell
elif ch == 'E':
cell.is_exit = True
maze.exit = cell
elif ch.isdigit():
cell.weight = int(ch)
cell.is_wall = False
if not maze.start or not maze.exit:
raise ValueError("Лабиринт должен содержать старт (S) и выход (E)")
return maze
class PathFindingStrategy(ABC):
@abstractmethod
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
pass
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
queue = deque([start])
visited = {start}
parent = {start: None}
while queue:
current = queue.popleft()
if current == exit_cell:
return self.reconstruct_path(parent, start, exit_cell)
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
parent[neighbor] = current
queue.append(neighbor)
return []
def reconstruct_path(self, parent: Dict, start: Cell, exit_cell: Cell) -> List[Cell]:
path = []
current = exit_cell
while current is not None:
path.append(current)
current = parent[current]
path.reverse()
return path
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
stack = [(start, [start])]
visited = {start}
while stack:
current, path = stack.pop()
if current == exit_cell:
return path
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
stack.append((neighbor, path + [neighbor]))
return []
class AStarStrategy(PathFindingStrategy):
def heuristic(self, a: Cell, b: Cell) -> int:
return abs(a.x - b.x) + abs(a.y - b.y)
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
open_set = [(0, start)]
came_from = {}
g_score = {start: 0}
f_score = {start: self.heuristic(start, exit_cell)}
while open_set:
_, current = heapq.heappop(open_set)
if current == exit_cell:
return self.reconstruct_path(came_from, start, exit_cell)
for neighbor in maze.get_neighbors(current):
tentative_g = g_score[current] + neighbor.weight
if neighbor not in g_score or tentative_g < g_score[neighbor]:
came_from[neighbor] = current
g_score[neighbor] = tentative_g
f_score[neighbor] = tentative_g + self.heuristic(neighbor, exit_cell)
heapq.heappush(open_set, (f_score[neighbor], neighbor))
return []
def reconstruct_path(self, came_from: Dict, start: Cell, exit_cell: Cell) -> List[Cell]:
path = []
current = exit_cell
while current != start:
path.append(current)
current = came_from[current]
path.append(start)
path.reverse()
return path
class DijkstraStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
pq = [(0, start)]
distances = {start: 0}
parent = {start: None}
while pq:
dist, current = heapq.heappop(pq)
if current == exit_cell:
return self.reconstruct_path(parent, start, exit_cell)
if dist > distances[current]:
continue
for neighbor in maze.get_neighbors(current):
new_dist = dist + neighbor.weight
if neighbor not in distances or new_dist < distances[neighbor]:
distances[neighbor] = new_dist
parent[neighbor] = current
heapq.heappush(pq, (new_dist, neighbor))
return []
def reconstruct_path(self, parent: Dict, start: Cell, exit_cell: Cell) -> List[Cell]:
path = []
current = exit_cell
while current is not None:
path.append(current)
current = parent[current]
path.reverse()
return path
class SearchStats:
def __init__(self, time_ms: float, visited_cells: int, path_length: int, path: List[Cell] = None):
self.time_ms = time_ms
self.visited_cells = visited_cells
self.path_length = path_length
self.path = path
class MazeSolver:
def __init__(self, maze: Maze, strategy: PathFindingStrategy):
self.maze = maze
self.strategy = strategy
self.observers = []
def set_strategy(self, strategy: PathFindingStrategy):
self.strategy = strategy
def add_observer(self, observer):
self.observers.append(observer)
def notify_observers(self, event: str, data=None):
for observer in self.observers:
observer.update(event, data)
def solve(self) -> SearchStats:
self.notify_observers("search_started")
start_time = time.perf_counter()
path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
end_time = time.perf_counter()
time_ms = (end_time - start_time) * 1000
visited_cells = self.count_visited_cells()
path_length = len(path)
stats = SearchStats(time_ms, visited_cells, path_length, path)
self.notify_observers("search_finished", stats)
return stats
def count_visited_cells(self) -> int:
if isinstance(self.strategy, BFSStrategy):
return len(self.bfs_visited)
elif isinstance(self.strategy, DFSStrategy):
return len(self.dfs_visited)
return 0
class Observer(ABC):
@abstractmethod
def update(self, event: str, data=None):
pass
class ConsoleView(Observer):
def __init__(self):
self.maze = None
self.current_path = None
def set_maze(self, maze: Maze):
self.maze = maze
def update(self, event: str, data=None):
if event == "search_finished":
self.display_path(data.path)
elif event == "search_started":
print("\nПоиск пути начат...")
def display_path(self, path: List[Cell]):
if not path:
print("\nПуть не найден!")
return
print(f"\nПуть найден! Длина: {len(path)} шагов")
self.render(path)
def render(self, path: List[Cell] = None):
if not self.maze:
return
path_set = set(path) if path else set()
for y in range(self.maze.height):
for x in range(self.maze.width):
cell = self.maze.get_cell(x, y)
if cell in path_set:
print('*', end='')
elif cell.is_start:
print('S', end='')
elif cell.is_exit:
print('E', end='')
elif cell.is_wall:
print('#', end='')
else:
print(' ', end='')
print()
class Player:
def __init__(self, start_cell: Cell):
self.current = start_cell
self.history = []
def move_to(self, cell: Cell):
if cell and cell.is_passable():
self.history.append(self.current)
self.current = cell
return True
return False
def undo(self):
if self.history:
self.current = self.history.pop()
return True
return False
class Command(ABC):
@abstractmethod
def execute(self) -> bool:
pass
@abstractmethod
def undo(self):
pass
class MoveCommand(Command):
def __init__(self, player: Player, direction: str, maze: Maze):
self.player = player
self.direction = direction
self.maze = maze
self.previous_position = None
def execute(self) -> bool:
self.previous_position = self.player.current
dx, dy = 0, 0
if self.direction == 'W':
dy = -1
elif self.direction == 'S':
dy = 1
elif self.direction == 'A':
dx = -1
elif self.direction == 'D':
dx = 1
new_cell = self.maze.get_cell(self.player.current.x + dx, self.player.current.y + dy)
if new_cell and new_cell.is_passable():
return self.player.move_to(new_cell)
return False
def undo(self):
if self.previous_position:
self.player.current = self.previous_position
def generate_test_mazes():
test_mazes = {
"tiny": [
"########",
"#S #",
"# #### #",
"# E #",
"########"
],
"empty": [
"########",
"#S #",
"# #",
"# E#",
"########"
],
"no_exit": [
"########",
"#S #",
"# #### #",
"# # #",
"########"
],
"weighted": [
"########",
"#S2 #",
"# 5#3 #",
"# 2 E #",
"########"
]
}
os.makedirs("mazes", exist_ok=True)
for name, maze_data in test_mazes.items():
filename = f"mazes/{name}.txt"
with open(filename, 'w', encoding='utf-8') as f:
f.write('\n'.join(maze_data))
print(f"Создан лабиринт: {filename}")
def run_experiments():
strategies = {
"BFS": BFSStrategy(),
"DFS": DFSStrategy(),
"A*": AStarStrategy(),
"Dijkstra": DijkstraStrategy()
}
mazes_list = ["tiny", "empty", "no_exit", "weighted"]
results = []
for maze_name in mazes_list:
filename = f"mazes/{maze_name}.txt"
try:
builder = TextFileMazeBuilder()
maze = builder.build_from_file(filename)
print(f"\nТестирование лабиринта: {maze_name}")
for strategy_name, strategy in strategies.items():
print(f" Стратегия: {strategy_name}")
times = []
visited_counts = []
path_lengths = []
for i in range(5):
solver = MazeSolver(maze, strategy)
stats = solver.solve()
times.append(stats.time_ms)
visited_counts.append(stats.visited_cells if stats.visited_cells else 0)
path_lengths.append(stats.path_length)
avg_time = sum(times) / len(times)
avg_visited = sum(visited_counts) / len(visited_counts)
avg_path_len = sum(path_lengths) / len(path_lengths)
results.append([
maze_name, strategy_name, avg_time, avg_visited, avg_path_len
])
print(f" Время: {avg_time:.3f} мс, Посещено: {avg_visited:.1f}, Путь: {avg_path_len:.1f}")
except Exception as e:
print(f"Ошибка загрузки {maze_name}: {e}")
with open("maze_results.csv", 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(["Лабиринт", "Стратегия", "Время_мс", "Посещено_клеток", "Длина_пути"])
writer.writerows(results)
print("\nРезультаты сохранены в maze_results.csv")
def interactive_mode():
print("\n=== ИНТЕРАКТИВНЫЙ РЕЖИМ ===")
filename = input("Введите имя файла с лабиринтом: ")
try:
builder = TextFileMazeBuilder()
maze = builder.build_from_file(filename)
print("\nВыберите стратегию:")
print("1. BFS (кратчайший путь)")
print("2. DFS (быстрый, не обязательно кратчайший)")
print("3. A* (оптимальный с эвристикой)")
print("4. Dijkstra (для взвешенных лабиринтов)")
choice = input("Ваш выбор (1-4): ")
strategies = {
'1': BFSStrategy(),
'2': DFSStrategy(),
'3': AStarStrategy(),
'4': DijkstraStrategy()
}
if choice not in strategies:
print("Неверный выбор!")
return
solver = MazeSolver(maze, strategies[choice])
view = ConsoleView()
view.set_maze(maze)
solver.add_observer(view)
stats = solver.solve()
print(f"\nСтатистика:")
print(f"Время выполнения: {stats.time_ms:.3f} мс")
print(f"Длина пути: {stats.path_length}")
input("\nНажмите Enter для ручного режима...")
player = Player(maze.start)
while player.current != maze.exit:
os.system('cls' if os.name == 'nt' else 'clear')
view.render()
print(f"\nТекущая позиция: ({player.current.x}, {player.current.y})")
print("Управление: W/A/S/D для движения, Z для отмены, Q для выхода")
cmd = input("> ").upper()
if cmd == 'Q':
break
elif cmd == 'Z':
command = MoveCommand(player, 'U', maze)
command.undo()
print("Отмена последнего хода")
elif cmd in ['W', 'A', 'S', 'D']:
command = MoveCommand(player, cmd, maze)
if command.execute():
print("Перемещение выполнено")
else:
print("Нельзя пройти в этом направлении")
else:
print("Неизвестная команда")
if player.current == maze.exit:
print("\nПОЗДРАВЛЯЮ! ВЫ НАШЛИ ВЫХОД!")
break
except Exception as e:
print(f"Ошибка: {e}")
def main():
print("="*80)
print("ПОИСК ВЫХОДА ИЗ ЛАБИРИНТА")
print("Применённые паттерны: Builder, Strategy, Observer, Command")
print("="*80)
generate_test_mazes()
print("\n1. Запустить эксперименты")
print("2. Интерактивный режим")
choice = input("\nВыберите режим (1-2): ")
if choice == '1':
run_experiments()
elif choice == '2':
interactive_mode()
else:
print("Неверный выбор!")
if __name__ == "__main__":
main()

View File

@ -0,0 +1 @@
Лабиринт,Стратегия,Время_мсосещено_клеток,Длина_пути
1 Лабиринт Стратегия Время_мс Посещено_клеток Длина_пути

View File

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

View File

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

View File

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

View File

@ -0,0 +1,5 @@
########
#S2 #
# 5#3 #
# 2 E #
########