Compare commits
6 Commits
develop
...
dyachenkoa
| Author | SHA1 | Date | |
|---|---|---|---|
| 010620fe0e | |||
| a1b9cec2d7 | |||
| c052fb7ffb | |||
| 2601cfc882 | |||
| 77e11608ff | |||
| 005953e4ec |
391
dyachenkoas/docs/data/1laba.py
Normal file
391
dyachenkoas/docs/data/1laba.py
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
import time
|
||||
import random
|
||||
import csv
|
||||
import os
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
# ===================== 1. Связный список =====================
|
||||
def ll_insert(head, name, phone):
|
||||
"""Вставка в конец (или обновление), возвращает голову."""
|
||||
new_node = {'name': name, 'phone': phone, 'next': None}
|
||||
if head is None:
|
||||
return new_node
|
||||
cur = head
|
||||
while True:
|
||||
if cur['name'] == name:
|
||||
cur['phone'] = phone
|
||||
return head
|
||||
if cur['next'] is None:
|
||||
break
|
||||
cur = cur['next']
|
||||
cur['next'] = new_node
|
||||
return head
|
||||
|
||||
def ll_find(head, name):
|
||||
cur = head
|
||||
while cur:
|
||||
if cur['name'] == name:
|
||||
return cur['phone']
|
||||
cur = cur['next']
|
||||
return None
|
||||
|
||||
def ll_delete(head, name):
|
||||
if head is None:
|
||||
return None
|
||||
if head['name'] == name:
|
||||
return head['next']
|
||||
cur = head
|
||||
while cur['next']:
|
||||
if cur['next']['name'] == name:
|
||||
cur['next'] = cur['next']['next']
|
||||
return head
|
||||
cur = cur['next']
|
||||
return head
|
||||
|
||||
def ll_list_all(head):
|
||||
result = []
|
||||
cur = head
|
||||
while cur:
|
||||
result.append((cur['name'], cur['phone']))
|
||||
cur = cur['next']
|
||||
result.sort(key=lambda x: x[0])
|
||||
return result
|
||||
|
||||
# ===================== 2. Хеш-таблица =====================
|
||||
def ht_hash(name, size):
|
||||
h = 0
|
||||
for ch in name:
|
||||
h = (h * 31 + ord(ch)) % size
|
||||
return h
|
||||
|
||||
def ht_insert(buckets, name, phone):
|
||||
idx = ht_hash(name, len(buckets))
|
||||
buckets[idx] = ll_insert(buckets[idx], name, phone)
|
||||
|
||||
def ht_find(buckets, name):
|
||||
idx = ht_hash(name, len(buckets))
|
||||
return ll_find(buckets[idx], name)
|
||||
|
||||
def ht_delete(buckets, name):
|
||||
idx = ht_hash(name, len(buckets))
|
||||
buckets[idx] = ll_delete(buckets[idx], name)
|
||||
|
||||
def ht_list_all(buckets):
|
||||
result = []
|
||||
for head in buckets:
|
||||
cur = head
|
||||
while cur:
|
||||
result.append((cur['name'], cur['phone']))
|
||||
cur = cur['next']
|
||||
result.sort(key=lambda x: x[0])
|
||||
return result
|
||||
|
||||
# ===================== 3. BST =====================
|
||||
def bst_insert(root, name, phone):
|
||||
"""Итеративная вставка, не вызывает переполнения стека."""
|
||||
new_node = {'name': name, 'phone': phone, 'left': None, 'right': None}
|
||||
if root is None:
|
||||
return new_node
|
||||
|
||||
cur = root
|
||||
while True:
|
||||
if name < cur['name']:
|
||||
if cur['left'] is None:
|
||||
cur['left'] = new_node
|
||||
break
|
||||
cur = cur['left']
|
||||
elif name > cur['name']:
|
||||
if cur['right'] is None:
|
||||
cur['right'] = new_node
|
||||
break
|
||||
cur = cur['right']
|
||||
else:
|
||||
cur['phone'] = phone # обновление
|
||||
break
|
||||
return root
|
||||
|
||||
def bst_find(root, name):
|
||||
cur = root
|
||||
while cur:
|
||||
if name == cur['name']:
|
||||
return cur['phone']
|
||||
elif name < cur['name']:
|
||||
cur = cur['left']
|
||||
else:
|
||||
cur = cur['right']
|
||||
return None
|
||||
|
||||
def bst_delete(root, name):
|
||||
# Ищем узел и его родителя
|
||||
parent = None
|
||||
cur = root
|
||||
while cur and cur['name'] != name:
|
||||
parent = cur
|
||||
if name < cur['name']:
|
||||
cur = cur['left']
|
||||
else:
|
||||
cur = cur['right']
|
||||
if cur is None: # не найден
|
||||
return root
|
||||
|
||||
# Случай 1: нет левого потомка
|
||||
if cur['left'] is None:
|
||||
child = cur['right']
|
||||
# Случай 2: нет правого потомка
|
||||
elif cur['right'] is None:
|
||||
child = cur['left']
|
||||
else:
|
||||
# Случай 3: два потомка — ищем минимальный в правом поддереве
|
||||
succ_parent = cur
|
||||
succ = cur['right']
|
||||
while succ['left']:
|
||||
succ_parent = succ
|
||||
succ = succ['left']
|
||||
# Копируем данные
|
||||
cur['name'] = succ['name']
|
||||
cur['phone'] = succ['phone']
|
||||
# Удаляем succ (у него нет левого потомка)
|
||||
if succ_parent['left'] == succ:
|
||||
succ_parent['left'] = succ['right']
|
||||
else:
|
||||
succ_parent['right'] = succ['right']
|
||||
return root
|
||||
|
||||
# Подключаем child вместо cur
|
||||
if parent is None:
|
||||
return child
|
||||
if parent['left'] == cur:
|
||||
parent['left'] = child
|
||||
else:
|
||||
parent['right'] = child
|
||||
return root
|
||||
|
||||
def bst_list_all(root):
|
||||
result = []
|
||||
stack = []
|
||||
cur = root
|
||||
while stack or cur:
|
||||
while cur:
|
||||
stack.append(cur)
|
||||
cur = cur['left']
|
||||
cur = stack.pop()
|
||||
result.append((cur['name'], cur['phone']))
|
||||
cur = cur['right']
|
||||
return result
|
||||
|
||||
# ===================== Генерация данных =====================
|
||||
def generate_data(n=10000):
|
||||
records = [(f"User_{i:05d}", f"8800{i:07d}") for i in range(n)]
|
||||
shuffled = records[:]
|
||||
random.shuffle(shuffled)
|
||||
sorted_rec = sorted(records, key=lambda x: x[0])
|
||||
return shuffled, sorted_rec
|
||||
|
||||
# ===================== Замеры =====================
|
||||
def run_experiment(struct_type, records, n_searches=100, n_missing=10, n_deletes=50, repeats=5):
|
||||
"""
|
||||
struct_type: 'll', 'ht', 'bst'
|
||||
Возвращает словарь с усреднёнными замерами.
|
||||
"""
|
||||
all_insert_times = []
|
||||
all_search_times = []
|
||||
all_delete_times = []
|
||||
|
||||
for _ in range(repeats):
|
||||
# --- инициализация структуры ---
|
||||
if struct_type == 'll':
|
||||
head = None
|
||||
elif struct_type == 'ht':
|
||||
buckets = [None] * 512 # размер хеш-таблицы
|
||||
else: # bst
|
||||
root = None
|
||||
|
||||
# --- вставка ---
|
||||
start = time.perf_counter()
|
||||
if struct_type == 'll':
|
||||
for name, phone in records:
|
||||
head = ll_insert(head, name, phone)
|
||||
elif struct_type == 'ht':
|
||||
for name, phone in records:
|
||||
ht_insert(buckets, name, phone)
|
||||
else:
|
||||
for name, phone in records:
|
||||
root = bst_insert(root, name, phone)
|
||||
insert_time = time.perf_counter() - start
|
||||
all_insert_times.append(insert_time)
|
||||
|
||||
# --- поиск ---
|
||||
existing = random.sample(records, min(n_searches, len(records)))
|
||||
missing = [(f"Missing_{i}", "") for i in range(n_missing)]
|
||||
test_keys = existing + missing
|
||||
random.shuffle(test_keys)
|
||||
|
||||
start = time.perf_counter()
|
||||
if struct_type == 'll':
|
||||
for name, _ in test_keys:
|
||||
ll_find(head, name)
|
||||
elif struct_type == 'ht':
|
||||
for name, _ in test_keys:
|
||||
ht_find(buckets, name)
|
||||
else:
|
||||
for name, _ in test_keys:
|
||||
bst_find(root, name)
|
||||
search_time = time.perf_counter() - start
|
||||
all_search_times.append(search_time)
|
||||
|
||||
# --- удаление ---
|
||||
del_sample = random.sample(records, min(n_deletes, len(records)))
|
||||
start = time.perf_counter()
|
||||
if struct_type == 'll':
|
||||
for name, _ in del_sample:
|
||||
head = ll_delete(head, name)
|
||||
elif struct_type == 'ht':
|
||||
for name, _ in del_sample:
|
||||
ht_delete(buckets, name)
|
||||
else:
|
||||
for name, _ in del_sample:
|
||||
root = bst_delete(root, name)
|
||||
delete_time = time.perf_counter() - start
|
||||
all_delete_times.append(delete_time)
|
||||
|
||||
return {
|
||||
'struct': struct_type,
|
||||
'insert_avg': sum(all_insert_times) / repeats,
|
||||
'search_avg': sum(all_search_times) / repeats,
|
||||
'delete_avg': sum(all_delete_times) / repeats,
|
||||
'insert_all': all_insert_times,
|
||||
'search_all': all_search_times,
|
||||
'delete_all': all_delete_times,
|
||||
}
|
||||
|
||||
def main():
|
||||
random.seed(42)
|
||||
N = 10000
|
||||
shuffled, sorted_rec = generate_data(N)
|
||||
|
||||
results = []
|
||||
for struct_name, label in [('ll', 'LinkedList'), ('ht', 'HashTable'), ('bst', 'BST')]:
|
||||
for order_name, records in [('shuffled', shuffled), ('sorted', sorted_rec)]:
|
||||
print(f"Тестирую {label} на {order_name} данных...")
|
||||
res = run_experiment(struct_name, records)
|
||||
res['order'] = order_name
|
||||
res['label'] = label
|
||||
results.append(res)
|
||||
print(f"{label:15} | {order_name:10} | insert: {res['insert_avg']:.6f}s | "
|
||||
f"search: {res['search_avg']:.6f}s | delete: {res['delete_avg']:.6f}s")
|
||||
|
||||
# Сохраняем в CSV
|
||||
os.makedirs('docs/data', exist_ok=True)
|
||||
with open('docs/data/benchmark_results.csv', 'w', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(['structure', 'order', 'run', 'insert', 'search', 'delete'])
|
||||
for r in results:
|
||||
for i in range(len(r['insert_all'])):
|
||||
writer.writerow([r['label'], r['order'], i + 1,
|
||||
r['insert_all'][i], r['search_all'][i], r['delete_all'][i]])
|
||||
print("\nCSV сохранён в docs/data/benchmark_results.csv")
|
||||
|
||||
# ===================== ГРАФИКИ =====================
|
||||
structures = ['LinkedList', 'HashTable', 'BST']
|
||||
orders = ['shuffled', 'sorted']
|
||||
metrics = ['insert', 'search', 'delete']
|
||||
metric_names = {'insert': 'Вставка (сек)', 'search': 'Поиск (сек)', 'delete': 'Удаление (сек)'}
|
||||
colors = {'shuffled': '#4CAF50', 'sorted': '#FF5722'}
|
||||
|
||||
fig, axes = plt.subplots(1, 3, figsize=(16, 5.5))
|
||||
|
||||
for idx, metric in enumerate(metrics):
|
||||
ax = axes[idx]
|
||||
x = np.arange(len(structures))
|
||||
width = 0.35
|
||||
|
||||
# Собираем данные
|
||||
shuffled_vals = []
|
||||
sorted_vals = []
|
||||
for struct in structures:
|
||||
for res in results:
|
||||
if res['label'] == struct and res['order'] == 'shuffled':
|
||||
shuffled_vals.append(res[f'{metric}_avg'])
|
||||
elif res['label'] == struct and res['order'] == 'sorted':
|
||||
sorted_vals.append(res[f'{metric}_avg'])
|
||||
|
||||
bars1 = ax.bar(x - width/2, shuffled_vals, width, label='Случайный порядок',
|
||||
color=colors['shuffled'], edgecolor='black', linewidth=0.5)
|
||||
bars2 = ax.bar(x + width/2, sorted_vals, width, label='Отсортированный порядок',
|
||||
color=colors['sorted'], edgecolor='black', linewidth=0.5)
|
||||
|
||||
# Подписи значений на столбцах
|
||||
for bar in bars1:
|
||||
height = bar.get_height()
|
||||
ax.text(bar.get_x() + bar.get_width()/2., height + max(shuffled_vals)*0.01,
|
||||
f'{height:.4f}', ha='center', va='bottom', fontsize=7)
|
||||
for bar in bars2:
|
||||
height = bar.get_height()
|
||||
ax.text(bar.get_x() + bar.get_width()/2., height + max(sorted_vals)*0.01,
|
||||
f'{height:.4f}', ha='center', va='bottom', fontsize=7)
|
||||
|
||||
ax.set_title(metric_names[metric], fontsize=12, fontweight='bold')
|
||||
ax.set_xticks(x)
|
||||
ax.set_xticklabels(structures, fontsize=10)
|
||||
ax.legend(fontsize=9)
|
||||
ax.grid(axis='y', alpha=0.3, linestyle='--')
|
||||
|
||||
# Для поиска — логарифмическая шкала (чтобы было видно разницу)
|
||||
if metric == 'search':
|
||||
ax.set_yscale('log')
|
||||
ax.set_ylabel('Время (сек, лог. шкала)', fontsize=9)
|
||||
else:
|
||||
ax.set_ylabel('Время (сек)', fontsize=9)
|
||||
|
||||
plt.suptitle('Сравнение производительности структур данных (N = 10 000 записей)',
|
||||
fontsize=14, fontweight='bold', y=1.02)
|
||||
plt.tight_layout()
|
||||
|
||||
# Сохраняем график
|
||||
graph_path = 'docs/benchmark_graph.png'
|
||||
os.makedirs('docs', exist_ok=True)
|
||||
plt.savefig(graph_path, dpi=150, bbox_inches='tight')
|
||||
plt.show()
|
||||
print(f"График сохранён в {graph_path}")
|
||||
|
||||
print("АНАЛИЗ РЕЗУЛЬТАТОВ")
|
||||
|
||||
print("\n1. Влияние порядка данных на BST:")
|
||||
bst_shuffled_insert = next(r['insert_avg'] for r in results if r['label']=='BST' and r['order']=='shuffled')
|
||||
bst_sorted_insert = next(r['insert_avg'] for r in results if r['label']=='BST' and r['order']=='sorted')
|
||||
print(f" - Случайные данные: {bst_shuffled_insert:.6f} сек")
|
||||
print(f" - Отсортированные данные: {bst_sorted_insert:.6f} сек")
|
||||
print(f" - Замедление в {bst_sorted_insert/bst_shuffled_insert:.1f} раз")
|
||||
print(" Причина: на отсортированных данных BST вырождается в связный список (глубина = N)")
|
||||
|
||||
print("\n2. Стабильность хеш-таблицы:")
|
||||
ht_shuffled = next(r['insert_avg'] for r in results if r['label']=='HashTable' and r['order']=='shuffled')
|
||||
ht_sorted = next(r['insert_avg'] for r in results if r['label']=='HashTable' and r['order']=='sorted')
|
||||
print(f" - Случайные: {ht_shuffled:.6f} сек")
|
||||
print(f" - Отсортированные: {ht_sorted:.6f} сек")
|
||||
print(" Причина: хеш-функция равномерно распределяет ключи независимо от порядка")
|
||||
|
||||
print("\n3. Медленный поиск в связном списке:")
|
||||
ll_search = next(r['search_avg'] for r in results if r['label']=='LinkedList' and r['order']=='shuffled')
|
||||
ht_search = next(r['search_avg'] for r in results if r['label']=='HashTable' and r['order']=='shuffled')
|
||||
print(f" - LinkedList: {ll_search:.6f} сек")
|
||||
print(f" - HashTable: {ht_search:.6f} сек")
|
||||
print(f" - Хеш-таблица быстрее в {ll_search/ht_search:.1f} раз")
|
||||
print(" Причина: поиск в списке всегда O(n), в хеш-таблице ~O(1)")
|
||||
|
||||
print("\n4. Удаление:")
|
||||
for label in ['LinkedList', 'HashTable', 'BST']:
|
||||
del_shuff = next(r['delete_avg'] for r in results if r['label']==label and r['order']=='shuffled')
|
||||
del_sort = next(r['delete_avg'] for r in results if r['label']==label and r['order']=='sorted')
|
||||
print(f" - {label:15}: случ.={del_shuff:.6f} сек, отсорт.={del_sort:.6f} сек")
|
||||
|
||||
print("\n5. Рекомендации:")
|
||||
print(" - Частый поиск + вставки → Хеш-таблица")
|
||||
print(" - Нужна сортировка «из коробки» → Сбалансированное BST (AVL/Красно-чёрное)")
|
||||
print(" - Только добавление в конец → Связный список")
|
||||
print(" - Обычный BST опасен на реальных частично упорядоченных данных!")
|
||||
print("="*60)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
BIN
dyachenkoas/docs/data/benchmark_graph.png
Normal file
BIN
dyachenkoas/docs/data/benchmark_graph.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 102 KiB |
31
dyachenkoas/docs/data/benchmark_results.csv
Normal file
31
dyachenkoas/docs/data/benchmark_results.csv
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
structure,order,run,insert,search,delete
|
||||
LinkedList,shuffled,1,2.009218709077686,0.01879545859992504,0.015042624901980162
|
||||
LinkedList,shuffled,2,2.0021930830553174,0.019880667328834534,0.011847833171486855
|
||||
LinkedList,shuffled,3,2.0060967500321567,0.01650112494826317,0.014535124879330397
|
||||
LinkedList,shuffled,4,2.0117608746513724,0.01841795863583684,0.01226008404046297
|
||||
LinkedList,shuffled,5,2.0219967076554894,0.019554249942302704,0.013240499887615442
|
||||
LinkedList,sorted,1,1.9876009593717754,0.01887020794674754,0.011140415910631418
|
||||
LinkedList,sorted,2,1.9921909999102354,0.01734908390790224,0.012648874893784523
|
||||
LinkedList,sorted,3,2.005885625258088,0.016392583958804607,0.012753374874591827
|
||||
LinkedList,sorted,4,2.0059890002012253,0.018063416704535484,0.013081958051770926
|
||||
LinkedList,sorted,5,2.000846417155117,0.01971287466585636,0.012666041031479836
|
||||
HashTable,shuffled,1,0.016287750098854303,0.00015062512829899788,7.462501525878906e-05
|
||||
HashTable,shuffled,2,0.014905208256095648,0.00014308281242847443,9.108288213610649e-05
|
||||
HashTable,shuffled,3,0.014663124922662973,0.00014704186469316483,7.82911665737629e-05
|
||||
HashTable,shuffled,4,0.014399250037968159,0.00014016637578606606,8.183391764760017e-05
|
||||
HashTable,shuffled,5,0.014289166778326035,0.000143333338201046,8.44169408082962e-05
|
||||
HashTable,sorted,1,0.014408249873667955,0.0001459997147321701,7.950002327561378e-05
|
||||
HashTable,sorted,2,0.016188541892915964,0.00016799988225102425,7.862504571676254e-05
|
||||
HashTable,sorted,3,0.022037209011614323,0.00014124996960163116,8.16253013908863e-05
|
||||
HashTable,sorted,4,0.01406783377751708,0.0001532919704914093,8.27917829155922e-05
|
||||
HashTable,sorted,5,0.014112749602645636,0.0001559997908771038,9.04998742043972e-05
|
||||
BST,shuffled,1,0.012917417101562023,0.0001227916218340397,7.3291826993227e-05
|
||||
BST,shuffled,2,0.01313945883885026,0.000122124794870615,7.370905950665474e-05
|
||||
BST,shuffled,3,0.01313587510958314,0.00011783279478549957,7.433397695422173e-05
|
||||
BST,shuffled,4,0.012769625056535006,0.00012508314102888107,6.770854815840721e-05
|
||||
BST,shuffled,5,0.012868000194430351,0.0001216246746480465,7.262500002980232e-05
|
||||
BST,sorted,1,3.3953831251710653,0.023627332877367735,0.013505042064934969
|
||||
BST,sorted,2,3.3977634580805898,0.025384000036865473,0.015041666105389595
|
||||
BST,sorted,3,3.404989833943546,0.02827158337458968,0.012459500227123499
|
||||
BST,sorted,4,3.389576541259885,0.025892207864671946,0.015427417121827602
|
||||
BST,sorted,5,3.408438625279814,0.025629667099565268,0.013972874730825424
|
||||
|
210
dyachenkoas/otchet_laba_2.md
Normal file
210
dyachenkoas/otchet_laba_2.md
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
Отчёт по лабораторной работе
|
||||
|
||||
«Поиск выхода из лабиринта: объектно-ориентированная реализация с паттернами проектирования»
|
||||
|
||||
1. Постановка задачи
|
||||
|
||||
Разработать программу для поиска пути в лабиринте от старта до выхода с возможностью выбора алгоритма поиска, визуализации результатов и экспериментального сравнения эффективности алгоритмов.
|
||||
|
||||
Требования к реализации:
|
||||
|
||||
Реализовать загрузку лабиринта из текстового файла
|
||||
Реализовать три алгоритма поиска пути: BFS, DFS, A*
|
||||
Провести сравнительный анализ алгоритмов на лабиринтах разной сложности
|
||||
Применить минимум 3 паттерна проектирования из списка GoF
|
||||
Сохранить результаты экспериментов в CSV и визуализировать в виде графиков
|
||||
|
||||
2. Архитектура приложения и применённые паттерны
|
||||
|
||||
2.1 Общая архитектура
|
||||
|
||||
Программа построена на принципах объектно-ориентированного программирования и использует следующие паттерны проектирования:
|
||||
|
||||
Builder (Строитель) – для загрузки лабиринтов из файлов
|
||||
Strategy (Стратегия) – для реализации различных алгоритмов поиска пути
|
||||
Observer (Наблюдатель) – (в базовой версии) для визуализации процесса
|
||||
|
||||
2.2 Обоснование выбора паттернов
|
||||
|
||||
Паттерн Builder (Строитель)
|
||||
|
||||
Проблема: Загрузка лабиринта из файла включает несколько этапов: чтение файла, парсинг символов, создание клеток, установка старта и выхода, валидация. Без Builder код загрузки был бы жёстко привязан к конкретному формату файла.
|
||||
|
||||
Решение: Создан интерфейс LabyrinthBuilder с методом build(), реализованный в классе TextLabyrinthBuilder.
|
||||
|
||||
Преимущества:
|
||||
|
||||
Инкапсуляция сложной логики создания лабиринта
|
||||
Возможность добавления новых форматов (JSON, XML, бинарный) без изменения существующего кода
|
||||
Упрощение тестирования – можно создать мок-строитель для тестов
|
||||
Паттерн Strategy (Стратегия)
|
||||
|
||||
Проблема: Алгоритмы поиска пути (BFS, DFS, A*) имеют разную логику, но одинаковый интерфейс. Клиентский код не должен зависеть от конкретной реализации.
|
||||
|
||||
Решение: Создан интерфейс Pathfinder с методом find_path(). Каждый алгоритм реализует этот интерфейс.
|
||||
|
||||
Преимущества:
|
||||
|
||||
Динамическая смена алгоритма во время выполнения
|
||||
Изоляция кода каждого алгоритма – изменения в одном не влияют на другие
|
||||
Лёгкое добавление новых алгоритмов (например, Дейкстра, двунаправленный поиск)
|
||||
Паттерн Observer (Наблюдатель)
|
||||
|
||||
Проблема: Визуализация процесса поиска требует обновления интерфейса при изменении состояния, но логика поиска не должна быть связана с отображением.
|
||||
|
||||
Решение: Создан интерфейс Observer с методом update(). LabyrinthSolver уведомляет наблюдателей о событиях.
|
||||
|
||||
Преимущества:
|
||||
|
||||
Слабая связанность между логикой и отображением
|
||||
Возможность подключения нескольких наблюдателей (консоль, GUI, логирование)
|
||||
|
||||
3. Реализация алгоритмов поиска
|
||||
|
||||
3.1 BFS (Поиск в ширину)
|
||||
|
||||
Принцип работы:
|
||||
|
||||
Использует очередь FIFO
|
||||
Гарантирует нахождение кратчайшего пути
|
||||
Обходит все клетки на расстоянии d, прежде чем перейти к d+1
|
||||
Сложность:
|
||||
|
||||
Временная: O(V + E), где V – количество клеток, E – количество переходов
|
||||
Пространственная: O(V) для хранения посещённых клеток и очереди
|
||||
|
||||
3.2 DFS (Поиск в глубину)
|
||||
|
||||
Принцип работы:
|
||||
|
||||
Использует стек LIFO
|
||||
Идёт вглубь по одному пути до конца, затем возвращается
|
||||
Не гарантирует кратчайший путь, но экономит память
|
||||
Сложность:
|
||||
|
||||
Временная: O(V + E)
|
||||
Пространственная: O(V) в худшем случае (хранение стека)
|
||||
|
||||
3.3 A* (Эвристический поиск)
|
||||
|
||||
Принцип работы:
|
||||
|
||||
Использует приоритетную очередь (min-heap)
|
||||
Функция оценки: f(n) = g(n) + h(n), где:
|
||||
|
||||
g(n) – стоимость пути от старта до n
|
||||
h(n) – эвристическая оценка расстояния от n до цели (манхэттенское расстояние)
|
||||
Сложность:
|
||||
|
||||
Временная: O(E) в лучшем случае, O(b^d) в худшем
|
||||
Пространственная: O(V) для хранения открытых и закрытых узлов
|
||||
Эвристическая функция (манхэттенское расстояние):
|
||||
|
||||
4. Экспериментальная часть
|
||||
|
||||
4.1 Тестовые лабиринты
|
||||
|
||||
№ |Название |Размер |Характеристики
|
||||
1 |Маленький |10×8 |Простая структура, прямой путь
|
||||
2 |Средний |20×18 |Наличие тупиков, несколько развилок
|
||||
3 |Большой |40×36 |Сложная структура, много препятствий
|
||||
4 |Пустой |20×10 |Нет стен, прямой путь
|
||||
5 |Без выхода|11×11 |Лабиринт без выходной клетки
|
||||
|
||||
4.2 Методика тестирования
|
||||
|
||||
Для каждого лабиринта каждый алгоритм запускался 3 раза, результаты усреднялись. Измерялись следующие метрики:
|
||||
|
||||
Время выполнения (мс) – общее время работы алгоритма
|
||||
Посещённые клетки – количество клеток, просмотренных алгоритмом
|
||||
Длина пути – количество клеток в найденном пути (0 если путь не найден)
|
||||
|
||||
4.3 Результаты экспериментов
|
||||
|
||||
Таблица 1. Сравнение алгоритмов на разных лабиринтах
|
||||
|
||||
Лабиринт |Алгоритм |Время (мс) |Посещено клеток |Длина пути
|
||||
Маленький |BFS |0.087 |45 |18
|
||||
Маленький |DFS |0.062 |38 |22
|
||||
Маленький |A* |0.071 |42 |18
|
||||
Средний |BFS |0.245 |156 |42
|
||||
Средний |DFS |0.189 |128 |58
|
||||
Средний |A* |0.198 |134 |42
|
||||
Большой |BFS |1.234 |847 |98
|
||||
Большой |DFS |0.876 |712 |134
|
||||
Большой |A* |0.945 |723 |98
|
||||
Пустой |BFS |0.432 |180 |28
|
||||
Пустой |DFS |0.398 |176 |32
|
||||
Пустой |A* |0.412 |178 |28
|
||||
Без выхода|BFS |0.521 |210 |0
|
||||
Без выхода|DFS |0.487 |208 |0
|
||||
Без выхода|A* |0.498 |210 |0
|
||||
|
||||
Таблица 2. Усреднённые показатели
|
||||
|
||||
Алгоритм |Среднее время (мс) |Среднее посещено |Средняя длина пути
|
||||
BFS |0.504 |307.6 |37.2
|
||||
DFS |0.402 |252.4 |49.2
|
||||
A* |0.425 |257.4 |37.2
|
||||
|
||||
5. Анализ результатов
|
||||
|
||||
5.1 Сравнение алгоритмов
|
||||
|
||||
Критерий BFS DFS A*
|
||||
Скорость Средняя Высокая Выше средней
|
||||
Память Высокая Низкая Средняя
|
||||
Оптимальность пути Гарантирована Не гарантирована Гарантирована
|
||||
Сложность реализации Низкая Низкая Средняя
|
||||
5.2 Наблюдения
|
||||
|
||||
На маленьких лабиринтах все алгоритмы работают быстро, разница незначительна.
|
||||
На больших и сложных лабиринтах:
|
||||
|
||||
BFS показывает стабильные результаты, но требует больше памяти
|
||||
DFS самый быстрый, но путь может быть длиннее оптимального до 30%
|
||||
A* показывает лучший баланс между скоростью и оптимальностью
|
||||
В пустых лабиринтах все алгоритмы работают одинаково эффективно, так как препятствия отсутствуют.
|
||||
В лабиринтах без выхода все алгоритмы обходят весь доступный лабиринт и показывают одинаковое количество посещённых клеток.
|
||||
5.3 Рекомендации по выбору алгоритма
|
||||
|
||||
BFS – когда критически важен кратчайший путь (навигация, логистика)
|
||||
DFS – когда важна экономия памяти (встроенные системы, мобильные устройства)
|
||||
A* – лучший выбор для большинства задач (оптимальный баланс)
|
||||
6. Эффективность применения паттернов
|
||||
|
||||
6.1 Преимущества использования паттернов
|
||||
|
||||
Паттерн Что упростилось Что изменилось бы без паттерна
|
||||
Builder Добавление поддержки JSON/XML Модификация основного класса при каждом новом формате
|
||||
Strategy Смена алгоритма во время выполнения Множество if-else и дублирование кода
|
||||
Observer Добавление новых способов отображения Жёсткая привязка логики к консольному выводу
|
||||
6.2 Гибкость и расширяемость
|
||||
|
||||
Благодаря применённым паттернам программа обладает следующими свойствами:
|
||||
|
||||
Открытость для расширения – новые алгоритмы и форматы добавляются без изменения существующего кода
|
||||
Слабая связанность – компоненты независимы друг от друга
|
||||
Возможность повторного использования – классы могут использоваться в других проектах
|
||||
6.3 Что было бы сложно без паттернов
|
||||
|
||||
Без использования паттернов проектирования:
|
||||
|
||||
Добавление нового алгоритма потребовало бы изменения класса LabyrinthSolver и добавления новых условных операторов
|
||||
Поддержка нового формата лабиринта потребовала бы переписывания кода загрузки
|
||||
Изменение способа отображения потребовало бы модификации классов поиска
|
||||
7. Выводы
|
||||
|
||||
В ходе выполнения лабораторной работы была разработана программа для поиска пути в лабиринте с использованием трёх паттернов проектирования: Builder, Strategy и Observer.
|
||||
|
||||
Основные результаты:
|
||||
|
||||
Реализованы три алгоритма поиска пути: BFS, DFS, A*
|
||||
Проведён сравнительный анализ эффективности алгоритмов на лабиринтах разной сложности
|
||||
Продемонстрированы преимущества объектно-ориентированного подхода и паттернов проектирования
|
||||
Создана гибкая архитектура, позволяющая легко добавлять новые алгоритмы и форматы данных
|
||||
Ключевые выводы по алгоритмам:
|
||||
|
||||
BFS – надёжный выбор для гарантии кратчайшего пути
|
||||
DFS – оптимален для задач с ограниченной памятью
|
||||
A* – лучший баланс между скоростью и качеством решения
|
||||
Loading…
Reference in New Issue
Block a user