forked from UNN/2026-rff_mp
Merge pull request '[1,2] Первая и вторая лаба' (#357) from vasilevia/2026-rff_mp:develop into develop
Reviewed-on: UNN/2026-rff_mp#357
This commit is contained in:
commit
d025768363
0
VasilevIA/428b.md
Normal file
0
VasilevIA/428b.md
Normal file
BIN
VasilevIA/lab1/docs/data/plot.png
Normal file
BIN
VasilevIA/lab1/docs/data/plot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 105 KiB |
109
VasilevIA/lab1/docs/data/results.csv
Normal file
109
VasilevIA/lab1/docs/data/results.csv
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
Структура,Режим,Операция,Повторение,Время (сек)
|
||||
LinkedList,случайный,вставка,1,10.862003074988023
|
||||
LinkedList,случайный,поиск,1,0.14576059998944402
|
||||
LinkedList,случайный,удаление,1,0.06351138700847514
|
||||
LinkedList,случайный,вставка,2,9.076335112011293
|
||||
LinkedList,случайный,поиск,2,0.07830005697906017
|
||||
LinkedList,случайный,удаление,2,0.04071814299095422
|
||||
LinkedList,случайный,вставка,3,7.758374091994483
|
||||
LinkedList,случайный,поиск,3,0.08570227198651992
|
||||
LinkedList,случайный,удаление,3,0.04625866198330186
|
||||
LinkedList,случайный,вставка,4,8.821534126007464
|
||||
LinkedList,случайный,поиск,4,0.08695586599060334
|
||||
LinkedList,случайный,удаление,4,0.04239285900257528
|
||||
LinkedList,случайный,вставка,5,7.9369856949779205
|
||||
LinkedList,случайный,поиск,5,0.07877582201035693
|
||||
LinkedList,случайный,удаление,5,0.05032521701650694
|
||||
LinkedList,отсортированный,вставка,1,8.435155968007166
|
||||
LinkedList,отсортированный,поиск,1,0.07126103100017644
|
||||
LinkedList,отсортированный,удаление,1,0.04161756800021976
|
||||
LinkedList,отсортированный,вставка,2,8.206100676994538
|
||||
LinkedList,отсортированный,поиск,2,0.0691266350040678
|
||||
LinkedList,отсортированный,удаление,2,0.03941221899003722
|
||||
LinkedList,отсортированный,вставка,3,7.438653188000899
|
||||
LinkedList,отсортированный,поиск,3,0.06440455198753625
|
||||
LinkedList,отсортированный,удаление,3,0.041969501005951315
|
||||
LinkedList,отсортированный,вставка,4,8.762798506999388
|
||||
LinkedList,отсортированный,поиск,4,0.07810852699913085
|
||||
LinkedList,отсортированный,удаление,4,0.04623017497942783
|
||||
LinkedList,отсортированный,вставка,5,6.8261132860207
|
||||
LinkedList,отсортированный,поиск,5,0.0646884269954171
|
||||
LinkedList,отсортированный,удаление,5,0.038998726988211274
|
||||
HashTable,случайный,вставка,1,0.01305636900360696
|
||||
HashTable,случайный,поиск,1,0.00017252800171263516
|
||||
HashTable,случайный,удаление,1,6.184400990605354e-05
|
||||
HashTable,случайный,вставка,2,0.01886462900438346
|
||||
HashTable,случайный,поиск,2,8.142000297084451e-05
|
||||
HashTable,случайный,удаление,2,4.8632005928084254e-05
|
||||
HashTable,случайный,вставка,3,0.010991099989041686
|
||||
HashTable,случайный,поиск,3,0.00010417000157758594
|
||||
HashTable,случайный,удаление,3,5.93799923080951e-05
|
||||
HashTable,случайный,вставка,4,0.011573908996069804
|
||||
HashTable,случайный,поиск,4,0.00010824101627804339
|
||||
HashTable,случайный,удаление,4,6.125500658527017e-05
|
||||
HashTable,случайный,вставка,5,0.009751884994329885
|
||||
HashTable,случайный,поиск,5,0.000209546007681638
|
||||
HashTable,случайный,удаление,5,0.00010141602251678705
|
||||
HashTable,отсортированный,вставка,1,0.010202526987995952
|
||||
HashTable,отсортированный,поиск,1,8.401999366469681e-05
|
||||
HashTable,отсортированный,удаление,1,4.9825001042336226e-05
|
||||
HashTable,отсортированный,вставка,2,0.011403590004192665
|
||||
HashTable,отсортированный,поиск,2,9.47820080909878e-05
|
||||
HashTable,отсортированный,удаление,2,5.351999425329268e-05
|
||||
HashTable,отсортированный,вставка,3,0.008862807007972151
|
||||
HashTable,отсортированный,поиск,3,0.00017667299835011363
|
||||
HashTable,отсортированный,удаление,3,5.925699952058494e-05
|
||||
HashTable,отсортированный,вставка,4,0.00984748499467969
|
||||
HashTable,отсортированный,поиск,4,8.850300218909979e-05
|
||||
HashTable,отсортированный,удаление,4,5.256402073428035e-05
|
||||
HashTable,отсортированный,вставка,5,0.009679784998297691
|
||||
HashTable,отсортированный,поиск,5,0.00011247699148952961
|
||||
HashTable,отсортированный,удаление,5,6.16690085735172e-05
|
||||
BST,случайный,вставка,1,0.145351675018901
|
||||
BST,случайный,поиск,1,0.0012233680172357708
|
||||
BST,случайный,удаление,1,0.00036901497514918447
|
||||
BST,случайный,вставка,2,0.11196767800720409
|
||||
BST,случайный,поиск,2,0.00044852300197817385
|
||||
BST,случайный,удаление,2,0.0004090379807166755
|
||||
BST,случайный,вставка,3,0.09934362399508245
|
||||
BST,случайный,поиск,3,0.0005716090090572834
|
||||
BST,случайный,удаление,3,0.0002630369854159653
|
||||
BST,случайный,вставка,4,0.062331134016858414
|
||||
BST,случайный,поиск,4,0.00044452102156355977
|
||||
BST,случайный,удаление,4,0.0002924139844253659
|
||||
BST,случайный,вставка,5,0.05811125799664296
|
||||
BST,случайный,поиск,5,0.0003970380057580769
|
||||
BST,случайный,удаление,5,0.0002677540178410709
|
||||
BST,отсортированный,вставка,1,27.313725582993357
|
||||
BST,отсортированный,поиск,1,0.09994954598369077
|
||||
BST,отсортированный,удаление,1,0.10366077398066409
|
||||
BST,отсортированный,вставка,2,24.108436000999063
|
||||
BST,отсортированный,поиск,2,0.09873830401920713
|
||||
BST,отсортированный,удаление,2,0.10281848098384216
|
||||
BST,отсортированный,вставка,3,30.65343388498877
|
||||
BST,отсортированный,поиск,3,0.10266653398866765
|
||||
BST,отсортированный,удаление,3,0.11113363798358478
|
||||
BST,отсортированный,вставка,4,37.78820445598103
|
||||
BST,отсортированный,поиск,4,0.19725433399435133
|
||||
BST,отсортированный,удаление,4,0.20082367697614245
|
||||
BST,отсортированный,вставка,5,31.69466849300079
|
||||
BST,отсортированный,поиск,5,0.1048340730194468
|
||||
BST,отсортированный,удаление,5,0.10346844801097177
|
||||
BST,отсортированный,вставка,СРЕДНЕЕ,30.3116936835926
|
||||
BST,отсортированный,поиск,СРЕДНЕЕ,0.12068855820107274
|
||||
BST,отсортированный,удаление,СРЕДНЕЕ,0.12438100358704104
|
||||
BST,случайный,вставка,СРЕДНЕЕ,0.09542107380693779
|
||||
BST,случайный,поиск,СРЕДНЕЕ,0.0006170118111185729
|
||||
BST,случайный,удаление,СРЕДНЕЕ,0.00032025158870965245
|
||||
HashTable,отсортированный,вставка,СРЕДНЕЕ,0.00999923879862763
|
||||
HashTable,отсортированный,поиск,СРЕДНЕЕ,0.00011129099875688553
|
||||
HashTable,отсортированный,удаление,СРЕДНЕЕ,5.536700482480228e-05
|
||||
HashTable,случайный,вставка,СРЕДНЕЕ,0.012847578397486358
|
||||
HashTable,случайный,поиск,СРЕДНЕЕ,0.0001351810060441494
|
||||
HashTable,случайный,удаление,СРЕДНЕЕ,6.650540744885802e-05
|
||||
LinkedList,отсортированный,вставка,СРЕДНЕЕ,7.933764325204538
|
||||
LinkedList,отсортированный,поиск,СРЕДНЕЕ,0.0695178343972657
|
||||
LinkedList,отсортированный,удаление,СРЕДНЕЕ,0.04164563799276948
|
||||
LinkedList,случайный,вставка,СРЕДНЕЕ,8.891046419995837
|
||||
LinkedList,случайный,поиск,СРЕДНЕЕ,0.09509892339119688
|
||||
LinkedList,случайный,удаление,СРЕДНЕЕ,0.048641253600362686
|
||||
|
62
VasilevIA/lab1/docs/Отчёт.md
Normal file
62
VasilevIA/lab1/docs/Отчёт.md
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# Лабораторная работа №1: Сравнительный анализ структур данных
|
||||
|
||||
## 1. Цель работы
|
||||
Реализация и экспериментальное сравнение производительности трех структур данных:
|
||||
1. **Связный список (LinkedList)**
|
||||
2. **Хеш-таблица (HashTable)**
|
||||
3. **Бинарное дерево поиска (BST)**
|
||||
|
||||
Структуры реализованы в процедурной парадигме (без использования классов). Особое внимание уделяется влиянию порядка входных данных (отсортированные vs случайные) на скорость операций вставки, поиска и удаления.
|
||||
|
||||
## 2. Методика эксперимента
|
||||
|
||||
* **Объем выборки:** $N = 10\,000$ записей (имя, телефон).
|
||||
* **Режимы входных данных:**
|
||||
* *Случайный (Shuffled)* — имена перемешаны.
|
||||
* *Отсортированный (Sorted)* — имена идут по алфавиту.
|
||||
* **Измеряемые метрики:**
|
||||
* Время полной вставки $N$ элементов.
|
||||
* Время 110 операций поиска (100 существующих + 10 несуществующих).
|
||||
* Время 50 операций удаления.
|
||||
* **Инструментарий:** Замеры выполнены через `time.perf_counter()`, анализ данных — через `pandas`, визуализация — через `matplotlib`.
|
||||
* **Повторяемость:** Каждый тест запущен 5 раз для усреднения погрешности.
|
||||
|
||||
## 3. Результаты
|
||||
### 3.1. Сводная таблица (Средние значения, сек)
|
||||
|
||||
| Структура | Режим | Вставка (N) | Поиск (110) | Удаление (50) |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| **HashTable** | Случайный | **0.011** | **0.0001** | **0.00006** |
|
||||
| **HashTable** | Отсортированный | 0.010 | 0.0001 | 0.00006 |
|
||||
| **BST** | Случайный | 0.049 | 0.0005 | 0.0003 |
|
||||
| **BST** | Отсортированный | **29.91** | 0.093 | 0.106 |
|
||||
| **LinkedList** | Случайный | 10.82 | 0.134 | 0.057 |
|
||||
| **LinkedList** | Отсортированный | 6.79 | 0.059 | 0.035 |
|
||||
|
||||
### 3.2. Визуализация
|
||||

|
||||
*(На графике ось Y логарифмическая. Это необходимо, так как диапазон времен составляет от $10^{-4}$ до $30$ секунд).*
|
||||
|
||||
## 4. Анализ результатов
|
||||
### 4.1. Двоичное дерево поиска (BST)
|
||||
Наблюдается критическая зависимость от порядка данных.
|
||||
* **Случайные данные:** Дерево сбалансировано, операции выполняются быстро ($\approx O(\log N)$). Время вставки — 0.05 сек.
|
||||
* **Отсортированные данные:** Произошла деградация до вырожденного дерева (по сути, связного списка). Каждая вставка проходит до самого глубокого уровня. Время вставки составило **~30 секунд**.
|
||||
* **Вывод:** Простое BST не подходит для гарантированно упорядоченных данных. Для реальных систем требуется балансировка (AVL, Red-Black Trees).
|
||||
|
||||
### 4.2. Хеш-таблица
|
||||
Показала **стабильную производительность** вне зависимости от режима данных.
|
||||
* Время вставки $\approx 0.01$ сек.
|
||||
* Время поиска $\approx 0.0001$ сек (в 1000 раз быстрее поиска в списке).
|
||||
* Это подтверждает теоретическую сложность $O(1)$ (в среднем). Хеш-функция равномерно распределила ключи, коллизий практически не было.
|
||||
|
||||
### 4.3. Связный список
|
||||
Демонстрирует самую низкую производительность среди структур для задач поиска.
|
||||
* Вставка в случайном порядке занимает больше времени (10.8 сек), чем в отсортированном (6.8 сек), так как при случайном вставке элементы в среднем распределяются по списку равномернее, а при отсортированной вставке мы всегда идем до конца (хвост списка), что оптимизируется кешем процессора лучше, чем хаотичные переходы.
|
||||
* Поиск занимает $\approx 0.1$ сек ($O(N)$), что значительно медленнее хеш-таблицы.
|
||||
|
||||
## 5. Итоговые выводы
|
||||
|
||||
1. **Для быстрого поиска и вставки (Телефонный справочник):** Идеально подходит **Хеш-таблица**. Она обеспечивает мгновенный доступ к данным ($O(1)$) и не чувствительна к порядку поступления информации.
|
||||
2. **Для хранения данных в отсортированном виде:** Теоретически подходит **BST**, но только при условии, что данные поступают в случайном порядке. Если данные отсортированы заранее, производительность падает в 600 раз. В реальных проектах следует использовать самобалансирующиеся деревья.
|
||||
3. **Связный список:** Неэффективен для задач типа "словарь" или "справочник" из-за линейной сложности поиска. Имеет смысл применять только там, где важна структура очереди или стека, либо в условиях жесткой экономии памяти.
|
||||
252
VasilevIA/lab1/Задание1.py
Normal file
252
VasilevIA/lab1/Задание1.py
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
import random
|
||||
import pandas as pd
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# Увеличиваем лимит рекурсии для BST на отсортированных данных (может достичь глубины N)
|
||||
sys.setrecursionlimit(20000)
|
||||
|
||||
# =========================================================
|
||||
# 1. СВЯЗНЫЙ СПИСОК (LinkedListPhoneBook)
|
||||
# =========================================================
|
||||
def ll_insert(head, name, phone):
|
||||
if head is None:
|
||||
return {'name': name, 'phone': phone, 'next': None}
|
||||
|
||||
curr = head
|
||||
while True:
|
||||
if curr['name'] == name:
|
||||
curr['phone'] = phone # Обновление существующей записи
|
||||
break
|
||||
if curr['next'] is None:
|
||||
curr['next'] = {'name': name, 'phone': phone, 'next': None}
|
||||
break
|
||||
curr = curr['next']
|
||||
return head
|
||||
|
||||
def ll_find(head, name):
|
||||
curr = head
|
||||
while curr:
|
||||
if curr['name'] == name:
|
||||
return curr['phone']
|
||||
curr = curr['next']
|
||||
return None
|
||||
|
||||
def ll_delete(head, name):
|
||||
if head is None:
|
||||
return None
|
||||
if head['name'] == name:
|
||||
return head['next']
|
||||
|
||||
curr = head
|
||||
while curr['next']:
|
||||
if curr['next']['name'] == name:
|
||||
curr['next'] = curr['next']['next']
|
||||
break
|
||||
curr = curr['next']
|
||||
return head
|
||||
|
||||
def ll_list_all(head):
|
||||
res = []
|
||||
curr = head
|
||||
while curr:
|
||||
res.append((curr['name'], curr['phone']))
|
||||
curr = curr['next']
|
||||
res.sort(key=lambda x: x[0])
|
||||
return res
|
||||
|
||||
# =========================================================
|
||||
# 2. ХЕШ-ТАБЛИЦА
|
||||
# =========================================================
|
||||
HT_SIZE = 10007 # Простое число для равномерного распределения
|
||||
|
||||
def ht_init():
|
||||
return [None] * HT_SIZE
|
||||
|
||||
def _ht_idx(name):
|
||||
return hash(name) % HT_SIZE
|
||||
|
||||
def ht_insert(buckets, name, phone):
|
||||
idx = _ht_idx(name)
|
||||
buckets[idx] = ll_insert(buckets[idx], name, phone)
|
||||
return buckets
|
||||
|
||||
def ht_find(buckets, name):
|
||||
return ll_find(buckets[_ht_idx(name)], name)
|
||||
|
||||
def ht_delete(buckets, name):
|
||||
idx = _ht_idx(name)
|
||||
buckets[idx] = ll_delete(buckets[idx], name)
|
||||
return buckets
|
||||
|
||||
def ht_list_all(buckets):
|
||||
res = []
|
||||
for bucket in buckets:
|
||||
curr = bucket
|
||||
while curr:
|
||||
res.append((curr['name'], curr['phone']))
|
||||
curr = curr['next']
|
||||
res.sort(key=lambda x: x[0])
|
||||
return res
|
||||
|
||||
# =========================================================
|
||||
# 3. ДВОИЧНОЕ ДЕРЕВО ПОИСКА (BST)
|
||||
# =========================================================
|
||||
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):
|
||||
curr = root
|
||||
while curr:
|
||||
if name == curr['name']:
|
||||
return curr['phone']
|
||||
elif name < curr['name']:
|
||||
curr = curr['left']
|
||||
else:
|
||||
curr = curr['right']
|
||||
return None
|
||||
|
||||
def bst_delete(root, name):
|
||||
if root is None:
|
||||
return None
|
||||
|
||||
if name < root['name']:
|
||||
root['left'] = bst_delete(root['left'], name)
|
||||
elif name > root['name']:
|
||||
root['right'] = bst_delete(root['right'], name)
|
||||
else:
|
||||
# Узел найден
|
||||
if root['left'] is None:
|
||||
return root['right']
|
||||
if root['right'] is None:
|
||||
return root['left']
|
||||
|
||||
# Два потомка: находим минимальный в правом поддереве
|
||||
min_node = root['right']
|
||||
while min_node['left'] is not None:
|
||||
min_node = min_node['left']
|
||||
|
||||
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):
|
||||
if root is None:
|
||||
return []
|
||||
return bst_list_all(root['left']) + [(root['name'], root['phone'])] + bst_list_all(root['right'])
|
||||
|
||||
# =========================================================
|
||||
# ЭКСПЕРИМЕНТАЛЬНАЯ ЧАСТЬ
|
||||
# =========================================================
|
||||
def run_experiments():
|
||||
N = 10000
|
||||
RECORDS = [(f"User_{i:05d}", f"+7900{i:04d}{i%100:02d}") for i in range(N)]
|
||||
|
||||
records_shuffled = RECORDS[:]
|
||||
random.shuffle(records_shuffled)
|
||||
|
||||
records_sorted = sorted(RECORDS, key=lambda x: x[0])
|
||||
|
||||
# Наборы для поиска и удаления
|
||||
existing_names = [r[0] for r in random.sample(RECORDS, 100)]
|
||||
non_existing_names = [f"None_{i}" for i in range(10)]
|
||||
find_names = existing_names + non_existing_names
|
||||
delete_names = [r[0] for r in random.sample(RECORDS, 50)]
|
||||
|
||||
structures = {
|
||||
"LinkedList": (lambda: None, ll_insert, ll_find, ll_delete),
|
||||
"HashTable": (ht_init, ht_insert, ht_find, ht_delete),
|
||||
"BST": (lambda: None, bst_insert, bst_find, bst_delete)
|
||||
}
|
||||
|
||||
modes = {"случайный": records_shuffled, "отсортированный": records_sorted}
|
||||
results = []
|
||||
|
||||
print("Запуск экспериментов...")
|
||||
trials = 5
|
||||
for struct_name, (init_f, ins_f, find_f, del_f) in structures.items():
|
||||
for mode_name, data in modes.items():
|
||||
print(f" {struct_name} | {mode_name}")
|
||||
for t in range(1, trials + 1):
|
||||
# Инициализация
|
||||
ds = init_f()
|
||||
|
||||
# A. Вставка
|
||||
t0 = time.perf_counter()
|
||||
for name, phone in data:
|
||||
ds = ins_f(ds, name, phone)
|
||||
t_ins = time.perf_counter() - t0
|
||||
|
||||
# B. Поиск
|
||||
t0 = time.perf_counter()
|
||||
for name in find_names:
|
||||
find_f(ds, name)
|
||||
t_find = time.perf_counter() - t0
|
||||
|
||||
# C. Удаление
|
||||
t0 = time.perf_counter()
|
||||
for name in delete_names:
|
||||
ds = del_f(ds, name)
|
||||
t_del = time.perf_counter() - t0
|
||||
|
||||
results.append([struct_name, mode_name, "вставка", t, t_ins])
|
||||
results.append([struct_name, mode_name, "поиск", t, t_find])
|
||||
results.append([struct_name, mode_name, "удаление", t, t_del])
|
||||
|
||||
return results
|
||||
|
||||
def save_and_plot(results):
|
||||
import os
|
||||
import matplotlib.pyplot as plt
|
||||
import pandas as pd
|
||||
|
||||
os.makedirs("docs/data", exist_ok=True)
|
||||
|
||||
# 1. Сохранение CSV (как было)
|
||||
df = pd.DataFrame(results, columns=["Структура", "Режим", "Операция", "Повторение", "Время (сек)"])
|
||||
avg = df.groupby(["Структура", "Режим", "Операция"])["Время (сек)"].mean().reset_index()
|
||||
avg["Повторение"] = "СРЕДНЕЕ"
|
||||
df_full = pd.concat([df, avg], ignore_index=True)
|
||||
df_full.to_csv("docs/data/results.csv", index=False, encoding="utf-8-sig")
|
||||
|
||||
# 2. Улучшенный график: 3 отдельных подграфика + логарифмическая шкала
|
||||
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
|
||||
operations = ["вставка", "поиск", "удаление"]
|
||||
structures_order = ["HashTable", "BST", "LinkedList"] # Фиксируем порядок для удобства чтения
|
||||
colors = {"случайный": "#6C157F", "отсортированный": "#1E299F"}
|
||||
|
||||
for ax, op in zip(axes, operations):
|
||||
op_data = avg[avg["Операция"] == op]
|
||||
pivot = op_data.pivot(index="Структура", columns="Режим", values="Время (сек)")
|
||||
pivot = pivot.reindex(structures_order) # Ставим структуры в удобном порядке
|
||||
|
||||
pivot.plot(kind="bar", ax=ax, color=[colors["случайный"], colors["отсортированный"]], width=0.75)
|
||||
ax.set_title(f"Операция: {op.capitalize()}")
|
||||
ax.set_ylabel("Время (сек)")
|
||||
ax.set_xticklabels(ax.get_xticklabels(), rotation=0)
|
||||
ax.grid(axis="y", alpha=0.3, linestyle="--")
|
||||
|
||||
# ЛОГАРИФМИЧЕСКАЯ ШКАЛА: обязательна при разбросе от 0.0001 до 30 сек
|
||||
ax.set_yscale("log")
|
||||
ax.legend(title="Режим", loc="upper right")
|
||||
|
||||
fig.suptitle("Сравнение производительности структур данных", fontsize=16, y=1.05)
|
||||
plt.tight_layout()
|
||||
plt.savefig("docs/data/plot.png", dpi=200, bbox_inches="tight")
|
||||
|
||||
if __name__ == "__main__":
|
||||
res = run_experiments()
|
||||
save_and_plot(res)
|
||||
print("Эксперимент завершен")
|
||||
239
VasilevIA/lab2/codes/maze.py
Normal file
239
VasilevIA/lab2/codes/maze.py
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
import heapq
|
||||
import time
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import deque
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
class Cell:
|
||||
def __init__(self, x, y, is_wall=False, is_start=False, is_exit=False):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.is_wall = is_wall
|
||||
self.is_start = is_start
|
||||
self.is_exit = is_exit
|
||||
|
||||
def is_passable(self):
|
||||
return not self.is_wall
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, Cell) and self.x == other.x and self.y == other.y
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.x, self.y))
|
||||
|
||||
def __repr__(self):
|
||||
return f"Cell({self.x},{self.y})"
|
||||
|
||||
|
||||
class Maze:
|
||||
def __init__(self, cells, width, height, start, exit_cell):
|
||||
self.cells = cells
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.start = start
|
||||
self.exit = exit_cell
|
||||
|
||||
def get_cell(self, x, y):
|
||||
if 0 <= x < self.width and 0 <= y < self.height:
|
||||
return self.cells[y][x]
|
||||
return None
|
||||
|
||||
def get_neighbors(self, cell):
|
||||
result = []
|
||||
for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]:
|
||||
n = self.get_cell(cell.x + dx, cell.y + dy)
|
||||
if n and n.is_passable():
|
||||
result.append(n)
|
||||
return result
|
||||
|
||||
def render(self, path=None):
|
||||
path_set = set(path) if path else set()
|
||||
lines = []
|
||||
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 += "##"
|
||||
elif cell in path_set:
|
||||
line += " ."
|
||||
else:
|
||||
line += " "
|
||||
lines.append(line)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
class MazeBuilder(ABC):
|
||||
@abstractmethod
|
||||
def build_from_file(self, filename) -> Maze:
|
||||
pass
|
||||
|
||||
|
||||
class TextFileMazeBuilder(MazeBuilder):
|
||||
def build_from_file(self, filename) -> Maze:
|
||||
with open(filename, encoding="utf-8") as f:
|
||||
lines = [l.rstrip("\n") for l in f]
|
||||
|
||||
height = len(lines)
|
||||
width = max(len(l) for l in lines)
|
||||
cells = []
|
||||
start = exit_cell = None
|
||||
|
||||
for y, line in enumerate(lines):
|
||||
row = []
|
||||
for x in range(width):
|
||||
ch = line[x] if x < len(line) else " "
|
||||
is_wall = ch == "#"
|
||||
is_start = ch == "S"
|
||||
is_exit = ch == "E"
|
||||
c = Cell(x, y, is_wall, is_start, is_exit)
|
||||
if is_start:
|
||||
start = c
|
||||
if is_exit:
|
||||
exit_cell = c
|
||||
row.append(c)
|
||||
cells.append(row)
|
||||
|
||||
if not start or not exit_cell:
|
||||
raise ValueError("Maze must have S and E")
|
||||
return Maze(cells, width, height, start, exit_cell)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SearchStats:
|
||||
strategy: str
|
||||
time_ms: float
|
||||
visited: int
|
||||
path_length: int
|
||||
path: List[Cell] = field(default_factory=list)
|
||||
|
||||
|
||||
class PathFindingStrategy(ABC):
|
||||
_visited = 0
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.__class__.__name__
|
||||
|
||||
@abstractmethod
|
||||
def find_path(self, maze: Maze, start: Cell, end: Cell) -> List[Cell]:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _build_path(parent, start, end):
|
||||
path, cur = [], end
|
||||
while cur:
|
||||
path.append(cur)
|
||||
cur = parent.get(cur)
|
||||
path.reverse()
|
||||
return path if path and path[0] == start else []
|
||||
|
||||
|
||||
class BFSStrategy(PathFindingStrategy):
|
||||
@property
|
||||
def name(self):
|
||||
return "BFS"
|
||||
|
||||
def find_path(self, maze, start, end):
|
||||
queue = deque([start])
|
||||
parent = {start: None}
|
||||
visited = 0
|
||||
while queue:
|
||||
cur = queue.popleft()
|
||||
visited += 1
|
||||
if cur == end:
|
||||
self._visited = visited
|
||||
return self._build_path(parent, start, end)
|
||||
for nb in maze.get_neighbors(cur):
|
||||
if nb not in parent:
|
||||
parent[nb] = cur
|
||||
queue.append(nb)
|
||||
self._visited = visited
|
||||
return []
|
||||
|
||||
|
||||
class DFSStrategy(PathFindingStrategy):
|
||||
@property
|
||||
def name(self):
|
||||
return "DFS"
|
||||
|
||||
def find_path(self, maze, start, end):
|
||||
stack = [start]
|
||||
parent = {start: None}
|
||||
visited = 0
|
||||
while stack:
|
||||
cur = stack.pop()
|
||||
visited += 1
|
||||
if cur == end:
|
||||
self._visited = visited
|
||||
return self._build_path(parent, start, end)
|
||||
for nb in maze.get_neighbors(cur):
|
||||
if nb not in parent:
|
||||
parent[nb] = cur
|
||||
stack.append(nb)
|
||||
self._visited = visited
|
||||
return []
|
||||
|
||||
|
||||
class AStarStrategy(PathFindingStrategy):
|
||||
@property
|
||||
def name(self):
|
||||
return "A*"
|
||||
|
||||
@staticmethod
|
||||
def _h(a, b):
|
||||
return abs(a.x - b.x) + abs(a.y - b.y)
|
||||
|
||||
def find_path(self, maze, start, end):
|
||||
counter = 0
|
||||
heap = [(0, counter, start)]
|
||||
parent = {start: None}
|
||||
g = {start: 0}
|
||||
closed = set()
|
||||
visited = 0
|
||||
while heap:
|
||||
_, _, cur = heapq.heappop(heap)
|
||||
if cur in closed:
|
||||
continue
|
||||
closed.add(cur)
|
||||
visited += 1
|
||||
if cur == end:
|
||||
self._visited = visited
|
||||
return self._build_path(parent, start, end)
|
||||
for nb in maze.get_neighbors(cur):
|
||||
if nb in closed:
|
||||
continue
|
||||
ng = g[cur] + 1
|
||||
if ng < g.get(nb, float("inf")):
|
||||
g[nb] = ng
|
||||
counter += 1
|
||||
heapq.heappush(heap, (ng + self._h(nb, end), counter, nb))
|
||||
parent[nb] = cur
|
||||
self._visited = visited
|
||||
return []
|
||||
|
||||
|
||||
class MazeSolver:
|
||||
def __init__(self, maze: Maze, strategy: PathFindingStrategy):
|
||||
self.maze = maze
|
||||
self.strategy = strategy
|
||||
|
||||
def set_strategy(self, strategy: PathFindingStrategy):
|
||||
self.strategy = strategy
|
||||
|
||||
def solve(self) -> SearchStats:
|
||||
t0 = time.perf_counter()
|
||||
path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
|
||||
t1 = time.perf_counter()
|
||||
return SearchStats(
|
||||
strategy=self.strategy.name,
|
||||
time_ms=(t1 - t0) * 1000,
|
||||
visited=self.strategy._visited,
|
||||
path_length=len(path),
|
||||
path=path
|
||||
)
|
||||
78
VasilevIA/lab2/codes/maze_generator.py
Normal file
78
VasilevIA/lab2/codes/maze_generator.py
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import os
|
||||
import random
|
||||
|
||||
def _backtracker(width, height, seed=42):
|
||||
rng = random.Random(seed)
|
||||
cw = (width - 1) // 2
|
||||
ch = (height - 1) // 2
|
||||
grid = [["#"] * width for _ in range(height)]
|
||||
visited = [[False] * cw for _ in range(ch)]
|
||||
stack = [(0, 0)]
|
||||
visited[0][0] = True
|
||||
grid[1][1] = " "
|
||||
while stack:
|
||||
cx, cy = stack[-1]
|
||||
gx, gy = cx * 2 + 1, cy * 2 + 1
|
||||
dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)]
|
||||
rng.shuffle(dirs)
|
||||
moved = False
|
||||
for dx, dy in dirs:
|
||||
nx, ny = cx + dx, cy + dy
|
||||
if 0 <= nx < cw and 0 <= ny < ch and not visited[ny][nx]:
|
||||
visited[ny][nx] = True
|
||||
grid[gy + dy][gx + dx] = " "
|
||||
grid[ny * 2 + 1][nx * 2 + 1] = " "
|
||||
stack.append((nx, ny))
|
||||
moved = True
|
||||
break
|
||||
if not moved:
|
||||
stack.pop()
|
||||
grid[1][1] = "S"
|
||||
grid[height - 2][width - 2] = "E"
|
||||
return grid
|
||||
|
||||
|
||||
def _empty(width, height):
|
||||
grid = [["#"] * width for _ in range(height)]
|
||||
for y in range(1, height - 1):
|
||||
for x in range(1, width - 1):
|
||||
grid[y][x] = " "
|
||||
grid[1][1] = "S"
|
||||
grid[height - 2][width - 2] = "E"
|
||||
return grid
|
||||
|
||||
|
||||
def _no_exit(width=11, height=11):
|
||||
grid = _backtracker(width, height, seed=99)
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if grid[y][x] == "E":
|
||||
grid[y][x] = "#"
|
||||
grid[1][width - 2] = "E"
|
||||
for dy in [-1, 0, 1]:
|
||||
for dx in [-1, 0, 1]:
|
||||
ny, nx = 1 + dy, (width - 2) + dx
|
||||
if 0 <= ny < height and 0 <= nx < width and grid[ny][nx] != "E":
|
||||
grid[ny][nx] = "#"
|
||||
return grid
|
||||
|
||||
|
||||
def _save(grid, path):
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
for row in grid:
|
||||
f.write("".join(row) + "\n")
|
||||
|
||||
|
||||
def generate_all(folder="mazes"):
|
||||
mazes = {
|
||||
"small.txt": _backtracker(11, 11, seed=1),
|
||||
"medium.txt": _backtracker(51, 51, seed=2),
|
||||
"large.txt": _backtracker(101, 101, seed=3),
|
||||
"empty.txt": _empty(51, 21),
|
||||
"no_exit.txt": _no_exit(11, 11),
|
||||
"sample.txt": _backtracker(15, 15, seed=5),
|
||||
}
|
||||
for name, grid in mazes.items():
|
||||
_save(grid, os.path.join(folder, name))
|
||||
print(f"Mazes saved to {folder}/")
|
||||
BIN
VasilevIA/lab2/docs/benchmark_plot.png
Normal file
BIN
VasilevIA/lab2/docs/benchmark_plot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 125 KiB |
BIN
VasilevIA/lab2/docs/mermaid.png
Normal file
BIN
VasilevIA/lab2/docs/mermaid.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 115 KiB |
193
VasilevIA/lab2/docs/report2.md
Normal file
193
VasilevIA/lab2/docs/report2.md
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
# Отчёт: Поиск выхода из лабиринта
|
||||
## 1. Описание задачи и выбранных паттернов
|
||||
### Задача
|
||||
|
||||
Разработать программу для загрузки лабиринта из текстового файла, поиска пути от старта до выхода тремя алгоритмами (BFS, DFS, A*), визуализации найденного пути и экспериментального сравнения алгоритмов по времени, числу посещённых клеток и длине пути.
|
||||
|
||||
### Структура файлов
|
||||
|
||||
```
|
||||
02/
|
||||
main.py - точка запуска
|
||||
codes/
|
||||
maze.py - все классы (Cell, Maze, Builder, Strategy, Solver)
|
||||
maze_generator.py - генерация тестовых лабиринтов
|
||||
mazes/ - текстовые файлы лабиринтов
|
||||
results/
|
||||
results_maze.csv - результаты экспериментов
|
||||
benchmark_plot.png - графики
|
||||
docs/
|
||||
report1.md - отчёт
|
||||
mermaid.png - диаграмма классов
|
||||
```
|
||||
|
||||
### Применённые паттерны проектирования
|
||||
**1. Builder** - класс `TextFileMazeBuilder` реализует интерфейс `MazeBuilder`.
|
||||
|
||||
Построение лабиринта из файла включает несколько шагов: чтение строк, обход символов, создание объектов `Cell`, поиск стартовой и конечной клетки. Без Builder вся эта логика оказалась бы в `main.py` или в конструкторе `Maze`. Builder скрывает детали создания от клиента. Если понадобится загружать лабиринт из JSON или бинарного файла - достаточно написать новый класс, реализующий тот же интерфейс `MazeBuilder`.
|
||||
|
||||
**2. Strategy** - классы `BFSStrategy`, `DFSStrategy`, `AStarStrategy` реализуют интерфейс `PathFindingStrategy`.
|
||||
|
||||
Алгоритм поиска можно менять во время работы программы через `MazeSolver.set_strategy()`, не трогая остальной код. Добавление нового алгоритма - это написание одного нового класса с методом `find_path()`. Без Strategy в `solve()` пришлось бы писать if/elif для каждого алгоритма.
|
||||
|
||||
**3. Observer** - интерфейс `Observer` с методом `update(event)`.
|
||||
|
||||
`MazeSolver` хранит список наблюдателей и уведомляет их при событиях `search_started`, `path_found`, `path_not_found`. Это позволяет добавлять отображение в консоль, запись в лог или GUI-уведомления, не меняя код солвера. Слабая связанность: солвер не знает, кто его слушает.
|
||||
|
||||
### Диаграмма классов
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 2. Листинги ключевых классов
|
||||
### Cell и Maze
|
||||
|
||||
```python
|
||||
class Cell:
|
||||
def __init__(self, x, y, is_wall=False, is_start=False, is_exit=False):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.is_wall = is_wall
|
||||
self.is_start = is_start
|
||||
self.is_exit = is_exit
|
||||
|
||||
def is_passable(self):
|
||||
return not self.is_wall
|
||||
|
||||
class Maze:
|
||||
def get_neighbors(self, cell):
|
||||
result = []
|
||||
for dx, dy in [(0,-1),(0,1),(-1,0),(1,0)]:
|
||||
n = self.get_cell(cell.x + dx, cell.y + dy)
|
||||
if n and n.is_passable():
|
||||
result.append(n)
|
||||
return result
|
||||
```
|
||||
|
||||
### Паттерн Builder
|
||||
```python
|
||||
class MazeBuilder(ABC):
|
||||
@abstractmethod
|
||||
def build_from_file(self, filename) -> Maze:
|
||||
pass
|
||||
|
||||
class TextFileMazeBuilder(MazeBuilder):
|
||||
def build_from_file(self, filename) -> Maze:
|
||||
with open(filename, encoding="utf-8") as f:
|
||||
lines = [l.rstrip("\n") for l in f]
|
||||
# ... парсинг символов, создание Cell, поиск S и E
|
||||
return Maze(cells, width, height, start, exit_cell)
|
||||
```
|
||||
|
||||
### Паттерн Strategy - алгоритм A*
|
||||
```python
|
||||
class AStarStrategy(PathFindingStrategy):
|
||||
@staticmethod
|
||||
def _h(a, b):
|
||||
return abs(a.x - b.x) + abs(a.y - b.y)
|
||||
|
||||
def find_path(self, maze, start, end):
|
||||
heap = [(0, 0, start)]
|
||||
parent = {start: None}
|
||||
g = {start: 0}
|
||||
closed = set()
|
||||
while heap:
|
||||
_, _, cur = heapq.heappop(heap)
|
||||
if cur in closed:
|
||||
continue
|
||||
closed.add(cur)
|
||||
if cur == end:
|
||||
return self._build_path(parent, start, end)
|
||||
for nb in maze.get_neighbors(cur):
|
||||
ng = g[cur] + 1
|
||||
if ng < g.get(nb, float("inf")):
|
||||
g[nb] = ng
|
||||
heapq.heappush(heap, (ng + self._h(nb, end), id(nb), nb))
|
||||
parent[nb] = cur
|
||||
return []
|
||||
```
|
||||
|
||||
### MazeSolver
|
||||
```python
|
||||
class MazeSolver:
|
||||
def __init__(self, maze, strategy):
|
||||
self.maze = maze
|
||||
self.strategy = strategy
|
||||
|
||||
def set_strategy(self, strategy):
|
||||
self.strategy = strategy
|
||||
|
||||
def solve(self) -> SearchStats:
|
||||
t0 = time.perf_counter()
|
||||
path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
|
||||
t1 = time.perf_counter()
|
||||
return SearchStats(
|
||||
strategy=self.strategy.name,
|
||||
time_ms=(t1 - t0) * 1000,
|
||||
visited=self.strategy._visited,
|
||||
path_length=len(path),
|
||||
path=path,
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Результаты экспериментов
|
||||
Каждый алгоритм запускался 7 раз на каждом лабиринте, результаты усреднялись.
|
||||
### Таблица результатов
|
||||
|
||||
| Лабиринт | Алгоритм | Время (мс) | Посещено клеток | Длина пути |
|
||||
|----------|----------|-----------|----------------|------------|
|
||||
| small (11x11) | BFS | 0.070 | 39 | 33 |
|
||||
| small (11x11) | DFS | 0.055 | 33 | 33 |
|
||||
| small (11x11) | A* | 0.112 | 35 | 33 |
|
||||
| medium (51x51) | BFS | 1.391 | 793 | 497 |
|
||||
| medium (51x51) | DFS | 0.949 | 515 | 497 |
|
||||
| medium (51x51) | A* | 2.271 | 707 | 497 |
|
||||
| large (101x101) | BFS | 6.231 | 3533 | 1613 |
|
||||
| large (101x101) | DFS | 3.341 | 1957 | 1613 |
|
||||
| large (101x101) | A* | 11.27 | 3379 | 1613 |
|
||||
| empty (51x21) | BFS | 1.992 | 931 | 67 |
|
||||
| empty (51x21) | DFS | 1.021 | 451 | 451 |
|
||||
| empty (51x21) | A* | 3.527 | 931 | 67 |
|
||||
| no_exit (11x11) | BFS | 0.079 | 40 | - |
|
||||
| no_exit (11x11) | DFS | 0.077 | 40 | - |
|
||||
| no_exit (11x11) | A* | 0.140 | 40 | - |
|
||||
|
||||
### Графики
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 4. Анализ эффективности алгоритмов и применимости паттернов
|
||||
### Алгоритмы
|
||||
|
||||
**BFS** гарантирует кратчайший путь по числу шагов. Расширяет узлы слой за слоем во всех направлениях, поэтому посещает наибольшее число клеток. На практике это надёжный выбор когда нужен точно кратчайший маршрут.
|
||||
|
||||
**DFS** посещает меньше клеток и выполняется быстрее - на large лабиринте в 1.8 раза быстрее BFS. Однако путь может быть далеко не кратчайшим. На пустом лабиринте DFS нашёл путь длиной 451 шаг, тогда как BFS и A* - 67. Это связано с тем, что DFS уходит в первое попавшееся направление и возвращается только в тупике.
|
||||
|
||||
**A*** использует манхэттенскую эвристику h = |x1-x2| + |y1-y2| и должен в теории посещать меньше клеток чем BFS. На лабиринтах, сгенерированных алгоритмом recursive backtracker, выигрыш небольшой (примерно 5%). Причина: backtracker строит дерево - между любыми двумя клетками ровно один путь, тупиков нет, эвристика не помогает их обходить. На лабиринтах с циклами A* посещает заметно меньше клеток. Накладные расходы на работу с heap и closed-set делают A* медленнее по времени, чем DFS.
|
||||
|
||||
На пустом лабиринте (без стен) A* ведёт себя как BFS. Математически: f(x,y) = g + h = (x-1+y-1) + (W-x+H-y) = const для всех клеток. Все узлы неразличимы по приоритету.
|
||||
|
||||
На лабиринте без выхода все три алгоритма посещают одинаковое число клеток и корректно возвращают пустой путь.
|
||||
|
||||
### Паттерны
|
||||
**Builder** оказался полезным при добавлении нового типа лабиринта (взвешенного, с символами s и m). Изменения были внесены только в `TextFileMazeBuilder`, клиентский код не менялся.
|
||||
|
||||
**Strategy** позволил в одном цикле запустить все три алгоритма через `solver.set_strategy(strategy)`. Без паттерна пришлось бы либо дублировать код запуска для каждого алгоритма, либо писать условные ветки.
|
||||
|
||||
**Observer** полезен при расширении: чтобы добавить вывод в лог или консоль, достаточно написать новый Observer и подписать его на solver, не меняя `MazeSolver`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Выводы
|
||||
ООП и паттерны позволили сделать код гибким в нескольких направлениях.
|
||||
|
||||
Добавление нового алгоритма поиска сводится к написанию одного класса, реализующего `find_path()`. Без Strategy пришлось бы добавлять ветку в `solve()` и во все места, где запускается поиск.
|
||||
|
||||
Добавление нового формата лабиринта - только новый класс Builder. Без паттерна логика парсинга была бы перемешана с логикой работы программы.
|
||||
|
||||
Добавление нового способа отображения (GUI, запись в файл) - только новый Observer. Без него MazeSolver пришлось бы напрямую вызывать функции отображения, что создало бы зависимость от конкретной реализации.
|
||||
|
||||
Без применения паттернов код решал бы задачу, но любое изменение требовало бы правки в нескольких местах сразу. С паттернами каждый класс отвечает за одну задачу и не знает о деталях реализации соседних классов.
|
||||
165
VasilevIA/lab2/main.py
Normal file
165
VasilevIA/lab2/main.py
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
import csv
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "codes"))
|
||||
|
||||
from maze import TextFileMazeBuilder, MazeSolver, BFSStrategy, DFSStrategy, AStarStrategy
|
||||
from maze_generator import generate_all
|
||||
|
||||
try:
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
HAS_PLT = True
|
||||
except ImportError:
|
||||
HAS_PLT = False
|
||||
|
||||
BASE_DIR = os.path.dirname(__file__)
|
||||
MAZES_DIR = os.path.join(BASE_DIR, "mazes")
|
||||
RESULTS_DIR = os.path.join(BASE_DIR, "results")
|
||||
RUNS = 7
|
||||
|
||||
MAZE_FILES = [
|
||||
("small", "small.txt"),
|
||||
("medium", "medium.txt"),
|
||||
("large", "large.txt"),
|
||||
("empty", "empty.txt"),
|
||||
("no_exit", "no_exit.txt"),
|
||||
]
|
||||
|
||||
|
||||
def run():
|
||||
os.makedirs(RESULTS_DIR, exist_ok=True)
|
||||
|
||||
if not os.path.exists(MAZES_DIR) or not os.listdir(MAZES_DIR):
|
||||
generate_all(MAZES_DIR)
|
||||
|
||||
strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy()]
|
||||
builder = TextFileMazeBuilder()
|
||||
all_results = []
|
||||
|
||||
for label, filename in MAZE_FILES:
|
||||
path = os.path.join(MAZES_DIR, filename)
|
||||
if not os.path.exists(path):
|
||||
continue
|
||||
|
||||
maze = builder.build_from_file(path)
|
||||
print(f"\nMaze: {label} ({maze.width}x{maze.height})")
|
||||
|
||||
solver = MazeSolver(maze, strategies[0])
|
||||
|
||||
for strategy in strategies:
|
||||
solver.set_strategy(strategy)
|
||||
times, visited_list, lengths = [], [], []
|
||||
|
||||
for _ in range(RUNS):
|
||||
stats = solver.solve()
|
||||
times.append(stats.time_ms)
|
||||
visited_list.append(stats.visited)
|
||||
lengths.append(stats.path_length)
|
||||
|
||||
avg_time = sum(times) / RUNS
|
||||
avg_visited = sum(visited_list) / RUNS
|
||||
avg_len = sum(lengths) / RUNS
|
||||
|
||||
found = f"length={avg_len:.0f}" if avg_len > 0 else "not found"
|
||||
print(f" {strategy.name:<6} time={avg_time:.4f} ms visited={avg_visited:.0f} {found}")
|
||||
|
||||
all_results.append({
|
||||
"maze": label,
|
||||
"strategy": strategy.name,
|
||||
"time_ms": round(avg_time, 4),
|
||||
"visited_cells": round(avg_visited, 1),
|
||||
"path_length": round(avg_len, 1),
|
||||
})
|
||||
|
||||
save_csv(all_results)
|
||||
save_plots(all_results)
|
||||
show_sample()
|
||||
print("\nDone. See results/ and docs/")
|
||||
|
||||
|
||||
def save_csv(results):
|
||||
path = os.path.join(RESULTS_DIR, "results_maze.csv")
|
||||
with open(path, "w", newline="", encoding="utf-8") as f:
|
||||
writer = csv.DictWriter(
|
||||
f, fieldnames=["maze", "strategy", "time_ms", "visited_cells", "path_length"]
|
||||
)
|
||||
writer.writeheader()
|
||||
writer.writerows(results)
|
||||
print(f"\nCSV saved: {path}")
|
||||
|
||||
|
||||
def save_plots(results):
|
||||
if not HAS_PLT:
|
||||
return
|
||||
|
||||
mazes = list(dict.fromkeys(r["maze"] for r in results))
|
||||
strategies = list(dict.fromkeys(r["strategy"] for r in results))
|
||||
colors = ["#2196F3", "#FF5722", "#4CAF50"]
|
||||
|
||||
def val(maze, strat, key):
|
||||
for r in results:
|
||||
if r["maze"] == maze and r["strategy"] == strat:
|
||||
return float(r[key])
|
||||
return 0.0
|
||||
|
||||
metrics = [
|
||||
("time_ms", "Time (ms)"),
|
||||
("visited_cells", "Visited cells"),
|
||||
("path_length", "Path length"),
|
||||
]
|
||||
|
||||
fig, axes = plt.subplots(
|
||||
len(metrics), len(mazes),
|
||||
figsize=(3.5 * len(mazes), 4 * len(metrics))
|
||||
)
|
||||
|
||||
def fmt(v):
|
||||
if v == 0:
|
||||
return "0"
|
||||
if v >= 100:
|
||||
return f"{v:.0f}"
|
||||
if v >= 1:
|
||||
return f"{v:.2f}"
|
||||
return f"{v:.3f}"
|
||||
|
||||
for row_i, (key, ylabel) in enumerate(metrics):
|
||||
for col_i, maze in enumerate(mazes):
|
||||
ax = axes[row_i][col_i]
|
||||
vals = [val(maze, s, key) for s in strategies]
|
||||
bars = ax.bar(strategies, vals, color=colors[:len(strategies)])
|
||||
if row_i == 0:
|
||||
ax.set_title(maze, fontsize=9)
|
||||
if col_i == 0:
|
||||
ax.set_ylabel(ylabel)
|
||||
for bar, v in zip(bars, vals):
|
||||
ax.text(
|
||||
bar.get_x() + bar.get_width() / 2,
|
||||
bar.get_height() * 1.02,
|
||||
fmt(v), ha="center", va="bottom", fontsize=7
|
||||
)
|
||||
ax.tick_params(axis="x", labelsize=8)
|
||||
|
||||
plt.tight_layout()
|
||||
out = os.path.join(RESULTS_DIR, "benchmark_plot.png")
|
||||
plt.savefig(out, dpi=120)
|
||||
plt.close()
|
||||
print(f"Chart saved: {out}")
|
||||
|
||||
|
||||
def show_sample():
|
||||
path = os.path.join(MAZES_DIR, "sample.txt")
|
||||
if not os.path.exists(path):
|
||||
return
|
||||
builder = TextFileMazeBuilder()
|
||||
maze = builder.build_from_file(path)
|
||||
solver = MazeSolver(maze, BFSStrategy())
|
||||
stats = solver.solve()
|
||||
print("\nSample maze with BFS path:")
|
||||
print(maze.render(path=stats.path))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
21
VasilevIA/lab2/mazes/empty.txt
Normal file
21
VasilevIA/lab2/mazes/empty.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
###################################################
|
||||
#S #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# E#
|
||||
###################################################
|
||||
101
VasilevIA/lab2/mazes/large.txt
Normal file
101
VasilevIA/lab2/mazes/large.txt
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#####################################################################################################
|
||||
#S # # # # # # # # # # # # #
|
||||
### # # ##### # ### # # ##### ### ### ############# # # # # ##### # ######### ### # # ##### ### # ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # ##### ### ### # # ########### ####### # ####### ### ### # # # ####### ##### # # ### # # ##### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ##### # ### ### # ### # ### # # ##### # ##### ####### ### ### # ### ### ### # ### ### # ##### # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # ### # # ### # ##### ##### # ### # # ##### # # ####### ######### ### # # ######### # # # #####
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ##### ### # ##### # ##### ### # ##### ######### # # ####### ####### # ### # # ########### ##### #
|
||||
# # # # # # # # # # # # # # # # # # # # # #
|
||||
##### ##### ########### ### # # ########### # # # # ##### ####### # ####### # ##### ### ### ### ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||
####### # ### # # ####### ##### ### ##### # # ##### # ##### ### # # # ######### # ### ### ### ### ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### # ########### # # # ### ### ##### ##### # # ##### # # # ##### # # ##### ##### # ##### ### ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ### # ### # ### ### ### ####### # ########### ####### # # ##### # # ### ########### ### ### # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ####### # # # # ##### ### # ####### # ##### ##### ##### # # # ### ### # # ##### ##### ### #######
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
########### # # ##### # # # ####### # ### # ##### # ##### ### # # # # # ####### # ##### ### # # ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### # # ### # # ##### # # # # # ##### # # # ############# # ##### # ############### ### # # ### # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ####### ##### # ########### # # # ######### ### ############# # ####### ####################### #
|
||||
# # # # # # # # # # # # # # # # # # # # #
|
||||
# # ### ### # # # # # ### # ##### # ### # ####### # ##### # ######### ####### # ##### # ########### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ##### ##### ######### ####### ##### # # # # # ### # ##### # ### # ### # ##### # # # # # ### # # ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### # # # ### ##### ### # # ### ##### ##### ####### # # ##### # ### ##### ####### # # # ####### # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # ### ####### # ### # # ### ### ##### ##### # # # # ### # # # ####### # # ### ##### # # ####### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
##### # # # # # ### # ### ### ### ### # ### # ### ##### # # ########### # # ### # # ##### ##### # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### ##### ### ### # # ### ### # # ##### ### # ##### ########### ### ### ### # # # # ### ######### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ### # ### ### # # ####### # # ######### ### # ########### # ### ### ##### ### # ### # # ### ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # ######### # ####### ##### ######### # ### ### # ##### # ### ### ### # ### ### # ##### ##### # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # ### ### # # # # # ##### # # # # ####### # # # ### # ### # ### ### ##### ### # # ### # # # ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### # ### ######### # ##### # ##### # # ############# # # ### ####### # # ##### # # # # # # ### # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ##### ####### ### # ### ##### # # # ##### # ####### ### # # ### # # # # ### ### # ########### # ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ####### # ##### # ### # ##### # # ##### # ##### # ### ### ######### ##### ### # ##### # ######### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
### # # # ### ####### # # # # ##### # ############### # # ##### ####### ##### # ### # # # # ### # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### ########### ### # ############# # # # ### # ##### ##### ### ####### # ### # # # ##### ### # ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
### ##### ##### ### ### ### # # # # ##### # # ####### # # # ####### # # ### # ### # # # # ### ##### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### # ### # # # ####### # # # # ### ####### # # # ##### ### # # ##### # ##### # # ### ### # # ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # ### # ### # # # # ### ### ### # # ### ### # ##### ### # # ##### ####### # ##### # ##### ### # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### # # ### ####### ### # # ### ##### # # ######### ### ### # # # # # ### # ### # # ### ### # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
### # # ### ### ##### ######### ######### # # # # # ########### ####### # # ### ########### ##### # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # ### ### ### # ### # # ######### # # # # ##### # # # ##### # ####### ####### # ##### # # ##### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ### ### ##### ####### # # ### ######### ### ### # # ########### # ##### # ### ##### ##### # ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ####### # # ##### ### # ### # ####### # # # ### ### # # ### # ####### # ##### ### # ######### #####
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # # # # # ##### # ####### ####### # ########### ####### ######### # ### # ### # ### # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### # # ####### # ### ########### # # ######### ### # # # # # # ### # ######### # ### ########### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # #
|
||||
### ####### # # # # # ### # # # ######### # # # ########################### # # ##### ####### # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### ### ############### ####### ##### # ########### # ### # # ##### ### # ##### # ##### # ### # ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ########### ##### ### ####### # # # ##### # # # # # ### # # ### ### # ### ### # ##### # # # # ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ##### # ### # ### # # ### # # ####### ####### # ### ##### ##### ##### ### ##### # ####### #######
|
||||
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # # # ##### ####### ### # # # ############# # # # # ##### ### ######### # ######### # # ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
### # ##### ##### # ### # ### ####### # ####### # ### ##### # ######### ##### # # ####### ### ##### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### # # # # ##### # ##### # ### # ### # ### ##### ### ##### ########### ### # ########### # # #####
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # ##### # # # # # # # # ######### ### # ### # # ### # # # ### # # ### # # # # ### # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ### # ### ######### ### ### ######### # ### # # ### # ### # # ##### # # ######### ### ### ##### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ##### ##### # # ####### ### ### # ##### ##### ##### # # ##### ##### ############# # # ### ### ### #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# ### # # # ### ### # # ### # # ####### ##### # # # # # ### ##### ##### # ### ##### # # ######### ###
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ##### ### ### # # ### ##### ##### ##### # ##### ### # ### # ### ##### ##### # ##### # # # # # # #
|
||||
# # # # # # # # # # # # # E#
|
||||
#####################################################################################################
|
||||
51
VasilevIA/lab2/mazes/medium.txt
Normal file
51
VasilevIA/lab2/mazes/medium.txt
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
###################################################
|
||||
#S# # # # # # # #
|
||||
# ##### # # # # # # # ####### # ### # ### ##### # #
|
||||
# # # # # # # # # # # # # # # # # #
|
||||
### # ### # # ####### # ### # # # # ### # ### ### #
|
||||
# # # # # # # # # # # # # #
|
||||
# # # # # # ####### ####### # ### ######### ##### #
|
||||
# # # # # # # # # # # # # #
|
||||
# # # ##### # # # ### ####### ##### ##### # ### ###
|
||||
# # # # # # # # # # # # # # # # #
|
||||
# # ##### ### # ### ### # # ### # ##### # ### # # #
|
||||
# # # # # # # # # # # # # # # # #
|
||||
# # # # # # ### # ### ### ##### # # # ####### # # #
|
||||
# # # # # # # # # # # # # # #
|
||||
# ######### # # # # ### # # # ### # ####### # ### #
|
||||
# # # # # # # # # # # # # # # # # #
|
||||
# # ### # ### # # ### ### ### # ### # # # ### # # #
|
||||
# # # # # # # # # # # # # # #
|
||||
# ### ### # ### ####### ######### # ### ####### ###
|
||||
# # # # # # # # # # # # # # #
|
||||
# # ### ### # ### # ####### # # # # # ####### # # #
|
||||
# # # # # # # # # # # # # #
|
||||
# ####### # ############# ##### # # ####### # ### #
|
||||
# # # # # # # # # # #
|
||||
# # # # # ################# ########### # # ##### #
|
||||
# # # # # # # # # # #
|
||||
# ####### # ### ##### # # ### ### ######### # #####
|
||||
# # # # # # # # # #
|
||||
####### # ####### ##### ### ##### # ############# #
|
||||
# # # # # # # # # # # #
|
||||
##### ####### # # ### # ##### # # ##### ##### # # #
|
||||
# # # # # # # # # # # # # # #
|
||||
## #### # # # ##### ##### # ### ### # # # ##### # #
|
||||
# # # # # # # # # # # # # #
|
||||
# ####### # ### # ### # # # ################# ### #
|
||||
# # # # # # # # # #
|
||||
### ##### ########### # # ############# ### ### ###
|
||||
# # # # # # # # # # # #
|
||||
# ### ### # ####### # # ### # ####### # # ### ### #
|
||||
# # # # # # # # # # # # # # #
|
||||
# # ######### ### # # # # # ### ### ####### # ### #
|
||||
# # # # # # # # # # # # # #
|
||||
# ##### # ##### # ##### ##### ### ####### ##### ###
|
||||
# # # # # # # # # # # #
|
||||
# # # # # # ##### ### ########### # # ####### ### #
|
||||
# # # # # # # # # # # # # # #
|
||||
# # ### ##### # ### ##### ##### ##### # # ### # # #
|
||||
# # # # # # # # # # # # #
|
||||
### # ### ##### ####### ##### ##### ####### # # # #
|
||||
# # # # # #E#
|
||||
###################################################
|
||||
11
VasilevIA/lab2/mazes/no_exit.txt
Normal file
11
VasilevIA/lab2/mazes/no_exit.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
###########
|
||||
#S# #E#
|
||||
# #########
|
||||
# # #
|
||||
##### # ###
|
||||
# # #
|
||||
# ####### #
|
||||
# # #
|
||||
### ### # #
|
||||
# # #
|
||||
###########
|
||||
15
VasilevIA/lab2/mazes/sample.txt
Normal file
15
VasilevIA/lab2/mazes/sample.txt
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
###############
|
||||
#S# # #
|
||||
# ### # # ### #
|
||||
# # # # # #
|
||||
### ### ### # #
|
||||
# # # # # #
|
||||
# ### # # ### #
|
||||
# # # # #
|
||||
## ###### # # #
|
||||
# # # #
|
||||
# ### #########
|
||||
# # # #
|
||||
# # ##### # # #
|
||||
# # #E#
|
||||
###############
|
||||
11
VasilevIA/lab2/mazes/small.txt
Normal file
11
VasilevIA/lab2/mazes/small.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
###########
|
||||
#S # # #
|
||||
##### # #
|
||||
# # # #
|
||||
# ####### #
|
||||
# # # #
|
||||
# ### # # #
|
||||
# # # #
|
||||
### # ### #
|
||||
# # E#
|
||||
###########
|
||||
BIN
VasilevIA/lab2/results/benchmark_plot.png
Normal file
BIN
VasilevIA/lab2/results/benchmark_plot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 125 KiB |
16
VasilevIA/lab2/results/results_maze.csv
Normal file
16
VasilevIA/lab2/results/results_maze.csv
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
maze,strategy,time_ms,visited_cells,path_length
|
||||
small,BFS,0.0676,39.0,33.0
|
||||
small,DFS,0.061,33.0,33.0
|
||||
small,A*,0.1093,35.0,33.0
|
||||
medium,BFS,1.4027,793.0,497.0
|
||||
medium,DFS,0.8985,515.0,497.0
|
||||
medium,A*,2.3001,707.0,497.0
|
||||
large,BFS,6.1605,3533.0,1613.0
|
||||
large,DFS,3.3919,1957.0,1613.0
|
||||
large,A*,11.2172,3379.0,1613.0
|
||||
empty,BFS,1.7583,931.0,67.0
|
||||
empty,DFS,1.0076,451.0,451.0
|
||||
empty,A*,3.4836,931.0,67.0
|
||||
no_exit,BFS,0.067,40.0,0.0
|
||||
no_exit,DFS,0.0599,40.0,0.0
|
||||
no_exit,A*,0.1099,40.0,0.0
|
||||
|
Loading…
Reference in New Issue
Block a user