[1, 2] laba1 laba2 #362

Open
larikovaaa wants to merge 9 commits from larikovaaa/2026-rff_mp:LarikovaAA into develop
31 changed files with 1523 additions and 0 deletions

2
LarikovaAA/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.DS_Store
../.DS_Store

BIN
LarikovaAA/task1/.DS_Store vendored Normal file

Binary file not shown.

83
LarikovaAA/task1/bst.py Normal file
View File

@ -0,0 +1,83 @@
def bst_insert(root, name, phone):
new_node = {'name': name, 'phone': phone, 'left': None, 'right': None}
if root is None:
return new_node
current = root
while True:
if name < current['name']:
if current['left'] is None:
current['left'] = new_node
break
else:
current = current['left']
elif name > current['name']:
if current['right'] is None:
current['right'] = new_node
break
else:
current = current['right']
else:
current['phone'] = phone
break
return root
def bst_find(root, name): #Итеративный поиск в BST
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(root): #Поиск минимального узла
current = root
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']
else:
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=None): #Возвращает отсортированные записи
if records is None:
records = []
stack = []
current = root
while stack or current:
while current is not None:
stack.append(current)
current = current['left']
current = stack.pop()
records.append((current['name'], current['phone']))
current = current['right']
return records

View File

@ -0,0 +1,3 @@
N = 10000 # Количество записей
REPEATS = 5 # Количество повторений каждого эксперимента
HASH_TABLE_SIZE = 1000 # Размер хеш-таблицы

View File

@ -0,0 +1,20 @@
import random
def generate_test_data(N): #Генерирует N записей с именами User_00000 ... User_N-1
records = [(f"User_{i:05d}", f"+7-999-{i:05d}") for i in range(N)]
records_shuffled = records.copy()
random.shuffle(records_shuffled)
records_sorted = sorted(records, key=lambda x: x[0])
return records, records_shuffled, records_sorted
def get_names_for_operations(records, num_find=100, num_delete=50, num_nonexistent=10): #Подготавливает имена для операций поиска и удаления
existing_names = [name for name, _ in records[:num_find + num_delete]]
names_to_find = existing_names[:num_find] + [f"None_{i}" for i in range(num_nonexistent)]
names_to_delete = existing_names[num_find:num_find + num_delete]
return names_to_find, names_to_delete

View File

@ -0,0 +1,19 @@
Структура,Режим,Операция,Повтор1,Повтор2,Повтор3,Повтор4,Повтор5,Среднее,Стд_откл
linkedlist,случайный,вставка,3.030525333015248,3.011153083993122,3.067337290965952,3.026814332988579,3.0305452499887906,3.0332750581903385,0.018473609585193725
linkedlist,случайный,поиск,0.023072624986525625,0.023000167042482644,0.023063208034727722,0.023132542031817138,0.023077582998666912,0.02306922501884401,4.2179776992719985e-05
linkedlist,случайный,удаление,0.01565887499600649,0.01571191701805219,0.01575241598766297,0.015799874963704497,0.015716666996013373,0.015727949992287903,4.674933972158777e-05
linkedlist,отсортированный,вставка,2.7900312499841675,2.842725833004806,2.8324795000371523,2.8226387499598786,2.824206833029166,2.822416433203034,0.01769628256337186
linkedlist,отсортированный,поиск,0.0026340839685872197,0.0026223339955322444,0.002628166985232383,0.0026764170033857226,0.0026917500072158873,0.0026505503919906914,2.805286048400875e-05
linkedlist,отсортированный,удаление,0.00025987502885982394,0.00026162504218518734,0.0002579580177552998,0.00026395899476483464,0.00025770795764401555,0.00026022500824183223,2.3452089690404486e-06
hashtable,случайный,вставка,0.19747637503314763,0.1957802499528043,0.195026625005994,0.1953300409950316,0.1995006250217557,0.19662278320174664,0.001669692081198908
hashtable,случайный,поиск,0.0007510409923270345,0.0007478750194422901,0.0007428750395774841,0.0007420409820042551,0.0007448329706676304,0.0007457330008037389,3.327822630240364e-06
hashtable,случайный,удаление,0.00037333305226638913,0.0003679579822346568,0.0003666249685920775,0.000368500011973083,0.00036679196637123823,0.00036864159628748896,2.448879400144638e-06
hashtable,отсортированный,вставка,0.19373183301649988,0.19131775002460927,0.20354575000237674,0.19244924996746704,0.19410508294822648,0.19502993319183587,0.004370349591521971
hashtable,отсортированный,поиск,7.366598583757877e-05,7.358303992077708e-05,7.379200542345643e-05,7.329200161620975e-05,7.29589955881238e-05,7.345840567722917e-05,2.9900259428386626e-07
hashtable,отсортированный,удаление,5.0292001105844975e-05,5.037500523030758e-05,5.124998278915882e-05,5.0540955271571875e-05,5.050003528594971e-05,5.059159593656659e-05,3.409075164884719e-07
bst,случайный,вставка,0.012036708008963615,0.011527249997016042,0.011410709004849195,0.011799749976489693,0.011473792023025453,0.011649641802068799,0.00023466763426973204
bst,случайный,поиск,6.741698598489165e-05,8.07500327937305e-05,6.450002547353506e-05,6.416701944544911e-05,6.570800906047225e-05,6.850841455161572e-05,6.225842896923898e-06
bst,случайный,удаление,5.729199619963765e-05,5.966599564999342e-05,5.4165953770279884e-05,5.4958974942564964e-05,5.529198097065091e-05,5.627498030662537e-05,1.9839081193124104e-06
bst,отсортированный,вставка,3.063095625024289,3.0107702090172097,3.0406965000438504,2.9900419160258025,3.014387540984899,3.02379835821921,0.025407148276338564
bst,отсортированный,поиск,0.0002975420211441815,0.0002921670093201101,0.00029941595857962966,0.0002905830042436719,0.00029925000853836536,0.0002957916003651917,3.6993963362568915e-06
bst,отсортированный,удаление,0.0003683330141939223,0.00036570901283994317,0.00037129101110622287,0.0003595000016503036,0.0003665830008685589,0.00036628320813179014,3.891304706952938e-06
1 Структура Режим Операция Повтор1 Повтор2 Повтор3 Повтор4 Повтор5 Среднее Стд_откл
2 linkedlist случайный вставка 3.030525333015248 3.011153083993122 3.067337290965952 3.026814332988579 3.0305452499887906 3.0332750581903385 0.018473609585193725
3 linkedlist случайный поиск 0.023072624986525625 0.023000167042482644 0.023063208034727722 0.023132542031817138 0.023077582998666912 0.02306922501884401 4.2179776992719985e-05
4 linkedlist случайный удаление 0.01565887499600649 0.01571191701805219 0.01575241598766297 0.015799874963704497 0.015716666996013373 0.015727949992287903 4.674933972158777e-05
5 linkedlist отсортированный вставка 2.7900312499841675 2.842725833004806 2.8324795000371523 2.8226387499598786 2.824206833029166 2.822416433203034 0.01769628256337186
6 linkedlist отсортированный поиск 0.0026340839685872197 0.0026223339955322444 0.002628166985232383 0.0026764170033857226 0.0026917500072158873 0.0026505503919906914 2.805286048400875e-05
7 linkedlist отсортированный удаление 0.00025987502885982394 0.00026162504218518734 0.0002579580177552998 0.00026395899476483464 0.00025770795764401555 0.00026022500824183223 2.3452089690404486e-06
8 hashtable случайный вставка 0.19747637503314763 0.1957802499528043 0.195026625005994 0.1953300409950316 0.1995006250217557 0.19662278320174664 0.001669692081198908
9 hashtable случайный поиск 0.0007510409923270345 0.0007478750194422901 0.0007428750395774841 0.0007420409820042551 0.0007448329706676304 0.0007457330008037389 3.327822630240364e-06
10 hashtable случайный удаление 0.00037333305226638913 0.0003679579822346568 0.0003666249685920775 0.000368500011973083 0.00036679196637123823 0.00036864159628748896 2.448879400144638e-06
11 hashtable отсортированный вставка 0.19373183301649988 0.19131775002460927 0.20354575000237674 0.19244924996746704 0.19410508294822648 0.19502993319183587 0.004370349591521971
12 hashtable отсортированный поиск 7.366598583757877e-05 7.358303992077708e-05 7.379200542345643e-05 7.329200161620975e-05 7.29589955881238e-05 7.345840567722917e-05 2.9900259428386626e-07
13 hashtable отсортированный удаление 5.0292001105844975e-05 5.037500523030758e-05 5.124998278915882e-05 5.0540955271571875e-05 5.050003528594971e-05 5.059159593656659e-05 3.409075164884719e-07
14 bst случайный вставка 0.012036708008963615 0.011527249997016042 0.011410709004849195 0.011799749976489693 0.011473792023025453 0.011649641802068799 0.00023466763426973204
15 bst случайный поиск 6.741698598489165e-05 8.07500327937305e-05 6.450002547353506e-05 6.416701944544911e-05 6.570800906047225e-05 6.850841455161572e-05 6.225842896923898e-06
16 bst случайный удаление 5.729199619963765e-05 5.966599564999342e-05 5.4165953770279884e-05 5.4958974942564964e-05 5.529198097065091e-05 5.627498030662537e-05 1.9839081193124104e-06
17 bst отсортированный вставка 3.063095625024289 3.0107702090172097 3.0406965000438504 2.9900419160258025 3.014387540984899 3.02379835821921 0.025407148276338564
18 bst отсортированный поиск 0.0002975420211441815 0.0002921670093201101 0.00029941595857962966 0.0002905830042436719 0.00029925000853836536 0.0002957916003651917 3.6993963362568915e-06
19 bst отсортированный удаление 0.0003683330141939223 0.00036570901283994317 0.00037129101110622287 0.0003595000016503036 0.0003665830008685589 0.00036628320813179014 3.891304706952938e-06

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View File

@ -0,0 +1,89 @@
# Отчёт по лабораторной работе
## Цель работы
Реализовать три структуры данных «с нуля» (связный список, хеш-таблица, двоичное дерево поиска), применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций.
## Параметры эксперимента
- Количество записей: 10000
- Количество повторов каждого теста: 5
- Размер хеш-таблицы: 1000 корзин
## Результаты экспериментов
### 1. Связный список
| Режим | Вставка (сек) | Поиск (сек) | Удаление (сек) |
|-------|---------------|-------------|----------------|
| Случайный | 3.0333 | 0.0231 | 0.0157 |
| Отсортированный | 2.8224 | 0.0027 | 0.0003 |
### 2. Хеш-таблица
| Режим | Вставка (сек) | Поиск (сек) | Удаление (сек) |
|-------|---------------|-------------|----------------|
| Случайный | 0.1966 | 0.0007 | 0.0004 |
| Отсортированный | 0.1950 | 0.0001 | 0.0001 |
### 3. Двоичное дерево поиска (BST)
| Режим | Вставка (сек) | Поиск (сек) | Удаление (сек) |
|-------|---------------|-------------|----------------|
| Случайный | 0.0116 | 0.0001 | 0.0001 |
| Отсортированный | 3.0238 | 0.0003 | 0.0004 |
## Анализ результатов
### 1. Влияние порядка данных на BST
На отсортированных данных BST деградирует с O(log n) до O(n).
Время вставки увеличилось с 0.0116 до 3.0238 секунд — в 259.6 раз.
### 2. Почему хеш-таблица не чувствительна к порядку
Хеш-функция распределяет элементы случайно, порядок ввода не влияет на позицию элемента.
Разница между случайным и отсортированным порядком:
- Вставка: 0.1966 vs 0.1950
- Отношение: 0.99x (почти не чувствительна)
### 3. Почему связный список медленный при поиске
Поиск требует последовательного прохода O(n) без возможности индексации.
Поэтому связный список хорош только когда записей мало.
Для больших телефонных справочников он не подходит.
Сравнение скорости поиска (случайные данные):
- LinkedList: 0.0231 сек
- HashTable: 0.0007 сек (в 30.9 раз быстрее)
- BST: 0.0001 сек
### 4. Сравнение удаления
| Структура | Сложность | Время на 50 удалений (случайные данные) |
|-----------|-----------|------------------------------------------|
| Связный список | O(n) | 0.0157 сек |
| Хеш-таблица | O(1) в среднем | 0.0004 сек |
| BST | O(log n) в среднем | 0.0001 сек |
## Вывод:
| Задача | Рекомендация | Почему |
|--------|-------------|--------|
| Частый поиск | Хеш-таблица | O(1) в среднем, не зависит от порядка |
| Частые вставки/удаления | Хеш-таблица | Амортизированное O(1) |
| Нужен отсортированный вывод | Сбалансированное дерево (AVL/Red-Black) | In-order обход даёт сортировку |
| Мало данных (<100 элементов) | Связный список или массив | Простота, накладные расходы не оправданы |
| Последовательный доступ (очередь/стек) | Связный список | Вставка/удаление в начало/конец за O(1) |
## Заключение
Эксперимент наглядно демонстрирует:
1. **BST без балансировки опасен** — на отсортированных данных он деградирует до O(n)
2. **Хеш-таблица стабильна** — её производительность не зависит от порядка входных данных
3. **Связный список** подходит только для специфических задач с малым объёмом данных
## Дата выполнения
2026-05-21 14:44:41

View File

@ -0,0 +1,94 @@
import time
import numpy as np
from linkedlist import ll_insert, ll_find, ll_delete
from hashtable import ht_create, ht_insert, ht_find, ht_delete
from bst import bst_insert, bst_find, bst_delete
def measure_insert(records, struct_type, params=None): #Замер времени вставки всех записей
start = time.perf_counter()
if struct_type == 'linkedlist':
head = None
for name, phone in records:
head = ll_insert(head, name, phone)
result = head
elif struct_type == 'hashtable':
size = params.get('size', 1000) if params else 1000
buckets = ht_create(size)
for name, phone in records:
ht_insert(buckets, name, phone)
result = buckets
elif struct_type == 'bst':
root = None
for name, phone in records:
root = bst_insert(root, name, phone)
result = root
end = time.perf_counter()
return end - start, result
def measure_find(structure, names_to_find, struct_type): #Замер времени поиска записей
start = time.perf_counter()
for name in names_to_find:
if struct_type == 'linkedlist':
ll_find(structure, name)
elif struct_type == 'hashtable':
ht_find(structure, name)
elif struct_type == 'bst':
bst_find(structure, name)
end = time.perf_counter()
return end - start
def measure_delete(structure, names_to_delete, struct_type): #Замер времени удаления записей
start = time.perf_counter()
for name in names_to_delete:
if struct_type == 'linkedlist':
structure = ll_delete(structure, name)
elif struct_type == 'hashtable':
ht_delete(structure, name)
elif struct_type == 'bst':
structure = bst_delete(structure, name)
end = time.perf_counter()
return end - start, structure
def run_single_experiment(struct_type, mode, data_records, names_to_find, names_to_delete, repeats, params=None): #Запуск одного эксперимента
insert_times = []
find_times = []
delete_times = []
for i in range(repeats):
if struct_type == 'hashtable':
insert_time, structure = measure_insert(data_records, struct_type, params)
else:
insert_time, structure = measure_insert(data_records, struct_type)
insert_times.append(insert_time)
find_time = measure_find(structure, names_to_find, struct_type)
find_times.append(find_time)
delete_time, structure = measure_delete(structure, names_to_delete, struct_type)
delete_times.append(delete_time)
return {
'structure': struct_type,
'mode': mode,
'insert_mean': np.mean(insert_times),
'insert_std': np.std(insert_times),
'insert_all': insert_times,
'find_mean': np.mean(find_times),
'find_std': np.std(find_times),
'find_all': find_times,
'delete_mean': np.mean(delete_times),
'delete_std': np.std(delete_times),
'delete_all': delete_times
}

View File

@ -0,0 +1,30 @@
from linkedlist import ll_insert, ll_find, ll_delete, ll_list_all
def hash_function(name, size):
return sum(ord(c) for c in name) % size
def ht_create(size):
return [None] * size
def ht_insert(buckets, name, phone):
index = hash_function(name, len(buckets))
buckets[index] = ll_insert(buckets[index], name, phone)
def ht_find(buckets, name):
index = hash_function(name, len(buckets))
return ll_find(buckets[index], name)
def ht_delete(buckets, name):
index = hash_function(name, len(buckets))
buckets[index] = ll_delete(buckets[index], name)
def ht_list_all(buckets):
records = []
for 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

View File

@ -0,0 +1,50 @@
def ll_insert(head, name, phone): #Oбновление записи в связном списке
if head is None:
return {'name': name, 'phone': phone, 'next': None}
current = head
while current is not None:
if current['name'] == name:
current['phone'] = phone
return head
current = current['next']
new_node = {'name': name, 'phone': phone, 'next': None}
current = head
while current['next'] is not None:
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

53
LarikovaAA/task1/main.py Normal file
View File

@ -0,0 +1,53 @@
from config import N, REPEATS, HASH_TABLE_SIZE
from data_generator import generate_test_data, get_names_for_operations
from experiment import run_single_experiment
from results_analyzer import save_to_csv, plot_results, print_analysis, save_report_md
def main():
print(f"Количество записей: {N}")
print(f"Количество повторов: {REPEATS}")
print(f"Размер хеш-таблицы: {HASH_TABLE_SIZE}")
print()
records, records_shuffled, records_sorted = generate_test_data(N)
names_to_find, names_to_delete = get_names_for_operations(records)
experiments = [
('linkedlist', 'случайный', records_shuffled),
('linkedlist', 'отсортированный', records_sorted),
('hashtable', 'случайный', records_shuffled),
('hashtable', 'отсортированный', records_sorted),
('bst', 'случайный', records_shuffled),
('bst', 'отсортированный', records_sorted),
]
results = []
for struct_type, mode, data_records in experiments:
print(f"Тестирование: {struct_type} - {mode}")
params = {'size': HASH_TABLE_SIZE} if struct_type == 'hashtable' else None
result = run_single_experiment(
struct_type, mode, data_records,
names_to_find, names_to_delete,
REPEATS, params
)
results.append(result)
print(f" Insert: {result['insert_mean']:.4f} ± {result['insert_std']:.4f} sec")
print(f" Find: {result['find_mean']:.4f} ± {result['find_std']:.4f} sec")
print(f" Delete: {result['delete_mean']:.4f} ± {result['delete_std']:.4f} sec")
print()
save_to_csv(results) # docs/data/results.csv
plot_results(results) # docs/performance_chart.png
save_report_md(results) # docs/report.md
print_analysis(results)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,297 @@
import csv
import os
import numpy as np
from matplotlib import pyplot as plt
def ensure_directories():
os.makedirs('docs/data', exist_ok=True)
def save_to_csv(results, filename="docs/data/results.csv"):
ensure_directories()
with open(filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['Структура', 'Режим', 'Операция',
'Повтор1', 'Повтор2', 'Повтор3', 'Повтор4', 'Повтор5',
'Среднее', 'Стд_откл'])
for res in results:
struct_name = res['structure']
mode = res['mode']
for op, times, mean, std in [
('вставка', res['insert_all'], res['insert_mean'], res['insert_std']),
('поиск', res['find_all'], res['find_mean'], res['find_std']),
('удаление', res['delete_all'], res['delete_mean'], res['delete_std'])
]:
row = [struct_name, mode, op] + times + [mean, std]
writer.writerow(row)
def plot_results(results, filename="docs/performance_chart.png"):
ensure_directories()
struct_names = {
'linkedlist': 'LinkedList',
'hashtable': 'HashTable',
'bst': 'BST'
}
operations = ['insert', 'find', 'delete']
op_names = {'insert': 'Вставка', 'find': 'Поиск', 'delete': 'Удаление'}
random_data = {}
sorted_data = {}
for res in results:
struct_name = struct_names.get(res['structure'], res['structure'])
mode = res['mode']
if mode == 'случайный':
random_data[struct_name] = {
'insert': res['insert_mean'],
'find': res['find_mean'],
'delete': res['delete_mean']
}
else:
sorted_data[struct_name] = {
'insert': res['insert_mean'],
'find': res['find_mean'],
'delete': res['delete_mean']
}
structure_order = ['LinkedList', 'HashTable', 'BST']
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
for idx, op in enumerate(operations):
ax = axes[idx]
x = np.arange(len(structure_order))
width = 0.35
random_means = []
sorted_means = []
for struct in structure_order:
if struct in random_data:
random_means.append(random_data[struct][op])
else:
random_means.append(0)
if struct in sorted_data:
sorted_means.append(sorted_data[struct][op])
else:
sorted_means.append(0)
if not random_means and not sorted_means:
print(f" Нет данных для операции {op}")
continue
bars1 = ax.bar(x - width/2, random_means, width,
label='Случайный порядок', color='skyblue')
bars2 = ax.bar(x + width/2, sorted_means, width,
label='Отсортированный порядок', color='salmon')
ax.set_xlabel('Структура данных')
ax.set_ylabel('Время (секунды)')
ax.set_title(f'{op_names.get(op, op)}')
ax.set_xticks(x)
ax.set_xticklabels(structure_order)
ax.legend()
for bar in bars1 + bars2:
height = bar.get_height()
if height > 0:
ax.annotate(f'{height:.3f}',
xy=(bar.get_x() + bar.get_width() / 2, height),
xytext=(0, 3), textcoords="offset points",
ha='center', va='bottom', fontsize=8)
plt.tight_layout()
plt.savefig(filename, dpi=150)
plt.show()
def save_report_md(results, filename="docs/report.md"):
ensure_directories()
results_dict = {}
for res in results:
key = (res['structure'], res['mode'])
results_dict[key] = res
def get_val(struct, mode, field):
key = (struct, mode)
if key in results_dict:
return results_dict[key][field]
return 0.0
ll_random_insert = get_val('linkedlist', 'случайный', 'insert_mean')
ll_random_find = get_val('linkedlist', 'случайный', 'find_mean')
ll_random_delete = get_val('linkedlist', 'случайный', 'delete_mean')
ll_sorted_insert = get_val('linkedlist', 'отсортированный', 'insert_mean')
ll_sorted_find = get_val('linkedlist', 'отсортированный', 'find_mean')
ll_sorted_delete = get_val('linkedlist', 'отсортированный', 'delete_mean')
ht_random_insert = get_val('hashtable', 'случайный', 'insert_mean')
ht_random_find = get_val('hashtable', 'случайный', 'find_mean')
ht_random_delete = get_val('hashtable', 'случайный', 'delete_mean')
ht_sorted_insert = get_val('hashtable', 'отсортированный', 'insert_mean')
ht_sorted_find = get_val('hashtable', 'отсортированный', 'find_mean')
ht_sorted_delete = get_val('hashtable', 'отсортированный', 'delete_mean')
bst_random_insert = get_val('bst', 'случайный', 'insert_mean')
bst_random_find = get_val('bst', 'случайный', 'find_mean')
bst_random_delete = get_val('bst', 'случайный', 'delete_mean')
bst_sorted_insert = get_val('bst', 'отсортированный', 'insert_mean')
bst_sorted_find = get_val('bst', 'отсортированный', 'find_mean')
bst_sorted_delete = get_val('bst', 'отсортированный', 'delete_mean')
from datetime import datetime
report_content = f"""# Отчёт по лабораторной работе
## Цель работы
Реализовать три структуры данных «с нуля» (связный список, хеш-таблица, двоичное дерево поиска), применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций.
## Параметры эксперимента
- Количество записей: 10000
- Количество повторов каждого теста: 5
- Размер хеш-таблицы: 1000 корзин
## Результаты экспериментов
### 1. Связный список
| Режим | Вставка (сек) | Поиск (сек) | Удаление (сек) |
|-------|---------------|-------------|----------------|
| Случайный | {ll_random_insert:.4f} | {ll_random_find:.4f} | {ll_random_delete:.4f} |
| Отсортированный | {ll_sorted_insert:.4f} | {ll_sorted_find:.4f} | {ll_sorted_delete:.4f} |
### 2. Хеш-таблица
| Режим | Вставка (сек) | Поиск (сек) | Удаление (сек) |
|-------|---------------|-------------|----------------|
| Случайный | {ht_random_insert:.4f} | {ht_random_find:.4f} | {ht_random_delete:.4f} |
| Отсортированный | {ht_sorted_insert:.4f} | {ht_sorted_find:.4f} | {ht_sorted_delete:.4f} |
### 3. Двоичное дерево поиска (BST)
| Режим | Вставка (сек) | Поиск (сек) | Удаление (сек) |
|-------|---------------|-------------|----------------|
| Случайный | {bst_random_insert:.4f} | {bst_random_find:.4f} | {bst_random_delete:.4f} |
| Отсортированный | {bst_sorted_insert:.4f} | {bst_sorted_find:.4f} | {bst_sorted_delete:.4f} |
## Анализ результатов
### 1. Влияние порядка данных на BST
На отсортированных данных BST деградирует с O(log n) до O(n).
Время вставки увеличилось с {bst_random_insert:.4f} до {bst_sorted_insert:.4f} секунд в {bst_sorted_insert/bst_random_insert:.1f} раз.
### 2. Почему хеш-таблица не чувствительна к порядку
Хеш-функция распределяет элементы случайно, порядок ввода не влияет на позицию элемента.
Разница между случайным и отсортированным порядком:
- Вставка: {ht_random_insert:.4f} vs {ht_sorted_insert:.4f}
- Отношение: {ht_sorted_insert/ht_random_insert:.2f}x (почти не чувствительна)
### 3. Почему связный список медленный при поиске
Поиск требует последовательного прохода O(n) без возможности индексации.
Поэтому связный список хорош только когда записей мало.
Для больших телефонных справочников он не подходит.
Сравнение скорости поиска (случайные данные):
- LinkedList: {ll_random_find:.4f} сек
- HashTable: {ht_random_find:.4f} сек (в {ll_random_find/ht_random_find:.1f} раз быстрее)
- BST: {bst_random_find:.4f} сек
### 4. Сравнение удаления
| Структура | Сложность | Время на 50 удалений (случайные данные) |
|-----------|-----------|------------------------------------------|
| Связный список | O(n) | {ll_random_delete:.4f} сек |
| Хеш-таблица | O(1) в среднем | {ht_random_delete:.4f} сек |
| BST | O(log n) в среднем | {bst_random_delete:.4f} сек |
## Вывод:
| Задача | Рекомендация | Почему |
|--------|-------------|--------|
| Частый поиск | Хеш-таблица | O(1) в среднем, не зависит от порядка |
| Частые вставки/удаления | Хеш-таблица | Амортизированное O(1) |
| Нужен отсортированный вывод | Сбалансированное дерево (AVL/Red-Black) | In-order обход даёт сортировку |
| Мало данных (<100 элементов) | Связный список или массив | Простота, накладные расходы не оправданы |
| Последовательный доступ (очередь/стек) | Связный список | Вставка/удаление в начало/конец за O(1) |
## Заключение
Эксперимент наглядно демонстрирует:
1. **BST без балансировки опасен** на отсортированных данных он деградирует до O(n)
2. **Хеш-таблица стабильна** её производительность не зависит от порядка входных данных
3. **Связный список** подходит только для специфических задач с малым объёмом данных
## Дата выполнения
{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
"""
with open(filename, 'w', encoding='utf-8') as f:
f.write(report_content)
def print_analysis(results):
print("\n" + "="*60)
print("Анализ резов")
print("="*60)
best_insert = min(results, key=lambda x: x['insert_mean'])
best_find = min(results, key=lambda x: x['find_mean'])
best_delete = min(results, key=lambda x: x['delete_mean'])
print(f"\n Лучшая для вставки: {best_insert['structure']} ({best_insert['mode']}) - {best_insert['insert_mean']:.4f} сек")
print(f" Лучшая для поиска: {best_find['structure']} ({best_find['mode']}) - {best_find['find_mean']:.4f} сек")
print(f" Лучшая для удаления: {best_delete['structure']} ({best_delete['mode']}) - {best_delete['delete_mean']:.4f} сек")
bst_random = None
bst_sorted = None
for res in results:
if res['structure'] == 'bst' and res['mode'] == 'случайный':
bst_random = res
elif res['structure'] == 'bst' and res['mode'] == 'отсортированный':
bst_sorted = res
if bst_random and bst_sorted:
print("\n Влияние порядка данных на BST:")
print(f" Вставка: случайный {bst_random['insert_mean']:.4f} сек vs отсортированный {bst_sorted['insert_mean']:.4f} сек")
print(f" Деградация в {bst_sorted['insert_mean']/bst_random['insert_mean']:.1f}x")
ht_random = None
ht_sorted = None
for res in results:
if res['structure'] == 'hashtable' and res['mode'] == 'случайный':
ht_random = res
elif res['structure'] == 'hashtable' and res['mode'] == 'отсортированный':
ht_sorted = res
if ht_random and ht_sorted:
print("\n Чувствительность хеш-таблицы к порядку:")
print(f" Вставка: случайный {ht_random['insert_mean']:.4f} сек vs отсортированный {ht_sorted['insert_mean']:.4f} сек")
print(f" Отношение: {ht_sorted['insert_mean']/ht_random['insert_mean']:.2f}x (почти не чувствительна)")
ll_random = None
for res in results:
if res['structure'] == 'linkedlist' and res['mode'] == 'случайный':
ll_random = res
elif res['structure'] == 'hashtable' and res['mode'] == 'случайный':
ht_random = res
if ll_random and ht_random:
print("\n Сравнение скорости поиска:")
print(f" LinkedList: {ll_random['find_mean']:.4f} сек")
print(f" HashTable: {ht_random['find_mean']:.4f} сек")
print(f" HashTable быстрее в {ll_random['find_mean']/ht_random['find_mean']:.1f} раз")

BIN
LarikovaAA/task2/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,56 @@
from abc import ABC, abstractmethod
from models import Cell, Maze
class MazeBuilder(ABC):
@abstractmethod
def build_from_file(self, filename: str) -> Maze:
pass
class TextFileMazeBuilder(MazeBuilder):
WALL_CHAR = '#'
START_CHAR = 'S'
EXIT_CHAR = 'E'
PASS_CHAR = ' '
def build_from_file(self, filename: str) -> Maze:
with open(filename, 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n') for line in f.readlines()]
if not lines:
raise ValueError("Файл с лабиринтом пуст")
height = len(lines)
width = max(len(line) for line in lines)
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
if x >= width:
continue
cell = Cell(x, y)
if ch == self.WALL_CHAR:
cell.is_wall = True
elif ch == self.START_CHAR:
cell.is_start = True
elif ch == self.EXIT_CHAR:
cell.is_exit = True
elif ch == self.PASS_CHAR:
pass
else:
cell.is_wall = True
maze.set_cell(x, y, cell)
if maze.start is None:
raise ValueError("В лабиринте нет стартовой клетки (S)")
if maze.exit is None:
raise ValueError("В лабиринте нет выхода (E)")
return maze

View File

@ -0,0 +1,62 @@
from abc import ABC, abstractmethod
from typing import Optional
from models import Cell, Maze
class Player:
def __init__(self, start_cell: Cell):
self.current_cell = start_cell
def move_to(self, new_cell: Cell) -> None:
self.current_cell = new_cell
class Command(ABC):
@abstractmethod
def execute(self) -> bool:
pass
@abstractmethod
def undo(self) -> None:
pass
class MoveCommand(Command):
def __init__(self, player: Player, maze: Maze, direction: str):
self.player = player
self.maze = maze
self.direction = direction
self.previous_cell: Optional[Cell] = None
self.new_cell: Optional[Cell] = None
def _get_target_cell(self) -> Optional[Cell]:
x, y = self.player.current_cell.x, self.player.current_cell.y
if self.direction == 'w':
y -= 1
elif self.direction == 's':
y += 1
elif self.direction == 'a':
x -= 1
elif self.direction == 'd':
x += 1
else:
return None
return self.maze.get_cell(x, y)
def execute(self) -> bool:
self.previous_cell = self.player.current_cell
self.new_cell = self._get_target_cell()
if self.new_cell and self.new_cell.is_passable():
self.player.move_to(self.new_cell)
return True
return False
def undo(self) -> None:
if self.previous_cell:
self.player.move_to(self.previous_cell)

Binary file not shown.

View File

@ -0,0 +1,13 @@
maze_file,maze_size,strategy,time_mean,time_min,time_max,visited_mean,path_length_mean,path_found
small.txt,10×10,BFS,0.14973321231082082,0.08187501225620508,0.3416250110603869,15.0,15.0,True
small.txt,10×10,DFS,0.05074121290817857,0.036958022974431515,0.09620800847187638,21.0,21.0,True
small.txt,10×10,A*,0.11340839555487037,0.07800001185387373,0.24145899806171656,15.0,15.0,True
medium.txt,20×11,BFS,0.28489179676398635,0.21541700698435307,0.3855000250041485,26.0,26.0,True
medium.txt,20×11,DFS,0.22732499055564404,0.16850000247359276,0.396291958168149,90.0,90.0,True
medium.txt,20×11,A*,0.2952334121800959,0.290708034299314,0.30733400490134954,26.0,26.0,True
large.txt,30×15,BFS,0.5741997971199453,0.4562910180538893,0.8350000134669244,40.0,40.0,True
large.txt,30×15,DFS,0.4984830040484667,0.38241699803620577,0.6361660198308527,196.0,196.0,True
large.txt,30×15,A*,0.6602250039577484,0.6104999920353293,0.7946669938974082,40.0,40.0,True
empty.txt,30×1,BFS,0.06608341354876757,0.04250003257766366,0.11475000064820051,30.0,30.0,True
empty.txt,30×1,DFS,0.048741791397333145,0.039041973650455475,0.06312498589977622,30.0,30.0,True
empty.txt,30×1,A*,0.06089139496907592,0.055582961067557335,0.07212499622255564,30.0,30.0,True
1 maze_file maze_size strategy time_mean time_min time_max visited_mean path_length_mean path_found
2 small.txt 10×10 BFS 0.14973321231082082 0.08187501225620508 0.3416250110603869 15.0 15.0 True
3 small.txt 10×10 DFS 0.05074121290817857 0.036958022974431515 0.09620800847187638 21.0 21.0 True
4 small.txt 10×10 A* 0.11340839555487037 0.07800001185387373 0.24145899806171656 15.0 15.0 True
5 medium.txt 20×11 BFS 0.28489179676398635 0.21541700698435307 0.3855000250041485 26.0 26.0 True
6 medium.txt 20×11 DFS 0.22732499055564404 0.16850000247359276 0.396291958168149 90.0 90.0 True
7 medium.txt 20×11 A* 0.2952334121800959 0.290708034299314 0.30733400490134954 26.0 26.0 True
8 large.txt 30×15 BFS 0.5741997971199453 0.4562910180538893 0.8350000134669244 40.0 40.0 True
9 large.txt 30×15 DFS 0.4984830040484667 0.38241699803620577 0.6361660198308527 196.0 196.0 True
10 large.txt 30×15 A* 0.6602250039577484 0.6104999920353293 0.7946669938974082 40.0 40.0 True
11 empty.txt 30×1 BFS 0.06608341354876757 0.04250003257766366 0.11475000064820051 30.0 30.0 True
12 empty.txt 30×1 DFS 0.048741791397333145 0.039041973650455475 0.06312498589977622 30.0 30.0 True
13 empty.txt 30×1 A* 0.06089139496907592 0.055582961067557335 0.07212499622255564 30.0 30.0 True

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@ -0,0 +1,94 @@
import csv
import time
from typing import List, Dict
from models import Maze
from builders import TextFileMazeBuilder
from strategies import BFSStrategy, DFSStrategy, AStarStrategy
from solver import MazeSolver
def run_experiment(maze: Maze, strategy_name: str, strategy, repeats: int = 5) -> Dict:
times = []
visited_counts = []
path_lengths = []
path_found = True
for _ in range(repeats):
solver = MazeSolver(maze, strategy)
path, stats = solver.solve()
times.append(stats.time_ms)
visited_counts.append(stats.visited_cells)
path_lengths.append(stats.path_length)
path_found = stats.path_found
return {
'strategy': strategy_name,
'time_mean': sum(times) / len(times),
'time_min': min(times),
'time_max': max(times),
'visited_mean': sum(visited_counts) / len(visited_counts),
'path_length_mean': sum(path_lengths) / len(path_lengths) if path_found else 0,
'path_found': path_found
}
def run_all_experiments(maze_files: List[str], repeats: int = 5) -> List[Dict]:
builder = TextFileMazeBuilder()
strategies = [
('BFS', BFSStrategy()),
('DFS', DFSStrategy()),
('A*', AStarStrategy())
]
results = []
for maze_file in maze_files:
try:
maze = builder.build_from_file(maze_file)
except (ValueError, FileNotFoundError) as e:
print(f" Ошибка: {e}")
continue
print(f" Размер: {maze.width}×{maze.height}")
print(f" Старт: ({maze.start.x}, {maze.start.y})")
print(f" Выход: ({maze.exit.x}, {maze.exit.y})")
for strategy_name, strategy in strategies:
print(f" Тестирование: {strategy_name}")
result = run_experiment(maze, strategy_name, strategy, repeats)
result['maze_file'] = maze_file.split('/')[-1]
result['maze_size'] = f"{maze.width}×{maze.height}"
results.append(result)
status = "ok" if result['path_found'] else "ne ok"
print(f" {status} Время: {result['time_mean']:.2f} мс, "
f"Посещено: {result['visited_mean']:.0f}, "
f"Путь: {result['path_length_mean']:.0f}")
return results
def save_results_to_csv(results: List[Dict], filename: str = "experiment_results.csv") -> None:
with open(filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=[
'maze_file', 'maze_size', 'strategy',
'time_mean', 'time_min', 'time_max',
'visited_mean', 'path_length_mean', 'path_found'
])
writer.writeheader()
writer.writerows(results)
def print_results_table(results: List[Dict]) -> None:
print("\n" + "=" * 80)
print("РЕЗУЛЬТАТЫ ЭКСПЕРИМЕНТОВ")
print("=" * 80)
for res in results:
print(f"\nЛабиринт: {res['maze_file']}")
print(f" Стратегия: {res['strategy']}")
print(f" Время (ср): {res['time_mean']:.2f} мс")
print(f" Посещено: {res['visited_mean']:.0f} клеток")
print(f" Длина пути: {res['path_length_mean']:.0f}")

146
LarikovaAA/task2/main.py Normal file
View File

@ -0,0 +1,146 @@
import os
from builders import TextFileMazeBuilder
from strategies import BFSStrategy, DFSStrategy, AStarStrategy
from solver import MazeSolver
from observers import ConsoleView
from commands import Player
from experiments import run_all_experiments, save_results_to_csv, print_results_table
def create_test_mazes():
os.makedirs("mazes", exist_ok=True)
small = """##########
#S #
# ### ## #
# # #
### # ####
# # #
# ### # #
# # #
# # E#
##########"""
medium = """####################
#S #
# # # # # # # # # #
# #
# # # # # # # # # #
# #
# # # # # # # # # #
# #
# # # # # # # # # #
# E#
####################"""
large = """##############################
#S #
# # # # # # # # # # # # # # #
# #
# # # # # # # # # # # # # # #
# #
# # # # # # # # # # # # # # #
# #
# # # # # # # # # # # # # # #
# #
# # # # # # # # # # # # # # #
# #
# # # # # # # # # # # # # # #
# E#
##############################"""
empty = "S" + " " * 28 + "E"
no_exit = """#######
#S #
# ### #
# # #
#######"""
with open("mazes/small.txt", "w") as f:
f.write(small)
with open("mazes/medium.txt", "w") as f:
f.write(medium)
with open("mazes/large.txt", "w") as f:
f.write(large)
with open("mazes/empty.txt", "w") as f:
f.write(empty)
with open("mazes/no_exit.txt", "w") as f:
f.write(no_exit)
def demo_maze_solver():
print("\n" + "=" * 60)
print("ДЕМОНСТРАЦИЯ РАБОТЫ MAZE SOLVER")
print("=" * 60)
builder = TextFileMazeBuilder()
view = ConsoleView()
maze = builder.build_from_file("mazes/small.txt")
view.update("maze_loaded", {"maze": maze})
strategies = [
("BFS", BFSStrategy(), "BFS"),
("DFS", DFSStrategy(), "DFSs"),
("A*", AStarStrategy(), "A*")
]
for name, strategy, description in strategies:
solver = MazeSolver(maze, strategy)
view.update("search_start", {"algorithm": description})
path, stats = solver.solve()
if stats.path_found:
view.update("path_found", {"maze": maze, "path": path, "stats": stats})
else:
view.update("no_path", {"stats": stats})
def demo_player_controls():
print("\n" + "=" * 60)
print("Command + Observer")
print("=" * 60)
builder = TextFileMazeBuilder()
view = ConsoleView()
maze = builder.build_from_file("mazes/small.txt")
player = Player(maze.start)
view.update("maze_loaded", {"maze": maze})
view.render(maze, player_position=player.current_cell)
def run_experiments():
print("\n" + "=" * 60)
print("ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ АЛГОРИТМОВ")
print("=" * 60)
maze_files = [
"mazes/small.txt",
"mazes/medium.txt",
"mazes/large.txt",
"mazes/empty.txt",
"mazes/no_exit.txt"
]
results = run_all_experiments(maze_files, repeats=5)
save_results_to_csv(results)
print_results_table(results)
def main():
print("Объектно-ориентированная реализация с паттернами")
print("Паттерны: Builder, Strategy, Observer, Command")
create_test_mazes()
demo_maze_solver()
demo_player_controls()
run_experiments()
if __name__ == "__main__":
main()

View File

@ -0,0 +1 @@
S E

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,79 @@
from typing import List, Optional
class Cell:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
self.is_wall = False
self.is_start = False
self.is_exit = False
def is_passable(self) -> bool:
return not self.is_wall
def __eq__(self, other) -> bool:
if not isinstance(other, Cell):
return False
return 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, width: int, height: int):
self.width = width
self.height = height
self._cells: List[List[Optional[Cell]]] = [[None for _ in range(width)] for _ in range(height)]
self.start: Optional[Cell] = None
self.exit: Optional[Cell] = None
def set_cell(self, x: int, y: int, cell: Cell) -> None:
if 0 <= x < self.width and 0 <= y < self.height:
self._cells[y][x] = cell
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 y in range(self.height):
row = []
for x in range(self.width):
cell = self.get_cell(x, y)
if cell is None:
row.append('?')
elif cell.is_start:
row.append('S')
elif cell.is_exit:
row.append('E')
elif cell.is_wall:
row.append('#')
else:
row.append(' ')
result.append(''.join(row))
return '\n'.join(result)

View File

@ -0,0 +1,66 @@
from abc import ABC, abstractmethod
from typing import List, Optional
from models import Cell, Maze
class Observer(ABC):
@abstractmethod
def update(self, event: str, data: dict) -> None:
pass
class ConsoleView(Observer):
def render(self, maze: Maze, player_position: Optional[Cell] = None, path: Optional[List[Cell]] = None) -> None:
path_set = set(path) if path else set()
print("\n+" + "-" * maze.width + "+")
for y in range(maze.height):
row = []
for x in range(maze.width):
cell = maze.get_cell(x, y)
if cell is None:
row.append('?')
elif player_position and cell == player_position:
row.append('@')
elif cell.is_start:
row.append('S')
elif cell.is_exit:
row.append('E')
elif cell in path_set:
row.append('*')
elif cell.is_wall:
row.append('#')
else:
row.append(' ')
print("|" + ''.join(row) + "|")
print("+" + "-" * maze.width + "+")
def update(self, event: str, data: dict) -> None:
if event == "maze_loaded":
maze = data.get('maze')
print("\n Лабиринт загружен:")
self.render(maze)
elif event == "search_start":
algorithm = data.get('algorithm', 'Unknown')
print(f"\n Начинаем поиск алгоритмом: {algorithm}")
elif event == "path_found":
maze = data.get('maze')
path = data.get('path')
stats = data.get('stats')
self.render(maze, path=path)
elif event == "no_path":
stats = data.get('stats')
print(f"\n {stats}")
elif event == "player_moved":
maze = data.get('maze')
player = data.get('player')
if player:
self.render(maze, player_position=player.current_cell)

View File

@ -0,0 +1,49 @@
import time
from dataclasses import dataclass
from typing import List, Optional, Tuple
from models import Cell, Maze
from strategies import PathFindingStrategy
@dataclass
class SearchStats:
time_ms: float
visited_cells: int
path_length: int
path_found: bool = True
def __str__(self) -> str:
if not self.path_found:
return f"Путь не найден (время: {self.time_ms:.2f} мс)"
return (f"Время: {self.time_ms:.2f} мс, "
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("Стратегия не установлена")
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) if path else 0,
path_length=len(path) if path else 0,
path_found=bool(path)
)
return path, stats

View File

@ -0,0 +1,99 @@
from abc import ABC, abstractmethod
from collections import deque
from heapq import heappush, heappop
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
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
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, current)
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]], current: Cell) -> List[Cell]:
path = []
while current is not None:
path.append(current)
current = parent.get(current)
return list(reversed(path))
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
stack = [(start, [start])]
visited = {start}
while stack:
current, path = stack.pop()
if current == exit_cell:
return path
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
stack.append((neighbor, path + [neighbor]))
return []
class AStarStrategy(PathFindingStrategy):
def _heuristic(self, cell: Cell, exit_cell: Cell) -> int:
return abs(cell.x - exit_cell.x) + abs(cell.y - exit_cell.y)
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
counter = 0
open_set = [(self._heuristic(start, exit_cell), counter, start)]
g_score: Dict[Cell, float] = {start: 0}
parent: Dict[Cell, Optional[Cell]] = {start: None}
while open_set:
_, _, current = heappop(open_set)
if current == exit_cell:
return self._reconstruct_path(parent, current)
for neighbor in maze.get_neighbors(current):
tentative_g = g_score[current] + 1
if neighbor not in g_score or tentative_g < g_score[neighbor]:
parent[neighbor] = current
g_score[neighbor] = tentative_g
counter += 1
f = tentative_g + self._heuristic(neighbor, exit_cell)
heappush(open_set, (f, counter, neighbor))
return []
def _reconstruct_path(self, parent: Dict[Cell, Optional[Cell]], current: Cell) -> List[Cell]:
path = []
while current is not None:
path.append(current)
current = parent.get(current)
return list(reversed(path))

View File

@ -0,0 +1,77 @@
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from pathlib import Path
def plot_results(csv_file='experiment_results.csv'):
if not Path(csv_file).exists():
print(f"{csv_file} не найден. Сначала запустите main.py")
return
df = pd.read_csv(csv_file)
df = df[df['path_found'] == True]
if df.empty:
print("Нет данных для графиков")
return
mazes = [m.replace('.txt', '') for m in df['maze_file'].unique()]
strategies = df['strategy'].unique()
fig, axes = plt.subplots(1, 3, figsize=(14, 5))
fig.suptitle('Сравнение алгоритмов поиска в лабиринте', fontsize=14, fontweight='bold')
x = np.arange(len(mazes))
width = 0.25
colors = {'BFS': '#3498db', 'DFS': '#2ecc71', 'A*': '#e74c3c'}
for i, strategy in enumerate(strategies):
times, visited, lengths = [], [], []
for maze in df['maze_file'].unique():
data = df[(df['strategy'] == strategy) & (df['maze_file'] == maze)]
if not data.empty:
times.append(data['time_mean'].values[0])
visited.append(data['visited_mean'].values[0])
lengths.append(data['path_length_mean'].values[0])
else:
times.append(0)
visited.append(0)
lengths.append(0)
axes[0].bar(x + i*width, times, width, label=strategy,
color=colors.get(strategy, 'gray'), alpha=0.7)
axes[1].bar(x + i*width, visited, width, label=strategy,
color=colors.get(strategy, 'gray'), alpha=0.7)
axes[2].bar(x + i*width, lengths, width, label=strategy,
color=colors.get(strategy, 'gray'), alpha=0.7)
axes[0].set_title(' Время выполнения (мс)')
axes[0].set_xticks(x + width)
axes[0].set_xticklabels(mazes, rotation=45, ha='right')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
axes[1].set_title(' Посещённые клетки')
axes[1].set_xticks(x + width)
axes[1].set_xticklabels(mazes, rotation=45, ha='right')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
axes[2].set_title(' Длина пути')
axes[2].set_xticks(x + width)
axes[2].set_xticklabels(mazes, rotation=45, ha='right')
axes[2].legend()
axes[2].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('experiment_results.png', dpi=150, bbox_inches='tight')
plt.show()
if __name__ == "__main__":
plot_results()