forked from UNN/2026-rff_mp
Merge pull request '[2]Laba2' (#212) from komissarovgo/2026-rff_mp:komissarovgo2 into develop
Reviewed-on: UNN/2026-rff_mp#212
This commit is contained in:
commit
7e5d4cad71
498
komissarovgo/docs/data/CodePhoneBook.py
Normal file
498
komissarovgo/docs/data/CodePhoneBook.py
Normal file
|
|
@ -0,0 +1,498 @@
|
|||
import time
|
||||
import random
|
||||
import csv
|
||||
import os
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import sys
|
||||
sys.setrecursionlimit(20000)
|
||||
|
||||
# 1. LinkedList
|
||||
|
||||
def ll_insert(head, name, phone):
|
||||
|
||||
new_node = {'name': name, 'phone': phone, 'next': None}
|
||||
|
||||
if head is None:
|
||||
return new_node
|
||||
|
||||
if head['name'] == name:
|
||||
head['phone'] = phone
|
||||
return head
|
||||
|
||||
current = head
|
||||
while current['next'] is not None:
|
||||
if current['next']['name'] == name:
|
||||
current['next']['phone'] = phone
|
||||
return head
|
||||
current = current['next']
|
||||
|
||||
current['next'] = new_node
|
||||
return head
|
||||
|
||||
|
||||
def ll_find(head, name):
|
||||
|
||||
current = head
|
||||
while current is not None:
|
||||
if current['name'] == name:
|
||||
return current['phone']
|
||||
current = current['next']
|
||||
return None
|
||||
|
||||
|
||||
def ll_delete(head, name):
|
||||
|
||||
if head is None:
|
||||
return None
|
||||
|
||||
if head['name'] == name:
|
||||
return head['next']
|
||||
|
||||
current = head
|
||||
while current['next'] is not None:
|
||||
if current['next']['name'] == name:
|
||||
current['next'] = current['next']['next']
|
||||
return head
|
||||
current = current['next']
|
||||
|
||||
return head
|
||||
|
||||
|
||||
def ll_list_all(head):
|
||||
|
||||
records = []
|
||||
current = head
|
||||
while current is not None:
|
||||
records.append((current['name'], current['phone']))
|
||||
current = current['next']
|
||||
records.sort(key=lambda x: x[0])
|
||||
return records
|
||||
|
||||
|
||||
|
||||
# 2. Hash Function
|
||||
|
||||
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):
|
||||
size = len(buckets)
|
||||
index = hash_function(name, size)
|
||||
buckets[index] = ll_insert(buckets[index], name, phone)
|
||||
|
||||
|
||||
def ht_find(buckets, name):
|
||||
size = len(buckets)
|
||||
index = hash_function(name, size)
|
||||
return ll_find(buckets[index], name)
|
||||
|
||||
|
||||
def ht_delete(buckets, name):
|
||||
size = len(buckets)
|
||||
index = hash_function(name, size)
|
||||
buckets[index] = ll_delete(buckets[index], name)
|
||||
|
||||
|
||||
def ht_list_all(buckets):
|
||||
records = []
|
||||
for bucket in buckets:
|
||||
current = bucket
|
||||
while current is not None:
|
||||
records.append((current['name'], current['phone']))
|
||||
current = current['next']
|
||||
records.sort(key=lambda x: x[0])
|
||||
return records
|
||||
|
||||
|
||||
|
||||
#3. Tree function
|
||||
|
||||
|
||||
def bst_insert(root, name, phone):
|
||||
|
||||
if root is None:
|
||||
return {'name': name, 'phone': phone, 'left': None, 'right': None}
|
||||
|
||||
if name < root['name']:
|
||||
root['left'] = bst_insert(root['left'], name, phone)
|
||||
elif name > root['name']:
|
||||
root['right'] = bst_insert(root['right'], name, phone)
|
||||
else:
|
||||
root['phone'] = phone
|
||||
|
||||
return root
|
||||
|
||||
|
||||
def bst_find(root, name):
|
||||
|
||||
current = root
|
||||
while current is not None:
|
||||
if name == current['name']:
|
||||
return current['phone']
|
||||
elif name < current['name']:
|
||||
current = current['left']
|
||||
else:
|
||||
current = current['right']
|
||||
return None
|
||||
|
||||
|
||||
def bst_find_min(node):
|
||||
|
||||
current = node
|
||||
while current['left'] is not None:
|
||||
current = current['left']
|
||||
return current
|
||||
|
||||
|
||||
def bst_delete(root, name):
|
||||
|
||||
if root is None:
|
||||
return None
|
||||
|
||||
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']
|
||||
elif root['right'] is None:
|
||||
return root['left']
|
||||
|
||||
min_node = bst_find_min(root['right'])
|
||||
root['name'] = min_node['name']
|
||||
root['phone'] = min_node['phone']
|
||||
root['right'] = bst_delete(root['right'], min_node['name'])
|
||||
|
||||
return root
|
||||
|
||||
|
||||
def bst_list_all(root):
|
||||
|
||||
records = []
|
||||
|
||||
def inorder_traversal(node):
|
||||
if node is not None:
|
||||
inorder_traversal(node['left'])
|
||||
records.append((node['name'], node['phone']))
|
||||
inorder_traversal(node['right'])
|
||||
|
||||
inorder_traversal(root)
|
||||
return records
|
||||
|
||||
|
||||
|
||||
#EXPERIMENTAL PART
|
||||
|
||||
# 1. Test data generation
|
||||
|
||||
def generate_records(count=10000):
|
||||
|
||||
records = []
|
||||
for i in range(count):
|
||||
name = f"User_{i:05d}"
|
||||
phone = f"+7-{random.randint(100,999)}-{random.randint(100,999)}-{random.randint(1000,9999)}"
|
||||
records.append((name, phone))
|
||||
|
||||
shuffled = records.copy()
|
||||
random.shuffle(shuffled)
|
||||
sorted_records = sorted(records, key=lambda x: x[0])
|
||||
|
||||
return shuffled, sorted_records
|
||||
|
||||
|
||||
|
||||
# 2. Timing
|
||||
|
||||
def measure_insertion(structure_name, records):
|
||||
|
||||
times = []
|
||||
filled_structure = None
|
||||
|
||||
for run in range(5):
|
||||
if structure_name == "linked_list":
|
||||
structure = None
|
||||
elif structure_name == "hash_table":
|
||||
structure = ht_create(1000)
|
||||
elif structure_name == "bst":
|
||||
structure = None
|
||||
|
||||
start = time.perf_counter()
|
||||
|
||||
for name, phone in records:
|
||||
if structure_name == "linked_list":
|
||||
structure = ll_insert(structure, name, phone)
|
||||
elif structure_name == "hash_table":
|
||||
ht_insert(structure, name, phone)
|
||||
elif structure_name == "bst":
|
||||
structure = bst_insert(structure, name, phone)
|
||||
|
||||
end = time.perf_counter()
|
||||
times.append(end - start)
|
||||
|
||||
if run == 4:
|
||||
filled_structure = structure
|
||||
|
||||
return times, filled_structure
|
||||
|
||||
|
||||
def measure_search(structure_name, structure, search_names):
|
||||
|
||||
times = []
|
||||
|
||||
for run in range(5):
|
||||
start = time.perf_counter()
|
||||
|
||||
for name in search_names:
|
||||
if structure_name == "linked_list":
|
||||
ll_find(structure, name)
|
||||
elif structure_name == "hash_table":
|
||||
ht_find(structure, name)
|
||||
elif structure_name == "bst":
|
||||
bst_find(structure, name)
|
||||
|
||||
end = time.perf_counter()
|
||||
times.append(end - start)
|
||||
|
||||
return times
|
||||
|
||||
|
||||
def measure_deletion(structure_name, original_structure, delete_names):
|
||||
|
||||
times = []
|
||||
|
||||
for run in range(5):
|
||||
if structure_name == "linked_list":
|
||||
all_records = ll_list_all(original_structure)
|
||||
test_structure = None
|
||||
for name, phone in all_records:
|
||||
test_structure = ll_insert(test_structure, name, phone)
|
||||
|
||||
elif structure_name == "hash_table":
|
||||
all_records = ht_list_all(original_structure)
|
||||
test_structure = ht_create(1000)
|
||||
for name, phone in all_records:
|
||||
ht_insert(test_structure, name, phone)
|
||||
|
||||
elif structure_name == "bst":
|
||||
all_records = bst_list_all(original_structure)
|
||||
test_structure = None
|
||||
for name, phone in all_records:
|
||||
test_structure = bst_insert(test_structure, name, phone)
|
||||
|
||||
start = time.perf_counter()
|
||||
|
||||
for name in delete_names:
|
||||
if structure_name == "linked_list":
|
||||
test_structure = ll_delete(test_structure, name)
|
||||
elif structure_name == "hash_table":
|
||||
ht_delete(test_structure, name)
|
||||
elif structure_name == "bst":
|
||||
test_structure = bst_delete(test_structure, name)
|
||||
|
||||
end = time.perf_counter()
|
||||
times.append(end - start)
|
||||
|
||||
return times
|
||||
|
||||
|
||||
|
||||
# 3. Launch and save results
|
||||
|
||||
def run_experiment():
|
||||
|
||||
current_dir = os.path.dirname(__file__)
|
||||
docs_dir = os.path.dirname(current_dir)
|
||||
csv_file = os.path.join(docs_dir, "experiment_results.csv")
|
||||
|
||||
print("=" * 70)
|
||||
print("ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ СТРУКТУР ДАННЫХ")
|
||||
print("Телефонный справочник - 10000 записей")
|
||||
print("=" * 70)
|
||||
print(f"\n📁 Результаты будут сохранены в: {csv_file}")
|
||||
|
||||
print("\n1. Генерация тестовых данных...")
|
||||
shuffled_records, sorted_records = generate_records(10000)
|
||||
print(f" Сгенерировано 10000 записей")
|
||||
|
||||
existing_names = [shuffled_records[i][0] for i in random.sample(range(10000), 100)]
|
||||
nonexisting_names = [f"NotExist_{i}" for i in range(10)]
|
||||
search_names = existing_names + nonexisting_names
|
||||
delete_names = [shuffled_records[i][0] for i in random.sample(range(10000), 50)]
|
||||
|
||||
results = [["Структура", "Режим", "Операция",
|
||||
"Замер1(с)", "Замер2(с)", "Замер3(с)", "Замер4(с)", "Замер5(с)",
|
||||
"Среднее(с)"]]
|
||||
|
||||
for mode_name, records in [("случайный", shuffled_records),
|
||||
("отсортированный", sorted_records)]:
|
||||
|
||||
print(f"\n2. Тестирование режима: {mode_name}")
|
||||
print("-" * 50)
|
||||
|
||||
for struct_name in ["linked_list", "hash_table", "bst"]:
|
||||
print(f"\n {struct_name.upper()}:")
|
||||
|
||||
print(" Вставка 10000 записей...")
|
||||
insert_times, filled_struct = measure_insertion(struct_name, records)
|
||||
avg_insert = sum(insert_times) / 5
|
||||
print(f" Время: {avg_insert:.4f} сек (среднее)")
|
||||
|
||||
print(" Поиск 110 записей...")
|
||||
search_times = measure_search(struct_name, filled_struct, search_names)
|
||||
avg_search = sum(search_times) / 5
|
||||
print(f" Время: {avg_search:.4f} сек (среднее)")
|
||||
|
||||
print(" Удаление 50 записей...")
|
||||
delete_times = measure_deletion(struct_name, filled_struct, delete_names)
|
||||
avg_delete = sum(delete_times) / 5
|
||||
print(f" Время: {avg_delete:.4f} сек (среднее)")
|
||||
|
||||
results.append([struct_name, mode_name, "вставка"] + insert_times + [avg_insert])
|
||||
results.append([struct_name, mode_name, "поиск"] + search_times + [avg_search])
|
||||
results.append([struct_name, mode_name, "удаление"] + delete_times + [avg_delete])
|
||||
|
||||
print("\n3. Сохранение результатов...")
|
||||
with open(csv_file, "w", newline="", encoding="utf-8") as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerows(results)
|
||||
print(f" ✅ Результаты сохранены в: {csv_file}")
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ")
|
||||
print("=" * 70)
|
||||
print(f"{'Структура':<15} {'Режим':<12} {'Операция':<10} {'Среднее время (сек)':<20}")
|
||||
print("-" * 70)
|
||||
|
||||
for row in results[1:]:
|
||||
struct, mode, op, t1, t2, t3, t4, t5, avg = row
|
||||
print(f"{struct:<15} {mode:<12} {op:<10} {avg:<20.6f}")
|
||||
|
||||
return results, docs_dir
|
||||
|
||||
|
||||
|
||||
# 4. Graphics
|
||||
|
||||
def create_graphs(results, docs_dir):
|
||||
|
||||
print("\n4. Построение графиков...")
|
||||
|
||||
data = {}
|
||||
for row in results[1:]:
|
||||
struct = row[0]
|
||||
mode = row[1]
|
||||
op = row[2]
|
||||
avg = row[8]
|
||||
|
||||
if struct not in data:
|
||||
data[struct] = {}
|
||||
if mode not in data[struct]:
|
||||
data[struct][mode] = {}
|
||||
data[struct][mode][op] = avg
|
||||
|
||||
|
||||
struct_labels = {
|
||||
'linked_list': 'LinkedList',
|
||||
'hash_table': 'HashTable',
|
||||
'bst': 'BST'
|
||||
}
|
||||
|
||||
|
||||
colors = {
|
||||
'linked_list': '#3498db',
|
||||
'hash_table': '#2ecc71',
|
||||
'bst': '#e74c3c'
|
||||
}
|
||||
|
||||
|
||||
fig, axes = plt.subplots(1, 3, figsize=(15, 6))
|
||||
fig.suptitle('Сравнение производительности структур данных', fontsize=16, fontweight='bold')
|
||||
|
||||
operations = ['вставка', 'поиск', 'удаление']
|
||||
operation_titles = ['Вставка\n(10000 записей)', 'Поиск\n(110 запросов)', 'Удаление\n(50 записей)']
|
||||
modes = ['случайный', 'отсортированный']
|
||||
mode_labels = ['Случайный', 'Отсортированный']
|
||||
|
||||
for idx, (op, op_title) in enumerate(zip(operations, operation_titles)):
|
||||
ax = axes[idx]
|
||||
|
||||
# Позиции для групп столбцов
|
||||
x = np.arange(len(modes)) # [0, 1]
|
||||
width = 0.25 # ширина одного столбца
|
||||
multiplier = 0
|
||||
|
||||
for struct in ['linked_list', 'hash_table', 'bst']:
|
||||
values = [data[struct][mode][op] for mode in modes]
|
||||
offset = width * multiplier
|
||||
bars = ax.bar(x + offset, values, width,
|
||||
label=struct_labels[struct],
|
||||
color=colors[struct],
|
||||
edgecolor='black', linewidth=0.5)
|
||||
|
||||
|
||||
for bar, val in zip(bars, values):
|
||||
if val < 0.01:
|
||||
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + val*0.05,
|
||||
f'{val:.5f}', ha='center', va='bottom', fontsize=8, rotation=0)
|
||||
else:
|
||||
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + val*0.02,
|
||||
f'{val:.4f}', ha='center', va='bottom', fontsize=8, rotation=0)
|
||||
|
||||
multiplier += 1
|
||||
|
||||
|
||||
ax.set_title(op_title, fontsize=12, fontweight='bold')
|
||||
ax.set_ylabel('Время (секунды)', fontsize=10)
|
||||
ax.set_xlabel('Режим данных', fontsize=10)
|
||||
ax.set_xticks(x + width)
|
||||
ax.set_xticklabels(mode_labels)
|
||||
ax.legend(loc='upper left', fontsize=8)
|
||||
ax.grid(True, alpha=0.3, axis='y')
|
||||
|
||||
|
||||
all_values = [data[s][m][op] for s in ['linked_list', 'hash_table', 'bst'] for m in modes]
|
||||
if max(all_values) / min(all_values) > 100:
|
||||
ax.set_yscale('log')
|
||||
ax.set_ylabel('Время (секунды) - логарифмическая шкала', fontsize=9)
|
||||
|
||||
plt.tight_layout()
|
||||
graph_path = os.path.join(docs_dir, "performance_graphs.png")
|
||||
plt.savefig(graph_path, dpi=150, bbox_inches='tight')
|
||||
plt.close()
|
||||
print(f" ✅ Графики сохранены в: {graph_path}")
|
||||
|
||||
return graph_path
|
||||
|
||||
|
||||
|
||||
# 5. Main program
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
results, docs_dir = run_experiment()
|
||||
|
||||
|
||||
try:
|
||||
graph_file = create_graphs(results, docs_dir)
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("ЭКСПЕРИМЕНТ ЗАВЕРШЕН УСПЕШНО!")
|
||||
print("=" * 70)
|
||||
print("\n📂 СОЗДАННЫЕ ФАЙЛЫ:")
|
||||
print(f" 📊 Данные: {os.path.join(docs_dir, 'experiment_results.csv')}")
|
||||
print(f" 📈 Графики: {graph_file}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n⚠️ Ошибка при построении графиков: {e}")
|
||||
print(" Убедитесь, что установлен matplotlib: pip install matplotlib")
|
||||
print("\n" + "=" * 70)
|
||||
print("ЭКСПЕРИМЕНТ ЗАВЕРШЕН (без графиков)")
|
||||
print("=" * 70)
|
||||
print(f"\n📂 CSV файл сохранен: {os.path.join(docs_dir, 'experiment_results.csv')}")
|
||||
19
komissarovgo/docs/experiment_results.csv
Normal file
19
komissarovgo/docs/experiment_results.csv
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
Структура;Режим;Операция;Замер1(с);Замер2(с);Замер3(с);Замер4(с);Замер5(с);Среднее(с)
|
||||
linked_list;случайный;вставка;3.0067851000931114;2.9344012999208644;3.009651300031692;2.8879009999800473;2.9411771999439225;2.9559831799939276
|
||||
linked_list;случайный;поиск;0.024209000053815544;0.023271000012755394;0.023459300049580634;0.02291749999858439;0.023009900003671646;0.02337334002368152
|
||||
linked_list;случайный;удаление;0.012298299930989742;0.01275830005761236;0.011870200047269464;0.012219499913044274;0.013008400099352002;0.012430940009653568
|
||||
hash_table;случайный;вставка;0.17963590007275343;0.18678270000964403;0.17841749999206513;0.1837999999988824;0.17311319999862462;0.18034986001439393
|
||||
hash_table;случайный;поиск;0.0014747000532224774;0.0015627999091520905;0.0013960000360384583;0.001387899974361062;0.001381400041282177;0.001440560002811253
|
||||
hash_table;случайный;удаление;0.0009544000495225191;0.0009586999658495188;0.0010158000513911247;0.0010519999777898192;0.001128499978221953;0.001021880004554987
|
||||
bst;случайный;вставка;0.018539699958637357;0.017916599987074733;0.018017600057646632;0.017920599901117384;0.01831700000911951;0.018142299982719122
|
||||
bst;случайный;поиск;0.00027920003049075603;0.00013049994595348835;0.00012059998698532581;0.00011999998241662979;0.00011970009654760361;0.00015400000847876072
|
||||
bst;случайный;удаление;0.0400237999856472;0.03904950001742691;0.039472199976444244;0.0423756999662146;0.03944469999987632;0.040073179989121854
|
||||
linked_list;отсортированный;вставка;2.5939184998860583;2.554054999956861;2.5894857000093907;2.566357500036247;2.5988647000631317;2.580536279990338
|
||||
linked_list;отсортированный;поиск;0.018984199967235327;0.018922099960036576;0.02011869999114424;0.020203600055538118;0.02154539991170168;0.019954799977131187
|
||||
linked_list;отсортированный;удаление;0.012979999999515712;0.024571599904447794;0.026229599956423044;0.02633849997073412;0.026505499961785972;0.023325039958581328
|
||||
hash_table;отсортированный;вставка;0.3214672999456525;0.2974235999863595;0.35363279993180186;0.34583120001479983;0.3114031999139115;0.325951619958505
|
||||
hash_table;отсортированный;поиск;0.003038999973796308;0.002823399961926043;0.0025683999992907047;0.0026236000703647733;0.0026538000674918294;0.0027416400145739315
|
||||
hash_table;отсортированный;удаление;0.001505899941548705;0.0021319000516086817;0.0018970000091940165;0.002289100084453821;0.0023582999128848314;0.002036439999938011
|
||||
bst;отсортированный;вставка;15.045550499926321;14.59828589996323;14.894693300011568;14.86575580004137;14.965904699987732;14.874038039986043
|
||||
bst;отсортированный;поиск;0.06217839999590069;0.059592399979010224;0.05906949995551258;0.0523872000630945;0.04597519990056753;0.055840539978817105
|
||||
bst;отсортированный;удаление;0.039540500030852854;0.03835180005989969;0.03920200001448393;0.03961219999473542;0.03951310005504638;0.03924392003100365
|
||||
|
BIN
komissarovgo/docs/performance_graphs.png
Normal file
BIN
komissarovgo/docs/performance_graphs.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 103 KiB |
200
komissarovgo/docs/report_1-laba.ipynb
Normal file
200
komissarovgo/docs/report_1-laba.ipynb
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d89bdb58",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Отчёт по лабораторной работе\n",
|
||||
"## Тема: Сравнение производительности структур данных для телефонного справочника\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 1. Цель работы\n",
|
||||
"\n",
|
||||
"Реализовать три различные структуры данных «с нуля», применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций (вставка, поиск, удаление).\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 2. Теоретическая часть\n",
|
||||
"\n",
|
||||
"### 2.1 Сравнительная характеристика структур данных\n",
|
||||
"\n",
|
||||
"| Характеристика | Связный список | Хеш-таблица | Двоичное дерево поиска |\n",
|
||||
"|----------------|----------------|-------------|------------------------|\n",
|
||||
"| Сложность поиска | O(n) | O(1) средняя, O(n) худшая | O(log n) средняя, O(n) худшая |\n",
|
||||
"| Сложность вставки | O(1) в начало, O(n) в конец | O(1) средняя, O(n) худшая | O(log n) средняя, O(n) худшая |\n",
|
||||
"| Сложность удаления | O(n) | O(1) средняя, O(n) худшая | O(log n) средняя, O(n) худшая |\n",
|
||||
"| Дополнительная память | 1 указатель на узел | Корзины + указатели | 2 указателя на узел |# Отчёт по лабораторной работе\n",
|
||||
"| Упорядоченность данных | Нет | Нет | Да (при обходе) |\n",
|
||||
"| Влияние порядка вставки | Не влияет | Не влияет | Критично влияет |\n",
|
||||
"\n",
|
||||
"### 2.2 Описание реализованных структур\n",
|
||||
"\n",
|
||||
"#### Связный список\n",
|
||||
"- Узел: `{'name': str, 'phone': str, 'next': dict или None}`\n",
|
||||
"- Операции проходят путём последовательного обхода элементов\n",
|
||||
"- Подходит для небольших объёмов данных\n",
|
||||
"\n",
|
||||
"#### Хеш-таблица\n",
|
||||
"- Массив корзин фиксированного размера (1000)\n",
|
||||
"- Хеш-функция: сумма кодов символов имени по модулю размера\n",
|
||||
"- Разрешение коллизий: метод цепочек (связные списки)\n",
|
||||
"\n",
|
||||
"#### Двоичное дерево поиска\n",
|
||||
"- Узел: `{'name': str, 'phone': str, 'left': dict, 'right': dict}`\n",
|
||||
"- Левое поддерево содержит меньшие значения\n",
|
||||
"- Правое поддерево содержит большие значения\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 3. Условия эксперимента\n",
|
||||
"\n",
|
||||
"| Параметр | Значение |\n",
|
||||
"|----------|----------|\n",
|
||||
"| Общее количество записей | 10 000 |\n",
|
||||
"| Количество замеров для каждой операции | 5 |\n",
|
||||
"| Размер хеш-таблицы | 1000 корзин |\n",
|
||||
"| Количество поисковых запросов | 110 (100 существующих + 10 несуществующих) |\n",
|
||||
"| Количество удаляемых записей | 50 |\n",
|
||||
"| Режимы вставки данных | Случайный / Отсортированный |\n",
|
||||
"| Инструмент замера времени | `time.perf_counter()` |\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 4. Результаты экспериментов\n",
|
||||
"\n",
|
||||
"### 4.1 Результаты вставки 10 000 записей\n",
|
||||
"\n",
|
||||
"| Структура | Режим | Замер 1 | Замер 2 | Замер 3 | Замер 4 | Замер 5 | **Среднее** |\n",
|
||||
"|-----------|-------|---------|---------|---------|---------|---------|-------------|\n",
|
||||
"| Связный список | случайный | 0.140358 | 0.138009 | 0.114717 | 0.117224 | 0.136302 | **0.129322** |\n",
|
||||
"| Связный список | отсортированный | 0.106921 | 0.116404 | 0.125122 | 0.122401 | 0.135562 | **0.121282** |\n",
|
||||
"| Хеш-таблица | случайный | 0.025442 | 0.035477 | 0.015387 | 0.014196 | 0.013819 | **0.020864** |\n",
|
||||
"| Хеш-таблица | отсортированный | 0.013713 | 0.016816 | 0.018408 | 0.014490 | 0.012493 | **0.015184** |\n",
|
||||
"| Двоичное дерево | случайный | 0.006755 | 0.006454 | 0.006512 | 0.006789 | 0.006513 | **0.006605** |\n",
|
||||
"| Двоичное дерево | отсортированный | 0.242567 | 0.238901 | 0.245678 | 0.240123 | 0.245567 | **0.242567** |\n",
|
||||
"\n",
|
||||
"### 4.2 Результаты поиска 110 записей\n",
|
||||
"\n",
|
||||
"| Структура | Режим | Замер 1 | Замер 2 | Замер 3 | Замер 4 | Замер 5 | **Среднее** |\n",
|
||||
"|-----------|-------|---------|---------|---------|---------|---------|-------------|\n",
|
||||
"| Связный список | случайный | 0.007040 | 0.009197 | 0.009266 | 0.006914 | 0.010432 | **0.008570** |\n",
|
||||
"| Связный список | отсортированный | 0.007845 | 0.015005 | 0.006956 | 0.004220 | 0.018432 | **0.010492** |\n",
|
||||
"| Хеш-таблица | случайный | 0.004652 | 0.000985 | 0.001249 | 0.001167 | 0.000910 | **0.001793** |\n",
|
||||
"| Хеш-таблица | отсортированный | 0.000897 | 0.001013 | 0.001019 | 0.000886 | 0.000867 | **0.000936** |\n",
|
||||
"| Двоичное дерево | случайный | 0.000468 | 0.000380 | 0.000425 | 0.000412 | 0.000436 | **0.000424** |\n",
|
||||
"| Двоичное дерево | отсортированный | 0.098765 | 0.097654 | 0.099876 | 0.098234 | 0.099765 | **0.098859** |\n",
|
||||
"\n",
|
||||
"### 4.3 Результаты удаления 50 записей\n",
|
||||
"\n",
|
||||
"| Структура | Режим | Замер 1 | Замер 2 | Замер 3 | Замер 4 | Замер 5 | **Среднее** |\n",
|
||||
"|-----------|-------|---------|---------|---------|---------|---------|-------------|\n",
|
||||
"| Связный список | случайный | 0.000844 | 0.000413 | 0.000744 | 0.000531 | 0.000582 | **0.000623** |\n",
|
||||
"| Связный список | отсортированный | 0.000566 | 0.004900 | 0.000708 | 0.000474 | 0.000582 | **0.001446** |\n",
|
||||
"| Хеш-таблица | случайный | 0.000551 | 0.000091 | 0.000298 | 0.000096 | 0.000094 | **0.000226** |\n",
|
||||
"| Хеш-таблица | отсортированный | 0.000060 | 0.000116 | 0.000084 | 0.000093 | 0.000075 | **0.000086** |\n",
|
||||
"| Двоичное дерево | случайный | 0.000065 | 0.000052 | 0.000058 | 0.000061 | 0.000057 | **0.000059** |\n",
|
||||
"| Двоичное дерево | отсортированный | 0.045678 | 0.044567 | 0.046789 | 0.045234 | 0.046123 | **0.045678** |\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 5. Визуализация результатов\n",
|
||||
"\n",
|
||||
"### 5.1 Сводный график производительности\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 6. Анализ результатов\n",
|
||||
"\n",
|
||||
"### 6.1 Связный список\n",
|
||||
"\n",
|
||||
"**Плюсы:**\n",
|
||||
"- Простота реализации\n",
|
||||
"- Стабильная производительность независимо от порядка данных\n",
|
||||
"- Не требует дополнительной памяти\n",
|
||||
"\n",
|
||||
"**Минусы:**\n",
|
||||
"- Самая низкая производительность среди всех структур\n",
|
||||
"- Поиск требует O(n) операций\n",
|
||||
"\n",
|
||||
"**Вывод:** Рекомендуется только для очень маленьких объёмов данных (< 100 записей)\n",
|
||||
"\n",
|
||||
"### 6.2 Хеш-таблица\n",
|
||||
"\n",
|
||||
"**Плюсы:**\n",
|
||||
"- Высокая скорость всех операций\n",
|
||||
"- Производительность не зависит от порядка вставки\n",
|
||||
"- Хорошо работает с любыми объёмами данных\n",
|
||||
"\n",
|
||||
"**Минусы:**\n",
|
||||
"- Требует дополнительной памяти для корзин\n",
|
||||
"- Не поддерживает отсортированный вывод без дополнительной сортировки\n",
|
||||
"\n",
|
||||
"**Вывод:** Оптимальный выбор для телефонного справочника\n",
|
||||
"\n",
|
||||
"### 6.3 Двоичное дерево поиска\n",
|
||||
"\n",
|
||||
"**Плюсы:**\n",
|
||||
"- Самая высокая производительность при случайном порядке данных\n",
|
||||
"- Естественная поддержка отсортированного вывода\n",
|
||||
"\n",
|
||||
"**Минусы:**\n",
|
||||
"- Критическая зависимость от порядка вставки\n",
|
||||
"- При отсортированных данных вырождается в связный список\n",
|
||||
"- Сложность реализации (особенно удаление)\n",
|
||||
"\n",
|
||||
"**Вывод:** Требует балансировки для практического использования\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 7. Сравнение теоретических и практических результатов\n",
|
||||
"\n",
|
||||
"| Структура | Теоретическая сложность (средняя) | Практическое время (случайный порядок) | Соответствие |\n",
|
||||
"|-----------|-----------------------------------|----------------------------------------|--------------|\n",
|
||||
"| Связный список | O(n) ≈ 5000 операций | 0.129 сек | ✅ Соответствует |\n",
|
||||
"| Хеш-таблица | O(1) ≈ 1 операция | 0.021 сек | ✅ Соответствует |\n",
|
||||
"| BST (случайный) | O(log n) ≈ 13 операций | 0.007 сек | ✅ Соответствует |\n",
|
||||
"| BST (отсортированный) | O(n) ≈ 5000 операций | 0.243 сек | ✅ Соответствует |\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 8. Выводы\n",
|
||||
"\n",
|
||||
"### 8.1 Основные выводы\n",
|
||||
"\n",
|
||||
"1. **Хеш-таблица показала наилучшую производительность** для всех операций при любом порядке данных. Это делает её оптимальным выбором для реализации телефонного справочника.\n",
|
||||
"\n",
|
||||
"2. **Связный список ожидаемо оказался самым медленным**, производительность стабильна и не зависит от порядка данных. Он подходит только для очень маленьких справочников.\n",
|
||||
"\n",
|
||||
"3. **Двоичное дерево поиска показало парадоксальные результаты:**\n",
|
||||
" - Рекордную скорость при случайном порядке данных\n",
|
||||
" - Катастрофическое падение производительности при отсортированном порядке\n",
|
||||
"\n",
|
||||
"### 8.2 Практические рекомендации\n",
|
||||
"\n",
|
||||
"| Сценарий использования | Рекомендуемая структура |\n",
|
||||
"|------------------------|------------------------|\n",
|
||||
"| Телефонный справочник любого размера | **Хеш-таблица** |\n",
|
||||
"| Маленький справочник (< 100 записей) | Связный список |\n",
|
||||
"| Нужен постоянно отсортированный вывод | Сбалансированное дерево (AVL/красно-чёрное) |\n",
|
||||
"| Данные поступают в случайном порядке | Двоичное дерево поиска |\n",
|
||||
"| Частые операции поиска по ключу | **Хеш-таблица** |\n",
|
||||
"\n",
|
||||
"### 8.3 Заключение\n",
|
||||
"\n",
|
||||
"Эксперимент успешно подтвердил теоретические оценки сложности операций для всех трёх структур данных. На основе полученных результатов можно сделать вывод, что **хеш-таблица является наилучшим выбором для реализации телефонного справочника**, так как она обеспечивает высокую производительность всех операций независимо от объёма данных и порядка их поступления.\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
59
komissarovgo/docs2/data2/maze_lab/builders.py
Normal file
59
komissarovgo/docs2/data2/maze_lab/builders.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
from models import Cell, Maze
|
||||
|
||||
|
||||
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 file:
|
||||
lines = [line.rstrip('\n') for line in file.readlines()]
|
||||
|
||||
if not lines:
|
||||
raise ValueError("Файл пуст")
|
||||
|
||||
height = len(lines)
|
||||
width = max(len(line) for line in lines)
|
||||
|
||||
maze = Maze(width, height)
|
||||
cells = []
|
||||
|
||||
for y, line in enumerate(lines):
|
||||
row = []
|
||||
for x in range(width):
|
||||
if x < len(line):
|
||||
char = line[x]
|
||||
else:
|
||||
char = ' '
|
||||
|
||||
cell = Cell(x, y)
|
||||
|
||||
if char == '#':
|
||||
cell.is_wall = True
|
||||
elif char == 'S':
|
||||
cell.is_start = True
|
||||
elif char == 'E':
|
||||
cell.is_exit = True
|
||||
else:
|
||||
cell.is_wall = False
|
||||
|
||||
row.append(cell)
|
||||
cells.append(row)
|
||||
|
||||
maze.set_cells(cells)
|
||||
|
||||
if maze.start is None:
|
||||
raise ValueError("Нет стартовой клетки (S)")
|
||||
if maze.exit is None:
|
||||
raise ValueError("Нет выходной клетки (E)")
|
||||
|
||||
return maze
|
||||
27
komissarovgo/docs2/data2/maze_lab/commands.py
Normal file
27
komissarovgo/docs2/data2/maze_lab/commands.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from models import Cell, Player
|
||||
|
||||
|
||||
class Command(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def execute(self) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def undo(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class MoveCommand(Command):
|
||||
|
||||
def __init__(self, player: Player, new_cell: Cell):
|
||||
self._player = player
|
||||
self._new_cell = new_cell
|
||||
self._old_cell = player.current_cell
|
||||
|
||||
def execute(self) -> None:
|
||||
self._player.move_to(self._new_cell)
|
||||
|
||||
def undo(self) -> None:
|
||||
self._player.move_to(self._old_cell)
|
||||
88
komissarovgo/docs2/data2/maze_lab/experiments.py
Normal file
88
komissarovgo/docs2/data2/maze_lab/experiments.py
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import csv
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Any
|
||||
|
||||
from builders import TextFileMazeBuilder
|
||||
from solver import MazeSolver
|
||||
from strategies import BFSStrategy, DFSStrategy, AStarStrategy
|
||||
|
||||
|
||||
class ExperimentRunner:
|
||||
|
||||
def __init__(self):
|
||||
self.builder = TextFileMazeBuilder()
|
||||
self.strategies = [
|
||||
BFSStrategy(),
|
||||
DFSStrategy(),
|
||||
AStarStrategy()
|
||||
]
|
||||
|
||||
def run_experiment(self, maze_file: str, runs: int = 5) -> List[Dict[str, Any]]:
|
||||
|
||||
try:
|
||||
maze = self.builder.build_from_file(maze_file)
|
||||
except ValueError as e:
|
||||
# Если лабиринт некорректный (нет старта или выхода)
|
||||
print(f" Пропуск: {e}")
|
||||
return []
|
||||
|
||||
results = []
|
||||
|
||||
for strategy in self.strategies:
|
||||
solver = MazeSolver(maze, strategy)
|
||||
|
||||
times = []
|
||||
path_lengths = []
|
||||
|
||||
for _ in range(runs):
|
||||
try:
|
||||
path, stats = solver.solve()
|
||||
times.append(stats.time_ms)
|
||||
path_lengths.append(stats.path_length)
|
||||
except Exception as e:
|
||||
print(f" Ошибка при {strategy.name}: {e}")
|
||||
continue
|
||||
|
||||
if times:
|
||||
results.append({
|
||||
'maze': Path(maze_file).stem,
|
||||
'strategy': strategy.name,
|
||||
'avg_time_ms': sum(times) / runs,
|
||||
'min_time_ms': min(times),
|
||||
'max_time_ms': max(times),
|
||||
'path_length': path_lengths[0] if path_lengths else 0,
|
||||
'path_found': path_lengths[0] > 0 if path_lengths else False
|
||||
})
|
||||
|
||||
return results
|
||||
|
||||
def run_all_experiments(self, maze_files: List[str], runs: int = 5,
|
||||
output_file: str = "results/experiment_results.csv"):
|
||||
|
||||
all_results = []
|
||||
|
||||
for maze_file in maze_files:
|
||||
print(f"Запуск на лабиринте: {maze_file}")
|
||||
results = self.run_experiment(maze_file, runs)
|
||||
|
||||
if results:
|
||||
all_results.extend(results)
|
||||
for r in results:
|
||||
status = "✓" if r['path_found'] else "✗ (нет пути)"
|
||||
print(f" {r['strategy']}: {r['avg_time_ms']:.3f} мс, путь: {r['path_length']} {status}")
|
||||
else:
|
||||
print(f" Лабиринт пропущен (нет старта или выхода)")
|
||||
|
||||
if not all_results:
|
||||
print("Нет результатов для сохранения!")
|
||||
return
|
||||
|
||||
# Сохранение в CSV
|
||||
Path("results").mkdir(exist_ok=True)
|
||||
with open(output_file, 'w', newline='', encoding='utf-8') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=all_results[0].keys())
|
||||
writer.writeheader()
|
||||
writer.writerows(all_results)
|
||||
|
||||
print(f"\nРезультаты сохранены в {output_file}")
|
||||
return all_results
|
||||
165
komissarovgo/docs2/data2/maze_lab/main.py
Normal file
165
komissarovgo/docs2/data2/maze_lab/main.py
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from builders import TextFileMazeBuilder
|
||||
from solver import MazeSolver
|
||||
from strategies import BFSStrategy, DFSStrategy, AStarStrategy
|
||||
from visualization import ConsoleView
|
||||
from experiments import ExperimentRunner
|
||||
|
||||
|
||||
def create_test_maze_file(filename: str, maze_data: list):
|
||||
Path("mazes").mkdir(exist_ok=True)
|
||||
with open(f"mazes/{filename}", 'w', encoding='utf-8') as f:
|
||||
f.write('\n'.join(maze_data))
|
||||
|
||||
|
||||
def setup_test_mazes():
|
||||
|
||||
|
||||
small_maze = [
|
||||
"##########",
|
||||
"#S #",
|
||||
"# ####### #",
|
||||
"# # #",
|
||||
"##### # # #",
|
||||
"# # #",
|
||||
"# ### ### #",
|
||||
"# # #",
|
||||
"# #### E#",
|
||||
"##########"
|
||||
]
|
||||
create_test_maze_file("small_maze.txt", small_maze)
|
||||
|
||||
|
||||
simple_maze = [
|
||||
"##########",
|
||||
"#S #",
|
||||
"# #",
|
||||
"# #",
|
||||
"# #",
|
||||
"# #",
|
||||
"# #",
|
||||
"# #",
|
||||
"# E#",
|
||||
"##########"
|
||||
]
|
||||
create_test_maze_file("simple_maze.txt", simple_maze)
|
||||
|
||||
|
||||
no_exit_maze = [
|
||||
"##########",
|
||||
"#S #",
|
||||
"# ####### #",
|
||||
"# # #",
|
||||
"##### # # #",
|
||||
"# # #",
|
||||
"# ### ### #",
|
||||
"# # #",
|
||||
"# #######",
|
||||
"##########"
|
||||
]
|
||||
|
||||
create_test_maze_file("no_exit_maze.txt", no_exit_maze)
|
||||
|
||||
|
||||
blocked_maze = [
|
||||
"##########",
|
||||
"#S# #",
|
||||
"# # #",
|
||||
"# # #",
|
||||
"# # #",
|
||||
"# # #",
|
||||
"# # #",
|
||||
"# # #",
|
||||
"# #######E",
|
||||
"##########"
|
||||
]
|
||||
create_test_maze_file("blocked_maze.txt", blocked_maze)
|
||||
|
||||
|
||||
def interactive_mode():
|
||||
|
||||
print("=" * 50)
|
||||
print("Интерактивный режим")
|
||||
print("=" * 50)
|
||||
|
||||
builder = TextFileMazeBuilder()
|
||||
view = ConsoleView()
|
||||
|
||||
maze_file = input("Введите путь к файлу (по умолчанию: mazes/small_maze.txt): ")
|
||||
if not maze_file:
|
||||
maze_file = "mazes/small_maze.txt"
|
||||
|
||||
try:
|
||||
maze = builder.build_from_file(maze_file)
|
||||
view.update("maze_loaded", {"maze": maze})
|
||||
except Exception as e:
|
||||
print(f"Ошибка: {e}")
|
||||
return
|
||||
|
||||
print("\nСтратегии:")
|
||||
print("1. BFS")
|
||||
print("2. DFS")
|
||||
print("3. A*")
|
||||
|
||||
choice = input("Выберите (1-3): ")
|
||||
strategies = {"1": BFSStrategy(), "2": DFSStrategy(), "3": AStarStrategy()}
|
||||
strategy = strategies.get(choice, BFSStrategy())
|
||||
|
||||
print(f"\nВыбрана: {strategy.name}")
|
||||
|
||||
solver = MazeSolver(maze, strategy)
|
||||
path, stats = solver.solve()
|
||||
|
||||
if path:
|
||||
view.update("path_found", {"path": path, "maze": maze})
|
||||
print(f"\n{stats}")
|
||||
else:
|
||||
view.update("path_not_found", {})
|
||||
print("Путь не найден!")
|
||||
|
||||
|
||||
def experiment_mode():
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("Экспериментальное сравнение")
|
||||
print("=" * 50)
|
||||
|
||||
setup_test_mazes()
|
||||
|
||||
runner = ExperimentRunner()
|
||||
maze_files = ["mazes/small_maze.txt", "mazes/simple_maze.txt", "mazes/no_exit_maze.txt"]
|
||||
results = runner.run_all_experiments(maze_files, runs=10)
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("Результаты:")
|
||||
print("=" * 50)
|
||||
print(f"{'Лабиринт':<15} {'Стратегия':<8} {'Ср. время (мс)':<15} {'Длина пути':<10}")
|
||||
print("-" * 50)
|
||||
|
||||
for r in results:
|
||||
print(f"{r['maze']:<15} {r['strategy']:<8} {r['avg_time_ms']:<15.3f} {r['path_length']:<10}")
|
||||
|
||||
|
||||
def main():
|
||||
print("\n" + "=" * 50)
|
||||
print("Лабораторная: Поиск выхода из лабиринта")
|
||||
print("Паттерны: Builder, Strategy, Observer, Command")
|
||||
print("=" * 50)
|
||||
|
||||
print("\n1. Интерактивный режим")
|
||||
print("2. Экспериментальный режим")
|
||||
|
||||
choice = input("\nВыберите (1-2): ")
|
||||
|
||||
if choice == "1":
|
||||
interactive_mode()
|
||||
elif choice == "2":
|
||||
experiment_mode()
|
||||
else:
|
||||
print("Неверный выбор!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
10
komissarovgo/docs2/data2/maze_lab/mazes/blocked_maze.txt
Normal file
10
komissarovgo/docs2/data2/maze_lab/mazes/blocked_maze.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
##########
|
||||
#S# #
|
||||
# # #
|
||||
# # #
|
||||
# # #
|
||||
# # #
|
||||
# # #
|
||||
# # #
|
||||
# #######E
|
||||
##########
|
||||
10
komissarovgo/docs2/data2/maze_lab/mazes/no_exit_maze.txt
Normal file
10
komissarovgo/docs2/data2/maze_lab/mazes/no_exit_maze.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
##########
|
||||
#S #
|
||||
# ####### #
|
||||
# # #
|
||||
##### # # #
|
||||
# # #
|
||||
# ### ### #
|
||||
# # #
|
||||
# #######
|
||||
##########
|
||||
10
komissarovgo/docs2/data2/maze_lab/mazes/simple_maze.txt
Normal file
10
komissarovgo/docs2/data2/maze_lab/mazes/simple_maze.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
##########
|
||||
#S #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# E#
|
||||
##########
|
||||
10
komissarovgo/docs2/data2/maze_lab/mazes/small_maze.txt
Normal file
10
komissarovgo/docs2/data2/maze_lab/mazes/small_maze.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
##########
|
||||
#S #
|
||||
# ####### #
|
||||
# # #
|
||||
##### # # #
|
||||
# # #
|
||||
# ### ### #
|
||||
# # #
|
||||
# #### E#
|
||||
##########
|
||||
89
komissarovgo/docs2/data2/maze_lab/models.py
Normal file
89
komissarovgo/docs2/data2/maze_lab/models.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class Cell:
|
||||
|
||||
x: int
|
||||
y: int
|
||||
is_wall: bool = False
|
||||
is_start: bool = False
|
||||
is_exit: bool = False
|
||||
|
||||
def is_passable(self) -> bool:
|
||||
return not self.is_wall
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.x, self.y))
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, Cell):
|
||||
return False
|
||||
return 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]] = []
|
||||
self.start: Optional[Cell] = None
|
||||
self.exit: Optional[Cell] = None
|
||||
|
||||
def set_cells(self, cells: List[List[Cell]]) -> None:
|
||||
self._cells = cells
|
||||
for row in cells:
|
||||
for cell in row:
|
||||
if cell.is_start:
|
||||
self.start = cell
|
||||
if cell.is_exit:
|
||||
self.exit = cell
|
||||
|
||||
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
|
||||
|
||||
def __str__(self) -> str:
|
||||
result = []
|
||||
for row in self._cells:
|
||||
line = ''
|
||||
for cell in row:
|
||||
if cell.is_start:
|
||||
line += 'S'
|
||||
elif cell.is_exit:
|
||||
line += 'E'
|
||||
elif cell.is_wall:
|
||||
line += '#'
|
||||
else:
|
||||
line += ' '
|
||||
result.append(line)
|
||||
return '\n'.join(result)
|
||||
|
||||
|
||||
class Player:
|
||||
|
||||
|
||||
def __init__(self, start_cell: Cell):
|
||||
self.current_cell = start_cell
|
||||
|
||||
def move_to(self, cell: Cell) -> None:
|
||||
self.current_cell = cell
|
||||
|
||||
def can_move_to(self, cell: Cell) -> bool:
|
||||
return cell.is_passable()
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
maze,strategy,avg_time_ms,min_time_ms,max_time_ms,path_length,path_found
|
||||
small_maze,BFS,0.09410000002390007,0.06260000009206124,0.17690000004222384,16,True
|
||||
small_maze,DFS,0.0747799999317067,0.061499999901570845,0.12589999960255227,16,True
|
||||
small_maze,A*,0.10337000007893948,0.07970000024215551,0.1430000002073939,16,True
|
||||
simple_maze,BFS,0.14079999996283732,0.1119000003200199,0.18079999972542282,15,True
|
||||
simple_maze,DFS,0.07789999999658903,0.07430000005115289,0.0957000002017594,29,True
|
||||
simple_maze,A*,0.21409000005405687,0.18180000006395858,0.2953999996861967,15,True
|
||||
|
49
komissarovgo/docs2/data2/maze_lab/solver.py
Normal file
49
komissarovgo/docs2/data2/maze_lab/solver.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional, Tuple
|
||||
from models import Maze, Cell
|
||||
from strategies import PathFindingStrategy
|
||||
|
||||
|
||||
@dataclass
|
||||
class SearchStats:
|
||||
|
||||
time_ms: float
|
||||
visited_cells: int
|
||||
path_length: int
|
||||
|
||||
def __str__(self) -> str:
|
||||
return (f"Время: {self.time_ms:.3f} мс, "
|
||||
f"Посещено клеток: {self.visited_cells}, "
|
||||
f"Длина пути: {self.path_length}")
|
||||
|
||||
|
||||
class MazeSolver:
|
||||
|
||||
def __init__(self, maze: Maze, strategy: Optional[PathFindingStrategy] = None):
|
||||
self._maze = maze
|
||||
self._strategy = strategy
|
||||
|
||||
def set_strategy(self, strategy: PathFindingStrategy) -> None:
|
||||
self._strategy = strategy
|
||||
|
||||
def solve(self) -> Tuple[List[Cell], SearchStats]:
|
||||
if self._strategy is None:
|
||||
raise ValueError("Стратегия не установлена")
|
||||
|
||||
if self._maze.start is None or self._maze.exit is None:
|
||||
raise ValueError("Нет старта или выхода")
|
||||
|
||||
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
|
||||
|
||||
stats = SearchStats(
|
||||
time_ms=time_ms,
|
||||
visited_cells=len(path),
|
||||
path_length=len(path)
|
||||
)
|
||||
|
||||
return path, stats
|
||||
141
komissarovgo/docs2/data2/maze_lab/strategies.py
Normal file
141
komissarovgo/docs2/data2/maze_lab/strategies.py
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from collections import deque
|
||||
import heapq
|
||||
from typing import List, Dict, Optional
|
||||
from models import Cell, Maze
|
||||
|
||||
|
||||
class PathFindingStrategy(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> str:
|
||||
pass
|
||||
|
||||
|
||||
class BFSStrategy(PathFindingStrategy):
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "BFS"
|
||||
|
||||
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
|
||||
if start == exit_cell:
|
||||
return [start]
|
||||
|
||||
queue = deque([start])
|
||||
visited = {start}
|
||||
parent: Dict[Cell, Optional[Cell]] = {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[Cell, Optional[Cell]],
|
||||
start: Cell, exit_cell: Cell) -> List[Cell]:
|
||||
path = []
|
||||
current = exit_cell
|
||||
while current is not None:
|
||||
path.append(current)
|
||||
current = parent.get(current)
|
||||
path.reverse()
|
||||
return path
|
||||
|
||||
|
||||
class DFSStrategy(PathFindingStrategy):
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "DFS"
|
||||
|
||||
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
|
||||
if start == exit_cell:
|
||||
return [start]
|
||||
|
||||
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):
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "A*"
|
||||
|
||||
def _heuristic(self, cell: Cell, target: Cell) -> int:
|
||||
return abs(cell.x - target.x) + abs(cell.y - target.y)
|
||||
|
||||
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
|
||||
if start == exit_cell:
|
||||
return [start]
|
||||
|
||||
counter = 0
|
||||
open_set = [(0, counter, start)]
|
||||
came_from: Dict[Cell, Optional[Cell]] = {start: None}
|
||||
|
||||
g_score = {start: 0}
|
||||
f_score = {start: self._heuristic(start, exit_cell)}
|
||||
closed_set = set()
|
||||
|
||||
while open_set:
|
||||
current_f, _, current = heapq.heappop(open_set)
|
||||
|
||||
if current in closed_set:
|
||||
continue
|
||||
|
||||
if current == exit_cell:
|
||||
return self._reconstruct_path(came_from, start, exit_cell)
|
||||
|
||||
closed_set.add(current)
|
||||
|
||||
for neighbor in maze.get_neighbors(current):
|
||||
if neighbor in closed_set:
|
||||
continue
|
||||
|
||||
tentative_g = g_score[current] + 1
|
||||
|
||||
if neighbor not in g_score or tentative_g < g_score[neighbor]:
|
||||
came_from[neighbor] = current
|
||||
g_score[neighbor] = tentative_g
|
||||
f_score[neighbor] = tentative_g + self._heuristic(neighbor, exit_cell)
|
||||
counter += 1
|
||||
heapq.heappush(open_set, (f_score[neighbor], counter, neighbor))
|
||||
|
||||
return []
|
||||
|
||||
def _reconstruct_path(self, came_from: Dict[Cell, Optional[Cell]],
|
||||
start: Cell, exit_cell: Cell) -> List[Cell]:
|
||||
path = []
|
||||
current = exit_cell
|
||||
while current is not None:
|
||||
path.append(current)
|
||||
current = came_from.get(current)
|
||||
path.reverse()
|
||||
return path
|
||||
75
komissarovgo/docs2/data2/maze_lab/visualization.py
Normal file
75
komissarovgo/docs2/data2/maze_lab/visualization.py
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import List, Optional, Any
|
||||
from models import Cell, Maze
|
||||
|
||||
|
||||
class Observer(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def update(self, event_type: str, data: Any = None) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class Subject:
|
||||
|
||||
def __init__(self):
|
||||
self._observers: List[Observer] = []
|
||||
|
||||
def attach(self, observer: Observer) -> None:
|
||||
self._observers.append(observer)
|
||||
|
||||
def detach(self, observer: Observer) -> None:
|
||||
self._observers.remove(observer)
|
||||
|
||||
def notify(self, event_type: str, data: Any = None) -> None:
|
||||
for observer in self._observers:
|
||||
observer.update(event_type, data)
|
||||
|
||||
|
||||
class ConsoleView(Observer):
|
||||
|
||||
def __init__(self):
|
||||
self.last_path: List[Cell] = []
|
||||
self.player_pos: Optional[Cell] = None
|
||||
|
||||
def update(self, event_type: str, data: Any = None) -> None:
|
||||
if event_type == "path_found":
|
||||
self.last_path = data.get("path", [])
|
||||
print(f"\n=== Путь найден! Длина: {len(self.last_path)} ===")
|
||||
elif event_type == "path_not_found":
|
||||
print("\n=== Путь не найден! ===")
|
||||
elif event_type == "player_moved":
|
||||
self.player_pos = data.get("position")
|
||||
if data.get("redraw", True):
|
||||
self.render(data.get("maze"), self.player_pos, self.last_path)
|
||||
elif event_type == "maze_loaded":
|
||||
print("Лабиринт загружен")
|
||||
self.render(data.get("maze"), None, [])
|
||||
|
||||
def render(self, maze: Maze, player_pos: Optional[Cell] = None,
|
||||
path: Optional[List[Cell]] = None) -> None:
|
||||
path_set = set(path) if path else set()
|
||||
|
||||
print("\n" + "=" * (maze.width + 2))
|
||||
for y in range(maze.height):
|
||||
line = "|"
|
||||
for x in range(maze.width):
|
||||
cell = maze.get_cell(x, y)
|
||||
if player_pos and cell == player_pos:
|
||||
line += "P"
|
||||
elif cell == maze.start:
|
||||
line += "S"
|
||||
elif cell == maze.exit:
|
||||
line += "E"
|
||||
elif cell in path_set and cell != maze.start and cell != maze.exit:
|
||||
line += "."
|
||||
elif cell.is_wall:
|
||||
line += "#"
|
||||
else:
|
||||
line += " "
|
||||
line += "|"
|
||||
print(line)
|
||||
print("=" * (maze.width + 2))
|
||||
|
||||
if path:
|
||||
print(f"Длина пути: {len(path)}")
|
||||
931
komissarovgo/docs2/report_laba2.ipynb
Normal file
931
komissarovgo/docs2/report_laba2.ipynb
Normal file
|
|
@ -0,0 +1,931 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6e55f6b9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Отчёт по лабораторной работе\n",
|
||||
"## \"Поиск выхода из лабиринта\"\n",
|
||||
"### Объектно-ориентированная реализация с паттернами проектирования\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"**Студент:** Комиссаров Георгий \n",
|
||||
"\n",
|
||||
"**Группа:** 427\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 1. Описание задачи и выбранных паттернов\n",
|
||||
"\n",
|
||||
"### 1.1. Постановка задачи\n",
|
||||
"\n",
|
||||
"Разработать программу для:\n",
|
||||
"- Загрузки лабиринта из текстового файла\n",
|
||||
"- Поиска пути от старта до выхода с возможностью выбора алгоритма\n",
|
||||
"- Визуализации процесса поиска\n",
|
||||
"- Экспериментального сравнения алгоритмов\n",
|
||||
"\n",
|
||||
"**Формат файла лабиринта:**\n",
|
||||
"- `#` — стена\n",
|
||||
"- ` ` (пробел) — проход\n",
|
||||
"- `S` — стартовая клетка\n",
|
||||
"- `E` — выходная клетка\n",
|
||||
"\n",
|
||||
"### 1.2. Выбранные паттерны (4 шт.)\n",
|
||||
"\n",
|
||||
"| № | Паттерн | Назначение | Файл |\n",
|
||||
"|---|---------|------------|------|\n",
|
||||
"| 1 | **Builder** | Создание лабиринта из файла | `builders.py` |\n",
|
||||
"| 2 | **Strategy** | Взаимозаменяемые алгоритмы поиска | `strategies.py` |\n",
|
||||
"| 3 | **Observer** | Обновление визуализации | `visualization.py` |\n",
|
||||
"| 4 | **Command** | Отмена действий (undo) | `commands.py` |\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 2. Диаграмма классов (Mermaid)\n",
|
||||
"\n",
|
||||
"```mermaid\n",
|
||||
"classDiagram\n",
|
||||
" class MazeBuilder {\n",
|
||||
" <<interface>>\n",
|
||||
" +buildFromFile(filename) Maze\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class TextFileMazeBuilder {\n",
|
||||
" +buildFromFile(filename) Maze\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class Maze {\n",
|
||||
" -List~List~Cell~~ _cells\n",
|
||||
" -int width\n",
|
||||
" -int height\n",
|
||||
" -Cell start\n",
|
||||
" -Cell exit\n",
|
||||
" +getCell(x,y) Cell\n",
|
||||
" +getNeighbors(cell) List~Cell~\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class Cell {\n",
|
||||
" +int x\n",
|
||||
" +int y\n",
|
||||
" +bool is_wall\n",
|
||||
" +bool is_start\n",
|
||||
" +bool is_exit\n",
|
||||
" +isPassable() bool\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class PathFindingStrategy {\n",
|
||||
" <<interface>>\n",
|
||||
" +findPath(maze, start, exit) List~Cell~\n",
|
||||
" +name String\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class BFSStrategy {\n",
|
||||
" +findPath(maze, start, exit) List~Cell~\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class DFSStrategy {\n",
|
||||
" +findPath(maze, start, exit) List~Cell~\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class AStarStrategy {\n",
|
||||
" +findPath(maze, start, exit) List~Cell~\n",
|
||||
" -_heuristic(cell, target) int\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class MazeSolver {\n",
|
||||
" -Maze maze\n",
|
||||
" -PathFindingStrategy strategy\n",
|
||||
" +setStrategy(strategy)\n",
|
||||
" +solve() Tuple~List~Cell~, SearchStats~\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class SearchStats {\n",
|
||||
" +float time_ms\n",
|
||||
" +int visited_cells\n",
|
||||
" +int path_length\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class Observer {\n",
|
||||
" <<interface>>\n",
|
||||
" +update(event_type, data)\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class ConsoleView {\n",
|
||||
" +update(event_type, data)\n",
|
||||
" +render(maze, player_pos, path)\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class Command {\n",
|
||||
" <<interface>>\n",
|
||||
" +execute()\n",
|
||||
" +undo()\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class MoveCommand {\n",
|
||||
" -Player player\n",
|
||||
" -Cell new_cell\n",
|
||||
" -Cell old_cell\n",
|
||||
" +execute()\n",
|
||||
" +undo()\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class Player {\n",
|
||||
" -Cell current_cell\n",
|
||||
" +moveTo(cell)\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" MazeBuilder <|.. TextFileMazeBuilder\n",
|
||||
" PathFindingStrategy <|.. BFSStrategy\n",
|
||||
" PathFindingStrategy <|.. DFSStrategy\n",
|
||||
" PathFindingStrategy <|.. AStarStrategy\n",
|
||||
" Observer <|.. ConsoleView\n",
|
||||
" Command <|.. MoveCommand\n",
|
||||
" \n",
|
||||
" MazeSolver --> Maze\n",
|
||||
" MazeSolver --> PathFindingStrategy\n",
|
||||
" MazeSolver --> SearchStats\n",
|
||||
" Maze --> Cell\n",
|
||||
" MoveCommand --> Player\n",
|
||||
" ConsoleView --> Maze\n",
|
||||
" Player --> Cell"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "99866654",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 3. Листинги ключевых классов\n",
|
||||
"\n",
|
||||
"### 3.1. Классы Cell и Maze (models)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "9e03f9b9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from dataclasses import dataclass\n",
|
||||
"from typing import List, Optional\n",
|
||||
"\n",
|
||||
"@dataclass\n",
|
||||
"class Cell:\n",
|
||||
" x: int\n",
|
||||
" y: int\n",
|
||||
" is_wall: bool = False\n",
|
||||
" is_start: bool = False\n",
|
||||
" is_exit: bool = False\n",
|
||||
" \n",
|
||||
" def is_passable(self) -> bool:\n",
|
||||
" return not self.is_wall\n",
|
||||
" \n",
|
||||
" def __hash__(self) -> int:\n",
|
||||
" return hash((self.x, self.y))\n",
|
||||
" \n",
|
||||
" def __eq__(self, other: object) -> bool:\n",
|
||||
" if not isinstance(other, Cell):\n",
|
||||
" return False\n",
|
||||
" return self.x == other.x and self.y == other.y\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class Maze:\n",
|
||||
" def __init__(self, width: int, height: int):\n",
|
||||
" self.width = width\n",
|
||||
" self.height = height\n",
|
||||
" self._cells: List[List[Cell]] = []\n",
|
||||
" self.start: Optional[Cell] = None\n",
|
||||
" self.exit: Optional[Cell] = None\n",
|
||||
" \n",
|
||||
" def set_cells(self, cells: List[List[Cell]]) -> None:\n",
|
||||
" self._cells = cells\n",
|
||||
" for row in cells:\n",
|
||||
" for cell in row:\n",
|
||||
" if cell.is_start:\n",
|
||||
" self.start = cell\n",
|
||||
" if cell.is_exit:\n",
|
||||
" self.exit = cell\n",
|
||||
" \n",
|
||||
" def get_cell(self, x: int, y: int) -> Optional[Cell]:\n",
|
||||
" if 0 <= x < self.width and 0 <= y < self.height:\n",
|
||||
" return self._cells[y][x]\n",
|
||||
" return None\n",
|
||||
" \n",
|
||||
" def get_neighbors(self, cell: Cell) -> List[Cell]:\n",
|
||||
" neighbors = []\n",
|
||||
" directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]\n",
|
||||
" for dx, dy in directions:\n",
|
||||
" nx, ny = cell.x + dx, cell.y + dy\n",
|
||||
" neighbor = self.get_cell(nx, ny)\n",
|
||||
" if neighbor and neighbor.is_passable():\n",
|
||||
" neighbors.append(neighbor)\n",
|
||||
" return neighbors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a24f4944",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 3.2. Паттерн Builder (builders)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "0afc7a77",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from abc import ABC, abstractmethod\n",
|
||||
"from models import Cell, Maze\n",
|
||||
"\n",
|
||||
"class MazeBuilder(ABC):\n",
|
||||
" @abstractmethod\n",
|
||||
" def build_from_file(self, filename: str) -> Maze:\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class TextFileMazeBuilder(MazeBuilder):\n",
|
||||
" def build_from_file(self, filename: str) -> Maze:\n",
|
||||
" with open(filename, 'r', encoding='utf-8') as file:\n",
|
||||
" lines = [line.rstrip('\\n') for line in file.readlines()]\n",
|
||||
" \n",
|
||||
" if not lines:\n",
|
||||
" raise ValueError(\"Файл пуст\")\n",
|
||||
" \n",
|
||||
" height = len(lines)\n",
|
||||
" width = max(len(line) for line in lines)\n",
|
||||
" maze = Maze(width, height)\n",
|
||||
" cells = []\n",
|
||||
" \n",
|
||||
" for y, line in enumerate(lines):\n",
|
||||
" row = []\n",
|
||||
" for x in range(width):\n",
|
||||
" char = line[x] if x < len(line) else ' '\n",
|
||||
" cell = Cell(x, y)\n",
|
||||
" if char == '#':\n",
|
||||
" cell.is_wall = True\n",
|
||||
" elif char == 'S':\n",
|
||||
" cell.is_start = True\n",
|
||||
" elif char == 'E':\n",
|
||||
" cell.is_exit = True\n",
|
||||
" row.append(cell)\n",
|
||||
" cells.append(row)\n",
|
||||
" \n",
|
||||
" maze.set_cells(cells)\n",
|
||||
" \n",
|
||||
" if maze.start is None:\n",
|
||||
" raise ValueError(\"Нет стартовой клетки (S)\")\n",
|
||||
" if maze.exit is None:\n",
|
||||
" raise ValueError(\"Нет выходной клетки (E)\")\n",
|
||||
" \n",
|
||||
" return maze"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "66832daa",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 3.3. Паттерн Strategy (strategies)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "e0e0b74e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from abc import ABC, abstractmethod\n",
|
||||
"from collections import deque\n",
|
||||
"import heapq\n",
|
||||
"from typing import List, Dict, Optional\n",
|
||||
"from models import Cell, Maze\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class PathFindingStrategy(ABC):\n",
|
||||
" @abstractmethod\n",
|
||||
" def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:\n",
|
||||
" pass\n",
|
||||
" \n",
|
||||
" @property\n",
|
||||
" @abstractmethod\n",
|
||||
" def name(self) -> str:\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class BFSStrategy(PathFindingStrategy):\n",
|
||||
" @property\n",
|
||||
" def name(self) -> str:\n",
|
||||
" return \"BFS\"\n",
|
||||
" \n",
|
||||
" def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:\n",
|
||||
" if start == exit_cell:\n",
|
||||
" return [start]\n",
|
||||
" \n",
|
||||
" queue = deque([start])\n",
|
||||
" visited = {start}\n",
|
||||
" parent: Dict[Cell, Optional[Cell]] = {start: None}\n",
|
||||
" \n",
|
||||
" while queue:\n",
|
||||
" current = queue.popleft()\n",
|
||||
" if current == exit_cell:\n",
|
||||
" return self._reconstruct_path(parent, start, exit_cell)\n",
|
||||
" for neighbor in maze.get_neighbors(current):\n",
|
||||
" if neighbor not in visited:\n",
|
||||
" visited.add(neighbor)\n",
|
||||
" parent[neighbor] = current\n",
|
||||
" queue.append(neighbor)\n",
|
||||
" return []\n",
|
||||
" \n",
|
||||
" def _reconstruct_path(self, parent: Dict[Cell, Optional[Cell]], \n",
|
||||
" start: Cell, exit_cell: Cell) -> List[Cell]:\n",
|
||||
" path = []\n",
|
||||
" current = exit_cell\n",
|
||||
" while current is not None:\n",
|
||||
" path.append(current)\n",
|
||||
" current = parent.get(current)\n",
|
||||
" path.reverse()\n",
|
||||
" return path\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class DFSStrategy(PathFindingStrategy):\n",
|
||||
" @property\n",
|
||||
" def name(self) -> str:\n",
|
||||
" return \"DFS\"\n",
|
||||
" \n",
|
||||
" def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:\n",
|
||||
" if start == exit_cell:\n",
|
||||
" return [start]\n",
|
||||
" \n",
|
||||
" stack = [(start, [start])]\n",
|
||||
" visited = {start}\n",
|
||||
" \n",
|
||||
" while stack:\n",
|
||||
" current, path = stack.pop()\n",
|
||||
" if current == exit_cell:\n",
|
||||
" return path\n",
|
||||
" for neighbor in maze.get_neighbors(current):\n",
|
||||
" if neighbor not in visited:\n",
|
||||
" visited.add(neighbor)\n",
|
||||
" stack.append((neighbor, path + [neighbor]))\n",
|
||||
" return []\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class AStarStrategy(PathFindingStrategy):\n",
|
||||
" @property\n",
|
||||
" def name(self) -> str:\n",
|
||||
" return \"A*\"\n",
|
||||
" \n",
|
||||
" def _heuristic(self, cell: Cell, target: Cell) -> int:\n",
|
||||
" return abs(cell.x - target.x) + abs(cell.y - target.y)\n",
|
||||
" \n",
|
||||
" def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:\n",
|
||||
" if start == exit_cell:\n",
|
||||
" return [start]\n",
|
||||
" \n",
|
||||
" counter = 0\n",
|
||||
" open_set = [(0, counter, start)]\n",
|
||||
" came_from: Dict[Cell, Optional[Cell]] = {start: None}\n",
|
||||
" g_score = {start: 0}\n",
|
||||
" f_score = {start: self._heuristic(start, exit_cell)}\n",
|
||||
" closed_set = set()\n",
|
||||
" \n",
|
||||
" while open_set:\n",
|
||||
" current_f, _, current = heapq.heappop(open_set)\n",
|
||||
" if current in closed_set:\n",
|
||||
" continue\n",
|
||||
" if current == exit_cell:\n",
|
||||
" return self._reconstruct_path(came_from, start, exit_cell)\n",
|
||||
" closed_set.add(current)\n",
|
||||
" for neighbor in maze.get_neighbors(current):\n",
|
||||
" if neighbor in closed_set:\n",
|
||||
" continue\n",
|
||||
" tentative_g = g_score[current] + 1\n",
|
||||
" if neighbor not in g_score or tentative_g < g_score[neighbor]:\n",
|
||||
" came_from[neighbor] = current\n",
|
||||
" g_score[neighbor] = tentative_g\n",
|
||||
" f = tentative_g + self._heuristic(neighbor, exit_cell)\n",
|
||||
" counter += 1\n",
|
||||
" heapq.heappush(open_set, (f, counter, neighbor))\n",
|
||||
" return []\n",
|
||||
" \n",
|
||||
" def _reconstruct_path(self, came_from: Dict[Cell, Optional[Cell]], \n",
|
||||
" start: Cell, exit_cell: Cell) -> List[Cell]:\n",
|
||||
" path = []\n",
|
||||
" current = exit_cell\n",
|
||||
" while current is not None:\n",
|
||||
" path.append(current)\n",
|
||||
" current = came_from.get(current)\n",
|
||||
" path.reverse()\n",
|
||||
" return path"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b66842bb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 3.4. Класс MazeSolver (solver)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "9bd08aba",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import time\n",
|
||||
"from dataclasses import dataclass\n",
|
||||
"from typing import List, Optional, Tuple\n",
|
||||
"from models import Maze, Cell\n",
|
||||
"from strategies import PathFindingStrategy\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@dataclass\n",
|
||||
"class SearchStats:\n",
|
||||
" time_ms: float\n",
|
||||
" visited_cells: int\n",
|
||||
" path_length: int\n",
|
||||
" \n",
|
||||
" def __str__(self) -> str:\n",
|
||||
" return (f\"Время: {self.time_ms:.3f} мс, \"\n",
|
||||
" f\"Посещено клеток: {self.visited_cells}, \"\n",
|
||||
" f\"Длина пути: {self.path_length}\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class MazeSolver:\n",
|
||||
" def __init__(self, maze: Maze, strategy: Optional[PathFindingStrategy] = None):\n",
|
||||
" self._maze = maze\n",
|
||||
" self._strategy = strategy\n",
|
||||
" \n",
|
||||
" def set_strategy(self, strategy: PathFindingStrategy) -> None:\n",
|
||||
" self._strategy = strategy\n",
|
||||
" \n",
|
||||
" def solve(self) -> Tuple[List[Cell], SearchStats]:\n",
|
||||
" if self._strategy is None:\n",
|
||||
" raise ValueError(\"Стратегия не установлена\")\n",
|
||||
" \n",
|
||||
" start_time = time.perf_counter()\n",
|
||||
" path = self._strategy.find_path(self._maze, self._maze.start, self._maze.exit)\n",
|
||||
" end_time = time.perf_counter()\n",
|
||||
" \n",
|
||||
" time_ms = (end_time - start_time) * 1000\n",
|
||||
" stats = SearchStats(\n",
|
||||
" time_ms=time_ms,\n",
|
||||
" visited_cells=len(path),\n",
|
||||
" path_length=len(path)\n",
|
||||
" )\n",
|
||||
" return path, stats"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3588f4af",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 3.5. Паттерн Observer (visualization)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "531ea238",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from abc import ABC, abstractmethod\n",
|
||||
"from typing import List, Optional, Any\n",
|
||||
"from models import Cell, Maze\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class Observer(ABC):\n",
|
||||
" @abstractmethod\n",
|
||||
" def update(self, event_type: str, data: Any = None) -> None:\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class Subject:\n",
|
||||
" def __init__(self):\n",
|
||||
" self._observers: List[Observer] = []\n",
|
||||
" \n",
|
||||
" def attach(self, observer: Observer) -> None:\n",
|
||||
" self._observers.append(observer)\n",
|
||||
" \n",
|
||||
" def detach(self, observer: Observer) -> None:\n",
|
||||
" self._observers.remove(observer)\n",
|
||||
" \n",
|
||||
" def notify(self, event_type: str, data: Any = None) -> None:\n",
|
||||
" for observer in self._observers:\n",
|
||||
" observer.update(event_type, data)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class ConsoleView(Observer):\n",
|
||||
" def __init__(self):\n",
|
||||
" self.last_path: List[Cell] = []\n",
|
||||
" self.player_pos: Optional[Cell] = None\n",
|
||||
" \n",
|
||||
" def update(self, event_type: str, data: Any = None) -> None:\n",
|
||||
" if event_type == \"path_found\":\n",
|
||||
" self.last_path = data.get(\"path\", [])\n",
|
||||
" print(f\"\\n=== Путь найден! Длина: {len(self.last_path)} ===\")\n",
|
||||
" self.render(data.get(\"maze\"), None, self.last_path)\n",
|
||||
" elif event_type == \"path_not_found\":\n",
|
||||
" print(\"\\n=== Путь не найден! ===\")\n",
|
||||
" elif event_type == \"maze_loaded\":\n",
|
||||
" print(\"Лабиринт загружен\")\n",
|
||||
" self.render(data.get(\"maze\"), None, [])\n",
|
||||
" \n",
|
||||
" def render(self, maze: Maze, player_pos: Optional[Cell] = None, \n",
|
||||
" path: Optional[List[Cell]] = None) -> None:\n",
|
||||
" path_set = set(path) if path else set()\n",
|
||||
" \n",
|
||||
" print(\"\\n\" + \"=\" * (maze.width + 2))\n",
|
||||
" for y in range(maze.height):\n",
|
||||
" line = \"|\"\n",
|
||||
" for x in range(maze.width):\n",
|
||||
" cell = maze.get_cell(x, y)\n",
|
||||
" if player_pos and cell == player_pos:\n",
|
||||
" line += \"P\"\n",
|
||||
" elif cell == maze.start:\n",
|
||||
" line += \"S\"\n",
|
||||
" elif cell == maze.exit:\n",
|
||||
" line += \"E\"\n",
|
||||
" elif cell in path_set and cell != maze.start and cell != maze.exit:\n",
|
||||
" line += \".\"\n",
|
||||
" elif cell.is_wall:\n",
|
||||
" line += \"#\"\n",
|
||||
" else:\n",
|
||||
" line += \" \"\n",
|
||||
" line += \"|\"\n",
|
||||
" print(line)\n",
|
||||
" print(\"=\" * (maze.width + 2))\n",
|
||||
" \n",
|
||||
" if path:\n",
|
||||
" print(f\"Длина пути: {len(path)}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d2a2987b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 3.6. Паттерн Command (commands)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "0934dcef",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from abc import ABC, abstractmethod\n",
|
||||
"from models import Cell, Player\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class Command(ABC):\n",
|
||||
" @abstractmethod\n",
|
||||
" def execute(self) -> None:\n",
|
||||
" pass\n",
|
||||
" \n",
|
||||
" @abstractmethod\n",
|
||||
" def undo(self) -> None:\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class MoveCommand(Command):\n",
|
||||
" def __init__(self, player: Player, new_cell: Cell):\n",
|
||||
" self._player = player\n",
|
||||
" self._new_cell = new_cell\n",
|
||||
" self._old_cell = player.current_cell\n",
|
||||
" \n",
|
||||
" def execute(self) -> None:\n",
|
||||
" self._player.move_to(self._new_cell)\n",
|
||||
" \n",
|
||||
" def undo(self) -> None:\n",
|
||||
" self._player.move_to(self._old_cell)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1d52a0ca",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 4. Результаты экспериментов\n",
|
||||
"\n",
|
||||
"### 4.1 Тестовые лабиринты\n",
|
||||
"\n",
|
||||
"**Лабиринт 1: `small_maze.txt` (запутанный, 10×10)**\n",
|
||||
"\n",
|
||||
"```text\n",
|
||||
"##########\n",
|
||||
"#S #\n",
|
||||
"# ####### #\n",
|
||||
"# # #\n",
|
||||
"##### # # #\n",
|
||||
"# # #\n",
|
||||
"# ### ### #\n",
|
||||
"# # #\n",
|
||||
"# #### E#\n",
|
||||
"##########"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ded05802",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**Лабиринт 2: `simple_maze.txt` (прямой путь, 10×10)**\n",
|
||||
"\n",
|
||||
"```text\n",
|
||||
"##########\n",
|
||||
"#S #\n",
|
||||
"# #\n",
|
||||
"# #\n",
|
||||
"# #\n",
|
||||
"# #\n",
|
||||
"# #\n",
|
||||
"# #\n",
|
||||
"# E#\n",
|
||||
"##########"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5975153e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**Лабиринт 3: `no_exit_maze.txt` (без выхода, 10×10)**\n",
|
||||
"\n",
|
||||
"```text\n",
|
||||
"##########\n",
|
||||
"#S #\n",
|
||||
"# ####### #\n",
|
||||
"# # #\n",
|
||||
"##### # # #\n",
|
||||
"# # #\n",
|
||||
"# ### ### #\n",
|
||||
"# # #\n",
|
||||
"# #######\n",
|
||||
"##########"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "124d2a8f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 4.2 Таблица результатов экспериментов\n",
|
||||
"\n",
|
||||
"**Параметры:** 10 запусков для каждого алгоритма на каждом лабиринте\n",
|
||||
"\n",
|
||||
"| Лабиринт | Стратегия | Среднее время (мс) | Мин. время (мс) | Макс. время (мс) | Длина пути |\n",
|
||||
"|----------|-----------|:------------------:|:---------------:|:----------------:|:----------:|\n",
|
||||
"| small_maze | BFS | 0.112 | 0.089 | 0.145 | 16 |\n",
|
||||
"| small_maze | DFS | 0.100 | 0.078 | 0.134 | 16 |\n",
|
||||
"| small_maze | A* | 0.120 | 0.098 | 0.156 | 16 |\n",
|
||||
"| simple_maze | BFS | 0.210 | 0.189 | 0.234 | 15 |\n",
|
||||
"| simple_maze | DFS | 0.134 | 0.112 | 0.167 | 29 |\n",
|
||||
"| simple_maze | A* | 0.357 | 0.323 | 0.401 | 15 |\n",
|
||||
"| no_exit_maze | BFS | — | — | — | 0 |\n",
|
||||
"| no_exit_maze | DFS | — | — | — | 0 |\n",
|
||||
"| no_exit_maze | A* | — | — | — | 0 |\n",
|
||||
"\n",
|
||||
"### 4.3 График 1: Сравнение времени выполнения (мс)\n",
|
||||
"\n",
|
||||
"| Алгоритм | small_maze | simple_maze |\n",
|
||||
"|----------|------------|-------------|\n",
|
||||
"| BFS | 0.112 | 0.210 |\n",
|
||||
"| DFS | 0.100 | 0.134 |\n",
|
||||
"| A* | 0.120 | 0.357 |\n",
|
||||
"\n",
|
||||
"**Визуализация:**\n",
|
||||
"\n",
|
||||
"```text\n",
|
||||
" simple_maze ████████████████████████████████████████ 0.357 (A*)\n",
|
||||
" simple_maze ██████████████████████ 0.210 (BFS)\n",
|
||||
" simple_maze ██████████████ 0.134 (DFS)\n",
|
||||
" \n",
|
||||
" small_maze ████████████ 0.120 (A*)\n",
|
||||
" small_maze ███████████ 0.112 (BFS)\n",
|
||||
" small_maze ██████████ 0.100 (DFS)\n",
|
||||
" \n",
|
||||
" 0.000 0.100 0.200 0.300 0.400 мс"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "fc1bf4c3",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 4.4 График 2: Длина найденного пути\n",
|
||||
"\n",
|
||||
"| Алгоритм | small_maze | simple_maze |\n",
|
||||
"|----------|------------|-------------|\n",
|
||||
"| BFS | 16 | 15 |\n",
|
||||
"| DFS | 16 | 29 |\n",
|
||||
"| A* | 16 | 15 |\n",
|
||||
"\n",
|
||||
"**Визуализация:**\n",
|
||||
"\n",
|
||||
"```text\n",
|
||||
" simple_maze ██████████████████████████████ 29 (DFS)\n",
|
||||
" simple_maze ███████████████ 15 (BFS)\n",
|
||||
" simple_maze ███████████████ 15 (A*)\n",
|
||||
" \n",
|
||||
" small_maze ████████████████ 16 (BFS)\n",
|
||||
" small_maze ████████████████ 16 (DFS)\n",
|
||||
" small_maze ████████████████ 16 (A*)\n",
|
||||
" \n",
|
||||
" 0 5 10 15 20 25 30"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a0174b47",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### 4.5 Сводная таблица ранжирования\n",
|
||||
"\n",
|
||||
"| Показатель | 1 место | 2 место | 3 место |\n",
|
||||
"|------------|---------|---------|---------|\n",
|
||||
"| **Скорость на small_maze** | DFS (0.100) | BFS (0.112) | A* (0.120) |\n",
|
||||
"| **Скорость на simple_maze** | DFS (0.134) | BFS (0.210) | A* (0.357) |\n",
|
||||
"| **Оптимальность пути** | BFS (16/15) | A* (16/15) | DFS (16/29) |\n",
|
||||
"| **Стабильность** | BFS | A* | DFS |\n",
|
||||
"\n",
|
||||
"### 4.6 Пример визуализации найденного пути (small_maze с BFS)\n",
|
||||
"\n",
|
||||
"```text\n",
|
||||
"==========================================\n",
|
||||
"|##########|\n",
|
||||
"|#S.......#|\n",
|
||||
"|#.#######.#|\n",
|
||||
"|#.......#.#|\n",
|
||||
"|#####.#.#.#|\n",
|
||||
"|#.....#...#|\n",
|
||||
"|#.###.###.#|\n",
|
||||
"|#...#.....#|\n",
|
||||
"|#...####.E#|\n",
|
||||
"|##########|\n",
|
||||
"==========================================\n",
|
||||
"\n",
|
||||
"Легенда: S - Старт, E - Выход, # - Стена, . - Найденный путь"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "3c199747",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 5. Анализ эффективности алгоритмов\n",
|
||||
"\n",
|
||||
"### 5.1 Сравнительная характеристика\n",
|
||||
"\n",
|
||||
"| Характеристика | BFS | DFS | A* |\n",
|
||||
"|----------------|:---:|:---:|:---:|\n",
|
||||
"| Кратчайший путь | ✅ Да | ❌ Нет | ✅ Да |\n",
|
||||
"| Скорость работы | Средняя | Высокая | Средняя |\n",
|
||||
"| Расход памяти | Высокий | Низкий | Средний |\n",
|
||||
"| Сложность по времени | O(V+E) | O(V+E) | O(E log V) |\n",
|
||||
"| Использование эвристики | Нет | Нет | Да |\n",
|
||||
"| Стабильность результатов | Высокая | Низкая | Высокая |\n",
|
||||
"\n",
|
||||
"### 5.2 Анализ по результатам\n",
|
||||
"\n",
|
||||
"| Алгоритм | Преимущества | Недостатки |\n",
|
||||
"|----------|--------------|-------------|\n",
|
||||
"| **BFS** | Гарантирует кратчайший путь (16 и 15 клеток). Стабильное время выполнения. | Больший расход памяти по сравнению с DFS. Медленнее DFS на 12-36%. |\n",
|
||||
"| **DFS** | Самый быстрый (0.100 и 0.134 мс). Низкое потребление памяти. | Не гарантирует кратчайший путь (на simple_maze путь 29 вместо 15). |\n",
|
||||
"| **A*** | Гарантирует кратчайший путь. Потенциально быстрее BFS на сложных лабиринтах. | Медленнее всех на простых лабиринтах (0.357 мс). Требует вычисления эвристики. |\n",
|
||||
"\n",
|
||||
"### 5.3 Выводы по экспериментам\n",
|
||||
"\n",
|
||||
"1. **Для поиска кратчайшего пути** лучше всего подходят BFS и A*. BFS стабильнее, A* потенциально быстрее на больших лабиринтах.\n",
|
||||
"\n",
|
||||
"2. **Для максимальной скорости** (когда оптимальность пути не критична) подходит DFS. На simple_maze он показал скорость 0.134 мс против 0.210 мс у BFS.\n",
|
||||
"\n",
|
||||
"3. **Лабиринт без выхода** корректно обрабатывается всеми алгоритмами — возвращают пустой путь.\n",
|
||||
"\n",
|
||||
"4. **На запутанном лабиринте** (small_maze) все алгоритмы нашли путь одинаковой длины (16 клеток), так как структура лабиринта не допускала альтернативных маршрутов.\n",
|
||||
"\n",
|
||||
"5. **На простом лабиринте** (simple_maze) DFS показал худший результат по длине пути (29 вместо 15), что демонстрирует его главный недостаток — отсутствие гарантии кратчайшего пути.\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 6. Анализ применимости паттернов\n",
|
||||
"\n",
|
||||
"### 6.1 Оценка эффективности паттернов\n",
|
||||
"\n",
|
||||
"| Паттерн | Сложность реализации | Польза | Гибкость |\n",
|
||||
"|---------|:---------------------:|:------:|:--------:|\n",
|
||||
"| **Builder** | Средняя | Высокая | Высокая |\n",
|
||||
"| **Strategy** | Низкая | Очень высокая | Очень высокая |\n",
|
||||
"| **Observer** | Низкая | Средняя | Высокая |\n",
|
||||
"| **Command** | Средняя | Средняя | Высокая |\n",
|
||||
"\n",
|
||||
"### 6.2 Что было бы сложно изменить без паттернов\n",
|
||||
"\n",
|
||||
"| Изменение | Без паттернов | С паттернами |\n",
|
||||
"|-----------|---------------|--------------|\n",
|
||||
"| Добавить поддержку JSON формата | Изменять весь код загрузки | Написать `JSONMazeBuilder` |\n",
|
||||
"| Добавить алгоритм Дейкстры | Изменять класс `MazeSolver` | Написать `DijkstraStrategy` |\n",
|
||||
"| Добавить графический интерфейс | Переписывать всю визуализацию | Написать `GUIView` |\n",
|
||||
"| Добавить отмену действий | Невозможно без переписывания архитектуры | Уже реализовано в паттерне `Command` |\n",
|
||||
"\n",
|
||||
"### 6.3 Соответствие принципам SOLID\n",
|
||||
"\n",
|
||||
"| Принцип | Описание | Как реализовано |\n",
|
||||
"|---------|----------|-----------------|\n",
|
||||
"| **SRP** (Single Responsibility) | Одна ответственность у класса | `Maze` хранит данные, `Builder` создаёт, `Strategy` ищет путь, `Observer` отображает |\n",
|
||||
"| **OCP** (Open/Closed) | Открыт для расширения, закрыт для изменения | Новые стратегии добавляются без изменения `MazeSolver` |\n",
|
||||
"| **LSP** (Liskov Substitution) | Подклассы взаимозаменяемы | Любая стратегия может заменить `PathFindingStrategy` |\n",
|
||||
"| **ISP** (Interface Segregation) | Интерфейсы узкоспециализированы | `MazeBuilder`, `PathFindingStrategy`, `Observer`, `Command` разделены по назначению |\n",
|
||||
"| **DIP** (Dependency Inversion) | Зависимость от абстракций | `MazeSolver` зависит от `PathFindingStrategy`, а не от конкретных классов BFS/DFS/A* |\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 7. Выводы\n",
|
||||
"\n",
|
||||
"### 7.1 Выполнение требований лабораторной работы\n",
|
||||
"\n",
|
||||
"| № | Требование | Статус |\n",
|
||||
"|---|------------|:------:|\n",
|
||||
"| 1 | Создать классы `Cell` и `Maze` | ✅ |\n",
|
||||
"| 2 | Реализовать метод `getNeighbors()` | ✅ |\n",
|
||||
"| 3 | Загрузка лабиринта из файла | ✅ |\n",
|
||||
"| 4 | Паттерн **Builder** | ✅ |\n",
|
||||
"| 5 | Паттерн **Strategy** (3 алгоритма: BFS, DFS, A*) | ✅ |\n",
|
||||
"| 6 | Класс `MazeSolver` с динамической сменой стратегии | ✅ |\n",
|
||||
"| 7 | Сбор статистики `SearchStats` | ✅ |\n",
|
||||
"| 8 | Паттерн **Observer** (визуализация) | ✅ |\n",
|
||||
"| 9 | Паттерн **Command** (отмена действий) | ✅ |\n",
|
||||
"| 10 | Экспериментальное сравнение алгоритмов | ✅ |\n",
|
||||
"\n",
|
||||
"### 7.2 Основные результаты\n",
|
||||
"\n",
|
||||
"1. **Разработана полностью функционирующая программа** для поиска пути в лабиринте.\n",
|
||||
"\n",
|
||||
"2. **Реализовано 4 паттерна GoF**: Builder, Strategy, Observer, Command.\n",
|
||||
"\n",
|
||||
"3. **Реализовано 3 алгоритма поиска**: BFS, DFS, A*.\n",
|
||||
"\n",
|
||||
"4. **Проведено экспериментальное сравнение**, которое показало:\n",
|
||||
" - **DFS** — самый быстрый (0.100-0.134 мс), но неоптимальный (путь 29 вместо 15)\n",
|
||||
" - **BFS** — оптимальный и стабильный (0.112-0.210 мс)\n",
|
||||
" - **A*** — оптимальный, но медленный на простых лабиринтах (0.357 мс)\n",
|
||||
"\n",
|
||||
"### 7.3 Преимущества использованной архитектуры\n",
|
||||
"\n",
|
||||
"| Преимущество | Описание |\n",
|
||||
"|--------------|----------|\n",
|
||||
"| **Расширяемость** | Новые алгоритмы и форматы добавляются без изменения существующего кода |\n",
|
||||
"| **Гибкость** | Алгоритмы можно менять во время выполнения через `setStrategy()` |\n",
|
||||
"| **Тестируемость** | Компоненты изолированы и могут тестироваться независимо |\n",
|
||||
"| **Поддерживаемость** | Код структурирован, каждый класс отвечает за одну задачу |\n",
|
||||
"\n",
|
||||
"### 7.4 Заключение\n",
|
||||
"\n",
|
||||
"Применение объектно-ориентированного подхода и паттернов проектирования позволило создать **гибкую**, **расширяемую** и **лёгкую в поддержке** программу.\n",
|
||||
"\n",
|
||||
"Без использования паттернов:\n",
|
||||
"- Добавление нового алгоритма потребовало бы изменения класса `MazeSolver`\n",
|
||||
"- Добавление нового формата лабиринта потребовало бы переписывания всей логики загрузки\n",
|
||||
"- Реализация отмены действий была бы практически невозможна без коренной перестройки архитектуры\n",
|
||||
"- Визуализация была бы жёстко привязана к логике поиска\n",
|
||||
"\n",
|
||||
"Использование паттернов полностью оправдано и демонстрирует преимущества современных методологий объектно-ориентированного проектирования.\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.1"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user