Merge pull request '[1, 2] task 1, task 2' (#180) from yanyaevaa/2026-rff_mp:task-1 into develop

Reviewed-on: #180
This commit is contained in:
VladimirGub 2026-05-30 11:51:42 +00:00
commit 4058e3b2f4
14 changed files with 1414 additions and 0 deletions

317
YanyaevAA/docs/Report_1.md Normal file
View File

@ -0,0 +1,317 @@
# Структуры данных
Цель работы: Реализовать три различные структуры данных «с нуля», применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций. Вы должны собственными руками написать код, чтобы понять внутреннее устройство связного списка, хеш-таблицы и двоичного дерева поиска, а также осознать их сильные и слабые стороны на практике.
## Подготовка среды
```Python
import time
from pathlib import Path
import random
import csv
import sys
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sys.setrecursionlimit(12000) #увеличивает глубину рекурсии
```
# Базовые операции
```Python
#Связный список
def ll_insert(head, name, phone):
current = head
while current:
if current['name'] == name:
current['phone'] = phone
return head
current = current['next']
new_node = {'name': name, 'phone': phone, 'next': None}
new_node['next'] = head
return new_node
def ll_find(head, name):
current = head
while current:
if current['name'] == name:
return current['phone']
current = current['next']
return None
def ll_delete(head, name):
if head['name'] == name:
return head['next']
current = head
while current['next']:
if current['next']['name'] == name:
current['next'] = current['next']['next']
break
current = current['next']
return head
def ll_list_all(head):
data= []
current = head
while current:
data.append((current['name'], current['phone']))
current = current['next']
return sorted(data)
#хеш-таблица
def ht_insert(buckets, name, phone):
id=hash(name)%len(buckets)
buckets[id] = ll_insert(buckets[id], name, phone)
def ht_find(buckets, name):
id= hash(name)%len(buckets)
return ll_find(buckets[id], name)
def ht_delete(buckets, name):
id= hash(name)%len(buckets)
buckets[id] = ll_delete(buckets[id], name)
def ht_list_all(buckets):
data = []
for head in buckets:
current = head
while current:
data.append((current['name'], current['phone']))
current = current['next']
return sorted(data)
#Двоичное дерево поиска
def bst_insert(root, name, phone):
if root is None:
return {'name': name, 'phone': phone, 'left': None, 'right': None}
if name == root['name']:
root['phone'] = phone
elif name < root['name']:
root['left'] = bst_insert(root['left'], name, phone)
else:
root['right'] = bst_insert(root['right'], name, phone)
return root
def bst_find(root, name):
if root is None:
return None
if root['name'] == name:
return root['phone']
elif name<root['name']:
return bst_find(root['left'], name)
else:
return bst_find(root['right'], name)
def minimum(node):
current = node
while current['left'] is not None:
current = current['left']
return current
def bst_delete(root, name):
if root is None:
return None
if name < root['name']:
root['left'] = bst_delete(root['left'], name)
elif name > root['name']:
root['right'] = bst_delete(root['right'], name)
else:
if root['left'] is None:
return root['right']
elif root['right'] is None:
return root['left']
min=minimum(root['right'])
root['name']=min['name']
root['phone']=min['phone']
root['right']=bst_delete(root['right'], min['name'])
return root
def bst_list_all(root):
result=[]
if root:
result.extend(bst_list_all(root['left']))
result.append((root['name'], root['phone']))
result.extend(bst_list_all(root['right']))
return result
```
# Экспериментальная часть
## Генерация
Создаем список records из N=10000 элементов. Каждый элемент — кортеж (name, phone).
Имена генерируются как f"User_{i:05d}" (равномерное распределение). Для проверки влияния порядка подготовим два варианта одного и того же набора:
records_shuffled — случайный порядок.
records_sorted — отсортированный по имени (по алфавиту).
```Python
def generate(n=10000):
records = [(f"User_{i:05d}", f"+7 ({random.randint(100, 999)}) {random.randint(100, 999)}-{random.randint(00, 99):02}-{random.randint(00, 99):02}") for i in range(n)]
records_sorted =records.copy()
records_shuffled=records.copy()
random.shuffle(records_shuffled)
return records_sorted, records_shuffled
```
## Проведение замеров
**А. Вставка всех записей**
Создаем пустую структуру.
Засекаем время, выполняем insert для каждой записи из входного списка.
Фиксируем общее время вставки.
```Python
def task_A(structure_name, data):
start =time.perf_counter()
if structure_name=="LinkedList":
head=None
for name, phone in data:
head = ll_insert(head, name, phone)
container=head
elif structure_name=="HashTable":
buckets=[None]*1000
for name, phone in data:
ht_insert(buckets, name, phone)
container=buckets
elif structure_name=="BinarySearchTree":
root=None
for name, phone in data:
root = bst_insert(root, name, phone)
container=root
end = time.perf_counter()
elapsed = end - start
return elapsed, container
```
**Б. Поиск 100 случайных записей**
Берем 100 случайных имён из того же набора (гарантированно существующих) и 10 имён, которых нет ("None_{i}").
Засекаем время на выполнение всех 110 вызовов find.
```Python
def task_B(structure_name,container, data):
start=time.perf_counter()
if structure_name=="LinkedList":
for name in data:
ll_find(container, name)
elif structure_name=="HashTable":
for name in data:
ht_find(container, name)
elif structure_name=="BinarySearchTree":
for name in data:
bst_find(container, name)
end=time.perf_counter()
elapsed = end - start
return elapsed
```
**В. Удаление 50 случайных записей**
Берем 50 случайных имён из набора.
Засекаем время на выполнение delete для каждого.
```Python
def task_C(structure_name,container, data):
start=time.perf_counter()
if structure_name=="LinkedList":
for name in data:
container=ll_delete(container, name)
elif structure_name=="HashTable":
for name in data:
ht_delete(container, name)
elif structure_name=="BinarySearchTree":
for name in data:
container = bst_delete(container, name)
end=time.perf_counter()
elapsed = end - start
return elapsed
```
### Реализация замеров
```Python
results=[["Структура", "Режим", "Операция", "Время (сек)"]]
structures_name=["LinkedList", "HashTable", "BinarySearchTree"]
experiment_name=["Вставка", "Поиск", "Удаление"]
mode_of_data=["Случайный", "Отсортированный"]
records_sorted, records_shuffled = generate()
container_shuffled=[]#хранилище структур со случайными данными
container_sorted=[]#хранилище структур с отсортированными данными
names=[record[0] for record in records_shuffled]
#Данные для задания Б
random_names=random.sample(names, 100)
missing_names=[f"None_{i}" for i in range(10)]
names_for_test=random_names+missing_names
#Данные для задания В
names_to_delete=random.sample(names,50)
for i in range(3):
container_shuffled.append(task_A(structures_name[i], records_shuffled)[1])
container_sorted.append(task_A(structures_name[i], records_sorted)[1])
for j in range(5):
# Реализация задания А
result_shuffled = task_A(structures_name[i], records_shuffled)[0]
results.append([structures_name[i], mode_of_data[0], experiment_name[0], result_shuffled])
result_sorted= task_A(structures_name[i], records_sorted)[0]
results.append([structures_name[i], mode_of_data[1], experiment_name[0], result_sorted])
print(f"{structures_name[i]}: Время вставки всех записей {mode_of_data[0]}: {result_shuffled} {mode_of_data[1]}: {result_sorted}")
# Реализация задания Б
result_shuffled = task_B(structures_name[i], container_shuffled[i], names_for_test)
results.append([structures_name[i], mode_of_data[0], experiment_name[1], result_shuffled])
result_sorted = task_B(structures_name[i], container_sorted[i], names_for_test)
results.append([structures_name[i], mode_of_data[1], experiment_name[1], result_sorted])
print(f"{structures_name[i]}: Время нахождения 110 записей для {mode_of_data[0]}: {result_shuffled} {mode_of_data[1]}: {result_sorted} ")
#Реализация задания В
shuffled = container_shuffled[i]
sorted = container_sorted[i]
result_shuffled = task_C(structures_name[i], shuffled, names_to_delete)
results.append([structures_name[i], mode_of_data[0], experiment_name[2], result_shuffled])
result_sorted = task_C(structures_name[i], sorted, names_to_delete)
results.append([structures_name[i], mode_of_data[1], experiment_name[2], result_sorted])
print(f"{structures_name[i]}: Время удаления 50 записей для {mode_of_data[0]}: {result_shuffled} {mode_of_data[1]}: {result_sorted}")
```
## Сохранение результатов
```Python
current_dir=Path.cwd()
target=current_dir.parent/"docs"/"data"
csv_file=target /"results.csv"
with open(csv_file, "w", newline="",encoding="utf-8-sig") as f:
writer = csv.writer(f)
writer.writerows(results)
```
# Анализ результатов
## Построение графиков
```Python
df = pd.read_csv(csv_file)
df_avg = df.groupby(["Структура", "Режим", "Операция"])["Время (сек)"].mean().reset_index()
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
for i, experiment in enumerate(experiment_name):
data_experiment = df_avg[df_avg["Операция"] == experiment]
sns.barplot(ax=axes[i],data=data_experiment, x="Структура",y="Время (сек)",hue="Режим")
axes[i].set_title(experiment)
axes[i].set_ylabel("Среднее время (сек)")
axes[i].set_yscale("log")
plt.tight_layout()
png_file= target/"graphics.png"
plt.savefig(png_file, dpi=300, bbox_inches='tight')
plt.show()
```
![](data/graphics.png)
### Как порядок входных данных влияет на скорость вставки в BST
Если подать на вход отсортированные данные, дерево превращается в связный список: каждый новый узел становится правым потомком предыдущего. И сложность меняется с логарифмической O(log n) на линейную O(n). Вставка для неотсортированных данных заняла 0.016531 с, а для отсортированных: 7.112118 с, разница в 430 раз. Получается, что BST сильно зависит от входных данных.
### Почему хеш-таблица почти не чувствительна к порядку
Хеш-таблица имеет низкую чувствительность к порядку входных данных, поскольку хеш-функция вычисляет индекс в массиве на основе значения ключа, обеспечивая равномерное распределение элементов по бакетам независимо от их исходной последовательности. По графикам видно, что разница между случайными и отсортированными данными минимальна. И для всех операций сложность составляет O(1).
### Почему связный список всегда медленен при поиске
Связный список всегда медленен при поиске, потому что у него отсутствует прямой доступ к элементам, и нужно перебирать все элементы по порядку. И из-за этого связный список имееет сложность O(n).
### Как удаление работает в каждой структуре
- **Связный список:** Сначала программа ищет нужный элемент, перебирая их по порядку от головы, что занимает время O(n). Как только элемент найден, то у предыдущего обновляется ссылка на элемент, который шел после удаляемого, что занимает время O(1). По графикам видно, что время удаления близко ко времени поиска. Время удаления для отсортированных данных: 0.017500 с, а для случайных: 0.018947 с.
- **Хеш-таблица:** Программа определяет нужный бакет и удаляет элемент из короткого связного списка внутри этого бакета за O(1). Время удаления для отсортированных данных: 0.000036 с, а для случайных: 0.000043 с.
- **Двоичное дерево поиска:** Нет потомков: Узел просто стирается. Один потомок: Потомок занимает место удаленного родителя. Два потомка: На место удаленного узла ставится самый минимальный элемент из его правого поддерева. Для случайных данных занимает O(log n), а для отсортированных данных занимает O(n). Время удаления для отсортированных данных: 0.039463 с, а для случайных: 0.000153 с.
# Вывод
На основе полученных результатов можно сделать вывод:
- **Связный список:** всегда имеет линейную сложность O(n), что делает его неподходящим для задач частых вставок, частого поиска и получения данных в порядке. Но подходит только в узких случаях: максимально быстрая вставка и удаление элементов в начало или конец структуры(очереди, стеки).
- **Хеш-таблица:** является лучшим выбором для максимально задач частого поиска, добавления и удаления элементов, которые имеют сложность O(1), при этом порядок входных данных не имеет значение. Она идеально подходит для словарей и кэшей.
- **Двоичное дерево поиска:** Необходимо использовать в тех случаях, когда необходимо получать данные в отсортированном состоянии и выполнять поиск в заданном диапазоне значений. При случайных входных данных имеет хорошую сложность O(log n), но при получении отсортированных входных данных сложность возрастает до линейной O(n).
Таким образом, для реальных задач наиболее подходят хеш-таблицы или сбалансированные деревья, если требуется получить данные в отсортированном виде.

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

View File

@ -0,0 +1,91 @@
Структура,Режим,Операция,Время (сек)
LinkedList,Случайный,Вставка,1.3509334000045783
LinkedList,Отсортированный,Вставка,1.3042261000009603
LinkedList,Случайный,Поиск,0.01588919999630889
LinkedList,Отсортированный,Поиск,0.014776199997868389
LinkedList,Случайный,Удаление,0.012387100003252272
LinkedList,Отсортированный,Удаление,0.008979600002930965
LinkedList,Случайный,Вставка,1.3995262999960687
LinkedList,Отсортированный,Вставка,1.3076703999977326
LinkedList,Случайный,Поиск,0.01563009999517817
LinkedList,Отсортированный,Поиск,0.014876699999149423
LinkedList,Случайный,Удаление,0.020549799999571405
LinkedList,Отсортированный,Удаление,0.019360199999937322
LinkedList,Случайный,Вставка,1.3874801999991178
LinkedList,Отсортированный,Вставка,1.2993992000047
LinkedList,Случайный,Поиск,0.015836999999010004
LinkedList,Отсортированный,Поиск,0.014835000001767185
LinkedList,Случайный,Удаление,0.020929600003000814
LinkedList,Отсортированный,Удаление,0.02016870000079507
LinkedList,Случайный,Вставка,1.3857238999989931
LinkedList,Отсортированный,Вставка,1.3020963999952073
LinkedList,Случайный,Поиск,0.015273999997589272
LinkedList,Отсортированный,Поиск,0.014580000002752058
LinkedList,Случайный,Удаление,0.0203378000005614
LinkedList,Отсортированный,Удаление,0.019558400003006682
LinkedList,Случайный,Вставка,1.4175892999992357
LinkedList,Отсортированный,Вставка,1.3036662000013166
LinkedList,Случайный,Поиск,0.015531899996858556
LinkedList,Отсортированный,Поиск,0.014790299996093381
LinkedList,Случайный,Удаление,0.0205294999977923
LinkedList,Отсортированный,Удаление,0.019432499997492414
HashTable,Случайный,Вставка,0.0048284000004059635
HashTable,Отсортированный,Вставка,0.00405250000039814
HashTable,Случайный,Поиск,9.529999806545675e-05
HashTable,Отсортированный,Поиск,6.0999998822808266e-05
HashTable,Случайный,Удаление,4.990000161342323e-05
HashTable,Отсортированный,Удаление,3.060000017285347e-05
HashTable,Случайный,Вставка,0.0040650000009918585
HashTable,Отсортированный,Вставка,0.0039127000054577366
HashTable,Случайный,Поиск,5.650000093737617e-05
HashTable,Отсортированный,Поиск,4.53000029665418e-05
HashTable,Случайный,Удаление,5.3499999921768904e-05
HashTable,Отсортированный,Удаление,4.27999984822236e-05
HashTable,Случайный,Вставка,0.004214900000079069
HashTable,Отсортированный,Вставка,0.03241159999743104
HashTable,Случайный,Поиск,5.999999848427251e-05
HashTable,Отсортированный,Поиск,5.619999865302816e-05
HashTable,Случайный,Удаление,4.2100000428035855e-05
HashTable,Отсортированный,Удаление,3.979999746661633e-05
HashTable,Случайный,Вставка,0.004221499999403022
HashTable,Отсортированный,Вставка,0.004123199993046001
HashTable,Случайный,Поиск,4.7599998652003706e-05
HashTable,Отсортированный,Поиск,4.7299996367655694e-05
HashTable,Случайный,Удаление,3.6600002204068005e-05
HashTable,Отсортированный,Удаление,3.4900003811344504e-05
HashTable,Случайный,Вставка,0.004094500000064727
HashTable,Отсортированный,Вставка,0.0039883999997982755
HashTable,Случайный,Поиск,4.220000118948519e-05
HashTable,Отсортированный,Поиск,4.189999890513718e-05
HashTable,Случайный,Удаление,3.440000000409782e-05
HashTable,Отсортированный,Удаление,3.2000003557186574e-05
BinarySearchTree,Случайный,Вставка,0.01629050000337884
BinarySearchTree,Отсортированный,Вставка,7.1500338000041666
BinarySearchTree,Случайный,Поиск,0.00027830000180983916
BinarySearchTree,Отсортированный,Поиск,0.05988200000138022
BinarySearchTree,Случайный,Удаление,0.0001686000032350421
BinarySearchTree,Отсортированный,Удаление,0.03961960000015097
BinarySearchTree,Случайный,Вставка,0.016419899999164045
BinarySearchTree,Отсортированный,Вставка,7.092110900004627
BinarySearchTree,Случайный,Поиск,0.0002615000048535876
BinarySearchTree,Отсортированный,Поиск,0.060809999995399266
BinarySearchTree,Случайный,Удаление,0.00014789999841013923
BinarySearchTree,Отсортированный,Удаление,0.039564300001075026
BinarySearchTree,Случайный,Вставка,0.016564800003834534
BinarySearchTree,Отсортированный,Вставка,7.115889100001368
BinarySearchTree,Случайный,Поиск,0.000284100002318155
BinarySearchTree,Отсортированный,Поиск,0.06236229999922216
BinarySearchTree,Случайный,Удаление,0.00015389999316539615
BinarySearchTree,Отсортированный,Удаление,0.03888590000133263
BinarySearchTree,Случайный,Вставка,0.01672099999996135
BinarySearchTree,Отсортированный,Вставка,7.124367500000517
BinarySearchTree,Случайный,Поиск,0.00027630000113276765
BinarySearchTree,Отсортированный,Поиск,0.06082099999912316
BinarySearchTree,Случайный,Удаление,0.00014789999841013923
BinarySearchTree,Отсортированный,Удаление,0.03982890000042971
BinarySearchTree,Случайный,Вставка,0.016656699997838587
BinarySearchTree,Отсортированный,Вставка,7.078189200001361
BinarySearchTree,Случайный,Поиск,0.0002753000007942319
BinarySearchTree,Отсортированный,Поиск,0.05944880000606645
BinarySearchTree,Случайный,Удаление,0.00014619999274145812
BinarySearchTree,Отсортированный,Удаление,0.039416899999196175
1 Структура Режим Операция Время (сек)
2 LinkedList Случайный Вставка 1.3509334000045783
3 LinkedList Отсортированный Вставка 1.3042261000009603
4 LinkedList Случайный Поиск 0.01588919999630889
5 LinkedList Отсортированный Поиск 0.014776199997868389
6 LinkedList Случайный Удаление 0.012387100003252272
7 LinkedList Отсортированный Удаление 0.008979600002930965
8 LinkedList Случайный Вставка 1.3995262999960687
9 LinkedList Отсортированный Вставка 1.3076703999977326
10 LinkedList Случайный Поиск 0.01563009999517817
11 LinkedList Отсортированный Поиск 0.014876699999149423
12 LinkedList Случайный Удаление 0.020549799999571405
13 LinkedList Отсортированный Удаление 0.019360199999937322
14 LinkedList Случайный Вставка 1.3874801999991178
15 LinkedList Отсортированный Вставка 1.2993992000047
16 LinkedList Случайный Поиск 0.015836999999010004
17 LinkedList Отсортированный Поиск 0.014835000001767185
18 LinkedList Случайный Удаление 0.020929600003000814
19 LinkedList Отсортированный Удаление 0.02016870000079507
20 LinkedList Случайный Вставка 1.3857238999989931
21 LinkedList Отсортированный Вставка 1.3020963999952073
22 LinkedList Случайный Поиск 0.015273999997589272
23 LinkedList Отсортированный Поиск 0.014580000002752058
24 LinkedList Случайный Удаление 0.0203378000005614
25 LinkedList Отсортированный Удаление 0.019558400003006682
26 LinkedList Случайный Вставка 1.4175892999992357
27 LinkedList Отсортированный Вставка 1.3036662000013166
28 LinkedList Случайный Поиск 0.015531899996858556
29 LinkedList Отсортированный Поиск 0.014790299996093381
30 LinkedList Случайный Удаление 0.0205294999977923
31 LinkedList Отсортированный Удаление 0.019432499997492414
32 HashTable Случайный Вставка 0.0048284000004059635
33 HashTable Отсортированный Вставка 0.00405250000039814
34 HashTable Случайный Поиск 9.529999806545675e-05
35 HashTable Отсортированный Поиск 6.0999998822808266e-05
36 HashTable Случайный Удаление 4.990000161342323e-05
37 HashTable Отсортированный Удаление 3.060000017285347e-05
38 HashTable Случайный Вставка 0.0040650000009918585
39 HashTable Отсортированный Вставка 0.0039127000054577366
40 HashTable Случайный Поиск 5.650000093737617e-05
41 HashTable Отсортированный Поиск 4.53000029665418e-05
42 HashTable Случайный Удаление 5.3499999921768904e-05
43 HashTable Отсортированный Удаление 4.27999984822236e-05
44 HashTable Случайный Вставка 0.004214900000079069
45 HashTable Отсортированный Вставка 0.03241159999743104
46 HashTable Случайный Поиск 5.999999848427251e-05
47 HashTable Отсортированный Поиск 5.619999865302816e-05
48 HashTable Случайный Удаление 4.2100000428035855e-05
49 HashTable Отсортированный Удаление 3.979999746661633e-05
50 HashTable Случайный Вставка 0.004221499999403022
51 HashTable Отсортированный Вставка 0.004123199993046001
52 HashTable Случайный Поиск 4.7599998652003706e-05
53 HashTable Отсортированный Поиск 4.7299996367655694e-05
54 HashTable Случайный Удаление 3.6600002204068005e-05
55 HashTable Отсортированный Удаление 3.4900003811344504e-05
56 HashTable Случайный Вставка 0.004094500000064727
57 HashTable Отсортированный Вставка 0.0039883999997982755
58 HashTable Случайный Поиск 4.220000118948519e-05
59 HashTable Отсортированный Поиск 4.189999890513718e-05
60 HashTable Случайный Удаление 3.440000000409782e-05
61 HashTable Отсортированный Удаление 3.2000003557186574e-05
62 BinarySearchTree Случайный Вставка 0.01629050000337884
63 BinarySearchTree Отсортированный Вставка 7.1500338000041666
64 BinarySearchTree Случайный Поиск 0.00027830000180983916
65 BinarySearchTree Отсортированный Поиск 0.05988200000138022
66 BinarySearchTree Случайный Удаление 0.0001686000032350421
67 BinarySearchTree Отсортированный Удаление 0.03961960000015097
68 BinarySearchTree Случайный Вставка 0.016419899999164045
69 BinarySearchTree Отсортированный Вставка 7.092110900004627
70 BinarySearchTree Случайный Поиск 0.0002615000048535876
71 BinarySearchTree Отсортированный Поиск 0.060809999995399266
72 BinarySearchTree Случайный Удаление 0.00014789999841013923
73 BinarySearchTree Отсортированный Удаление 0.039564300001075026
74 BinarySearchTree Случайный Вставка 0.016564800003834534
75 BinarySearchTree Отсортированный Вставка 7.115889100001368
76 BinarySearchTree Случайный Поиск 0.000284100002318155
77 BinarySearchTree Отсортированный Поиск 0.06236229999922216
78 BinarySearchTree Случайный Удаление 0.00015389999316539615
79 BinarySearchTree Отсортированный Удаление 0.03888590000133263
80 BinarySearchTree Случайный Вставка 0.01672099999996135
81 BinarySearchTree Отсортированный Вставка 7.124367500000517
82 BinarySearchTree Случайный Поиск 0.00027630000113276765
83 BinarySearchTree Отсортированный Поиск 0.06082099999912316
84 BinarySearchTree Случайный Удаление 0.00014789999841013923
85 BinarySearchTree Отсортированный Удаление 0.03982890000042971
86 BinarySearchTree Случайный Вставка 0.016656699997838587
87 BinarySearchTree Отсортированный Вставка 7.078189200001361
88 BinarySearchTree Случайный Поиск 0.0002753000007942319
89 BinarySearchTree Отсортированный Поиск 0.05944880000606645
90 BinarySearchTree Случайный Удаление 0.00014619999274145812
91 BinarySearchTree Отсортированный Удаление 0.039416899999196175

256
YanyaevAA/task1/[1].py Normal file
View File

@ -0,0 +1,256 @@
import time
from pathlib import Path
import random
import csv
import sys
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sys.setrecursionlimit(12000)
#Связный список
def ll_insert(head, name, phone):
current = head
while current:
if current['name'] == name:
current['phone'] = phone
return head
current = current['next']
new_node = {'name': name, 'phone': phone, 'next': None}
new_node['next'] = head
return new_node
def ll_find(head, name):
current = head
while current:
if current['name'] == name:
return current['phone']
current = current['next']
return None
def ll_delete(head, name):
if head['name'] == name:
return head['next']
current = head
while current['next']:
if current['next']['name'] == name:
current['next'] = current['next']['next']
break
current = current['next']
return head
def ll_list_all(head):
data= []
current = head
while current:
data.append((current['name'], current['phone']))
current = current['next']
return sorted(data)
#хэш-таблица
def ht_insert(buckets, name, phone):
id=hash(name)%len(buckets)
buckets[id] = ll_insert(buckets[id], name, phone)
def ht_find(buckets, name):
id= hash(name)%len(buckets)
return ll_find(buckets[id], name)
def ht_delete(buckets, name):
id= hash(name)%len(buckets)
buckets[id] = ll_delete(buckets[id], name)
def ht_list_all(buckets):
data = []
for head in buckets:
current = head
while current:
data.append((current['name'], current['phone']))
current = current['next']
return sorted(data)
#Двоичное дерево поиска
def bst_insert(root, name, phone):
if root is None:
return {'name': name, 'phone': phone, 'left': None, 'right': None}
if name == root['name']:
root['phone'] = phone
elif name < root['name']:
root['left'] = bst_insert(root['left'], name, phone)
else:
root['right'] = bst_insert(root['right'], name, phone)
return root
def bst_find(root, name):
if root is None:
return None
if root['name'] == name:
return root['phone']
elif name<root['name']:
return bst_find(root['left'], name)
else:
return bst_find(root['right'], name)
def minimum(node):
current = node
while current['left'] is not None:
current = current['left']
return current
def bst_delete(root, name):
if root is None:
return None
if name < root['name']:
root['left'] = bst_delete(root['left'], name)
elif name > root['name']:
root['right'] = bst_delete(root['right'], name)
else:
if root['left'] is None:
return root['right']
elif root['right'] is None:
return root['left']
min=minimum(root['right'])
root['name']=min['name']
root['phone']=min['phone']
root['right']=bst_delete(root['right'], min['name'])
return root
def bst_list_all(root):
result=[]
if root:
result.extend(bst_list_all(root['left']))
result.append((root['name'], root['phone']))
result.extend(bst_list_all(root['right']))
return result
#1. Генерация тестовых данных
def generate(n=10000):
records = [(f"User_{i:05d}", f"+7 ({random.randint(100, 999)}) {random.randint(100, 999)}-{random.randint(00, 99):02}-{random.randint(00, 99):02}") for i in range(n)]
records_sorted =records.copy()
records_shuffled=records.copy()
random.shuffle(records_shuffled)
return records_sorted, records_shuffled
#3.Проведение замеров
#А. Вставка всех записей
def task_A(structure_name, data):
start =time.perf_counter()
if structure_name=="LinkedList":
head=None
for name, phone in data:
head = ll_insert(head, name, phone)
container=head
elif structure_name=="HashTable":
buckets=[None]*1000
for name, phone in data:
ht_insert(buckets, name, phone)
container=buckets
elif structure_name=="BinarySearchTree":
root=None
for name, phone in data:
root = bst_insert(root, name, phone)
container=root
end = time.perf_counter()
elapsed = end - start
return elapsed, container
#Б. Поиск 100 случайных записей
def task_B(structure_name,container, data):
start=time.perf_counter()
if structure_name=="LinkedList":
for name in data:
ll_find(container, name)
elif structure_name=="HashTable":
for name in data:
ht_find(container, name)
elif structure_name=="BinarySearchTree":
for name in data:
bst_find(container, name)
end=time.perf_counter()
elapsed = end - start
return elapsed
#В. Удаление 50 случайных чисел
def task_C(structure_name,container, data):
start=time.perf_counter()
if structure_name=="LinkedList":
for name in data:
container=ll_delete(container, name)
elif structure_name=="HashTable":
for name in data:
ht_delete(container, name)
elif structure_name=="BinarySearchTree":
for name in data:
container = bst_delete(container, name)
end=time.perf_counter()
elapsed = end - start
return elapsed
results=[["Структура", "Режим", "Операция", "Время (сек)"]]
structures_name=["LinkedList", "HashTable", "BinarySearchTree"]
experiment_name=["Вставка", "Поиск", "Удаление"]
mode_of_data=["Случайный", "Отсортированный"]
records_sorted, records_shuffled = generate()
container_shuffled=[]#хранилище структур со случайными данными
container_sorted=[]#хранилище структур с отсортированными данными
names=[record[0] for record in records_shuffled]
#Данные для задания Б
random_names=random.sample(names, 100)
missing_names=[f"None_{i}" for i in range(10)]
names_for_test=random_names+missing_names
#Данные для задания В
names_to_delete=random.sample(names,50)
for i in range(3):
container_shuffled.append(task_A(structures_name[i], records_shuffled)[1])
container_sorted.append(task_A(structures_name[i], records_sorted)[1])
for j in range(5):
# Реализация задания А
result_shuffled = task_A(structures_name[i], records_shuffled)[0]
results.append([structures_name[i], mode_of_data[0], experiment_name[0], result_shuffled])
result_sorted= task_A(structures_name[i], records_sorted)[0]
results.append([structures_name[i], mode_of_data[1], experiment_name[0], result_sorted])
print(f"{structures_name[i]}: Время вставки всех записей {mode_of_data[0]}: {result_shuffled} {mode_of_data[1]}: {result_sorted}")
# Реализация задания Б
result_shuffled = task_B(structures_name[i], container_shuffled[i], names_for_test)
results.append([structures_name[i], mode_of_data[0], experiment_name[1], result_shuffled])
result_sorted = task_B(structures_name[i], container_sorted[i], names_for_test)
results.append([structures_name[i], mode_of_data[1], experiment_name[1], result_sorted])
print(f"{structures_name[i]}: Время нахождения 110 записей для {mode_of_data[0]}: {result_shuffled} {mode_of_data[1]}: {result_sorted} ")
#Реализация задания В
shuffled = container_shuffled[i]
sorted = container_sorted[i]
result_shuffled = task_C(structures_name[i], shuffled, names_to_delete)
results.append([structures_name[i], mode_of_data[0], experiment_name[2], result_shuffled])
result_sorted = task_C(structures_name[i], sorted, names_to_delete)
results.append([structures_name[i], mode_of_data[1], experiment_name[2], result_sorted])
print(f"{structures_name[i]}: Время удаления 50 записей для {mode_of_data[0]}: {result_shuffled} {mode_of_data[1]}: {result_sorted}")
#4. Сохранение результатов\
current_dir=Path.cwd()
target=current_dir.parent/"docs"/"data"
csv_file=target /"results.csv"
with open(csv_file, "w", newline="",encoding="utf-8-sig") as f:
writer = csv.writer(f)
writer.writerows(results)
#Построение графиков
df = pd.read_csv(csv_file)
df_avg = df.groupby(["Структура", "Режим", "Операция"])["Время (сек)"].mean().reset_index()
fig, axes = plt.subplots(1, 3, figsize=(18, 6), sharey=True)
for i, experiment in enumerate(experiment_name):
data_experiment = df_avg[df_avg["Операция"] == experiment]
sns.barplot(ax=axes[i],data=data_experiment, x="Структура",y="Время (сек)",hue="Режим")
axes[i].set_title(experiment)
axes[i].set_ylabel("Среднее время (сек)")
axes[i].set_yscale("log")
plt.tight_layout()
png_file= target/"graphics.png"
plt.savefig(png_file, dpi=300, bbox_inches='tight')
plt.show()

View File

@ -0,0 +1,238 @@
# Поиск выхода из лабиринта (объектно-ориентированная реализация с паттернами)
Цель работы:
Разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. В ходе работы необходимо применить минимум 3 паттерна проектирования из списка GoF, обосновать их выбор и продемонстрировать преимущества такой архитектуры.
## Выбранные паттерны проектирования
- Builder: шаблон проектирования, который инкапсулирует создание объекта и позволяет разделить его на различные этапы. В программе позволяет загружать лабиринт из файла.
- Strategy: поведенческий шаблон проектирования, предназначенный для определения семейства алгоритмов, инкапсуляции каждого из них и обеспечения их взаимозаменяемости. Это позволяет выбирать алгоритм путём определения соответствующего класса. В программе помогает менять способ поиска пути.
- Observer: это поведенческий паттерн, который позволяет объектам оповещать другие объекты об изменениях своего состояния. В программе служит для визуализации лабиринта и пути к выходу, а также для уведомляет о событиях ("path_found").
## Диаграмма классов
![](data/mermaid_diagram.png)
# Листинги ключевых классов
## Паттерн Builder
```Python
class MazeBuilder(ABC):
@abstractmethod
def buildFromFile(self, filename):
pass
class TextFileMazeBuilder(MazeBuilder):
def buildFromFile(self, filename):
with open(filename, 'r') as f:
lines = [line.rstrip('\n') for line in f]
height = len(lines)
width = max(len(line) for line in lines)
grid=[]
start_cell=None
exit_cell=None
for y in range(height):
row=[]
for x in range(width):
char=lines[y][x]
isWall = (char == '#')
isStart = (char == 'S')
isExit = (char == 'E')
cell=Cell(x, y, isWall, isStart, isExit)
if isStart:
start_cell =cell
if isExit:
exit_cell =cell
row.append(cell)
grid.append(row)
return Maze(grid, width, height, start_cell, exit_cell)
```
## Паттерн Strategy
```Python
class PathFindingStrategy(ABC):
@abstractmethod
def findPath(self,maze, start, exit):
pass
class BFS(PathFindingStrategy):
def findPath(self, maze, start, exit):
queue = deque([start])
traveled_path={start: None}
while queue:
current = queue.popleft()
if current==exit:
path=[]
while current is not None:
path.append(current)
current = traveled_path[current]
return path[::-1], len(traveled_path)
for neighbor in maze.getNeighbors(current):
if neighbor not in traveled_path:
traveled_path[neighbor] = current
queue.append(neighbor)
return [], len(traveled_path)
class DFS(PathFindingStrategy):
def findPath(self, maze, start, exit):
stack = [start]
traveled_path={start: None}
while stack:
current = stack.pop()
if current == exit:
path = []
while current is not None:
path.append(current)
current = traveled_path[current]
return path[::-1], len(traveled_path)
for neighbor in maze.getNeighbors(current):
if neighbor not in traveled_path:
traveled_path[neighbor] = current
stack.append(neighbor)
return [], len(traveled_path)
class AStar(PathFindingStrategy):
def findPath(self, maze, start, exit):
count = 0
open_set = [(0, count, start)]
traveled_path = {start: None}
g_score = {start: 0}
while open_set:
_,_,current = heapq.heappop(open_set)
if current == exit:
path = []
while current is not None:
path.append(current)
current = traveled_path[current]
return path[::-1], len(traveled_path)
for neighbor in maze.getNeighbors(current):
g_score_new = g_score[current]+1
if neighbor not in g_score or g_score_new < g_score[neighbor]:
traveled_path[neighbor] = current
g_score[neighbor] = g_score_new
f_score = g_score_new + abs(neighbor.x - exit.x) + abs(neighbor.y - exit.y)
count += 1
heapq.heappush(open_set, (f_score, count, neighbor))
return [],len(traveled_path)
```
## Паттерн Observer с объектом Event
```Python
class Event:
def __init__(self, event_type, data=None):
self.event_type = event_type
self.data = data
class Observer(ABC):
@abstractmethod
def update(self, event):
pass
class ConsoleView(Observer):
def update(self, event):
if event.event_type == "path_found":
stats=event.data
print("Путь найден:")
print("Время выполнения:", stats.time)
print("Количество посещённых клеток:", stats.visited_cells)
print("Длина найденного пути:", stats.path_length)
if event.event_type == "maze_loaded":
print("Загружен новый лабиринт")
def render(self, maze, path):
for y in range(maze.height):
row_str=""
for x in range(maze.width):
cell=maze.getCell(x, y)
if cell == maze.start:
row_str += "S"
elif cell == maze.exit:
row_str += "E"
elif cell in path:
row_str += "·"
elif cell.isWall:
row_str += "#"
else:
row_str += " "
print(row_str)
```
## Реализация программы
```Python
mazes = ["10x10.txt","50x50.txt","100x100.txt","empty.txt","without_exit.txt"]
results =[["лабиринт",
"стратегия",
ремя_мс",
"посещено_клеток",
"длина_пути"]]
strategies = {
"BFS": BFS(),
"DFS": DFS(),
"AStar": AStar()
}
builder = TextFileMazeBuilder()
n=10
directory = os.path.join("docs", "data")
for maze_name in mazes:
print(maze_name)
file_name=os.path.join(directory, maze_name)
maze = builder.buildFromFile(file_name)
viewer=ConsoleView()
for strategy_name, strategy in strategies.items():
total_time = 0.0
total_visited = 0
total_path_length = 0
solver = MazeSolver(maze, strategy)
for i in range(n):
stats = solver.solve()
total_time += stats.time
total_visited += stats.visited_cells
total_path_length += stats.path_length
avg_time = total_time/n
avg_visited = total_visited/n
avg_path_length = total_path_length/n
print("-"*100)
print(f"{maze_name} стратегия: {strategy_name} время_мс: {avg_time} посещено_клеток: {avg_visited} длина_пути: {avg_path_length}")
results.append([maze_name, strategy_name, avg_time, avg_visited, avg_path_length])
path, _ = strategy.findPath(maze, maze.start, maze.exit)
viewer.render(maze, path)
csv_filename = os.path.join(directory, "maze_results.csv")
with open(csv_filename, "w", newline="", encoding="utf-8-sig") as f:
writer = csv.writer(f)
writer.writerows(results)
```
# Результаты экспериментов
В ходе тестирования каждый алгоритм запускался по 10 раз на каждом типе лабиринта
| Лабиринт | Стратегия | Время (мс) | Посещено клеток | Длина пути |
| :--- | :--- |:------------------------------:| :---: | :---: |
| **10x10.txt** | BFS <br> DFS <br> AStar | 0.0264 <br> 0.0368 <br> 0.0320 | 30.0 <br> 43.0 <br> 30.0 | 29.0 <br> 29.0 <br> 29.0 |
| **50x50.txt** | BFS <br> DFS <br> AStar | 0.6698 <br> 0.4722 <br> 0.5986 | 799.0 <br> 562.0 <br> 539.0 | 316.0 <br> 350.0 <br> 316.0 |
| **100x100.txt** | BFS <br> DFS <br> AStar | 3.0005 <br> 0.4454 <br> 0.5787 | 3576.0 <br> 595.0 <br> 536.0 | 196.0 <br> 364.0 <br> 196.0 |
| **empty.txt** | BFS <br> DFS <br> AStar | 0.2904 <br> 0.1618 <br> 0.4074 | 324.0 <br> 324.0 <br> 324.0 | 35.0 <br> 171.0 <br> 35.0 |
| **without_exit.txt** | BFS <br> DFS <br> AStar | 0.0407 <br> 0.0408 <br> 0.0519 | 48.0 <br> 48.0 <br> 48.0 | 0.0 <br> 0.0 <br> 0.0 |
Сравнительные графики:
![](data/maze_graphics.png)
# Анализ эффективности алгоритмов и применимости паттернов.
- **BFS**:
- Время: Алгоритм демонстрирует рост времени с увеличением площади лабиринта и количества ветвлений: минимальное время в лабиринте 10x10: 0.0264; максимальное время в лабиринте 100x100: 3.0005.
- Количество посещенных клеток: Также показывает рост количества посещенных клеток с увеличением площади лабиринта. В лабиринтах 50x50, 100x100 показывает неэффективность алгоритма с точки зрения объема работы: 799 и 3576 соответственно.
- Длина пути: Во всех алгоритмах показывает выбор оптимального пути.
- **DFS**:
- Время: В маленьком лабиринте (10x10) работает медленнее других, но на больших и пустом лабиринтах является быстрейшим алгоритмом. Это происходит, потому что DFS использует стек, который в Python имеет сложность O(1).
- Количество посещенных клеток: На полученных данных, мы видим хорошие значения посещенных клеток, но для такого же по размерам лабиринта, но с другими путями, количество посещенных клеток может измениться. Это происходит из-за того, что количество посещенных клеток зависит от лабиринта, и "повезло" ли алгоритму сразу же наткнуться на коридор, ведущий к выходу.
- Длина пути: На маленьком лабиринте (10x10) показывает одинаковую длину пути со всеми алгоритмами. Но для других лабиринтов показывает пути значительно больше чем у других. DFS не гарантирует оптимальность пути, потому что ищет до первого пути ведущего к выходу, который может быть большим. Но на лабиринте без развилок (10x10) показывает результат на ровне с другими алгоритмами.
- **A***:
- Время: На большинстве лабиринтах показывает средние результат, в лабиринте без выхода и в пустом лабиринте показывает худшие результаты. Это происходит, потому что алгоритм использует приоритетную очередь, которая имеет логарифмическую сложность O(log n), также постоянная перестройка очереди занимает чуть больше времени.
- Количество посещенных клеток: Во всех случаях имеет минимальное количество посещенных клеток. Это происходит благодаря эвристической функции (Манхэттенское расстояние). Она минимизирует количество посещенных клеток, избегая ложные направления. Преимущество алгоритма хорошо видно на больших лабиринтах (50x50, 100x100)
- Длина пути: Также, как и BFS, показывает минимальную длину пути. Алгоритм выбирает кратчайший путь, также благодаря эвристике.
## Выводы по алгоритмам
- **BFS**: Всегда находит кратчайший путь, но для больших лабиринтов тратит много времени, а также для поиска пути исследует большое количество клеток.
- **DFS**: Алгоритм подходит для быстрого нахождения пути для простых или линейных лабиринтов. Но не подходит для выбора кратчайшего пути.
- **A***: Алгоритм показывает наибольшую общую эффективность. Всегда находит кратчайший путь и для поиска посещает наименьшее количество клеток, имеет хорошую скорость выполнения.
## Применяемость паттернов
- **Builder**: Позволяет отделить структуру самого лабиринта от источника его данных. Позволяет свободно, при необходимости, добавлять другие форматы, из которых будет строиться лабиринт.
- **Strategy**: Инкапсулирует алгоритмы поиска пути в отдельные классы, что позволяет свободно добавлять другие алгоритмы поиска
- **Observer**: Реализует механизм уведомления о шагах алгоритма. Позволяет визуализировать процесс поиска клеток, полностью отделяя логику вычислений от графического интерфейса.
# Вывод
Применение принципов И паттернов ООП позволило создать устойчивую к изменениям программу. Можно свободно добавлять новые алгоритмы поиска или новые способы вывода данных, создавая новые подклассы и не меняя ни единой строчки кода. Без ООП стало бы невозможно легко переключиться с чтения текстовых файлов на файлы другого формата или на алгоритм случайной генерации карт. В итоге ООП позволяет расширять код, не затрагивая работоспособность других компонентов.

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

View File

@ -0,0 +1,16 @@
лабиринт,стратегия,время_мс,посещено_клеток,длина_пути
10x10.txt,BFS,0.02643000007083174,30.0,29.0
10x10.txt,DFS,0.03684999974211678,43.0,29.0
10x10.txt,AStar,0.0320400002237875,30.0,29.0
50x50.txt,BFS,0.6697899993014289,799.0,316.0
50x50.txt,DFS,0.4721500004961854,562.0,350.0
50x50.txt,AStar,0.5986000000120839,539.0,316.0
100x100.txt,BFS,3.000480001355754,3576.0,196.0
100x100.txt,DFS,0.4453900000953581,595.0,364.0
100x100.txt,AStar,0.5786999998235842,522.0,196.0
empty.txt,BFS,0.29044999937468674,324.0,35.0
empty.txt,DFS,0.16180000056920107,324.0,171.0
empty.txt,AStar,0.40738000025157817,324.0,35.0
without_exit.txt,BFS,0.04074000025866553,48.0,0.0
without_exit.txt,DFS,0.040809999700286426,48.0,0.0
without_exit.txt,AStar,0.05192000025999732,48.0,0.0
1 лабиринт стратегия время_мс посещено_клеток длина_пути
2 10x10.txt BFS 0.02643000007083174 30.0 29.0
3 10x10.txt DFS 0.03684999974211678 43.0 29.0
4 10x10.txt AStar 0.0320400002237875 30.0 29.0
5 50x50.txt BFS 0.6697899993014289 799.0 316.0
6 50x50.txt DFS 0.4721500004961854 562.0 350.0
7 50x50.txt AStar 0.5986000000120839 539.0 316.0
8 100x100.txt BFS 3.000480001355754 3576.0 196.0
9 100x100.txt DFS 0.4453900000953581 595.0 364.0
10 100x100.txt AStar 0.5786999998235842 522.0 196.0
11 empty.txt BFS 0.29044999937468674 324.0 35.0
12 empty.txt DFS 0.16180000056920107 324.0 171.0
13 empty.txt AStar 0.40738000025157817 324.0 35.0
14 without_exit.txt BFS 0.04074000025866553 48.0 0.0
15 without_exit.txt DFS 0.040809999700286426 48.0 0.0
16 without_exit.txt AStar 0.05192000025999732 48.0 0.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

View File

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

299
YanyaevAA/task2/task_2.py Normal file
View File

@ -0,0 +1,299 @@
from abc import ABC, abstractmethod
from collections import deque
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
import heapq
import time
import os
import csv
#Этап 1
class Cell:
def __init__(self, x, y, isWall=False, isStart=False, isExit=False):
self.x = x
self.y = y
self.isWall = isWall
self.isStart = isStart
self.isExit = isExit
def isPassable(self):
return not self.isWall
class Maze:
def __init__(self, cells, width, height, start, exit):
self.width = width
self.height = height
self.cells =cells
self.start = start
self.exit = exit
def getCell(self, x, y):
if 0 <= x< self.width and 0 <=y< self.height:
return self.cells[y][x]
return None
def getNeighbors(self, cell: Cell):
neighbors = []
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
for dir_x, dir_y in directions:
neigh_x = cell.x+dir_x
neigh_y = cell.y+dir_y
neighbor = self.getCell(neigh_x, neigh_y)
if neighbor and neighbor.isPassable():
neighbors.append(neighbor)
return neighbors
#Этап 2
class MazeBuilder(ABC):
@abstractmethod
def buildFromFile(self, filename):
pass
class TextFileMazeBuilder(MazeBuilder):
def buildFromFile(self, filename):
with open(filename, 'r') as f:
lines = [line.rstrip('\n') for line in f]
height = len(lines)
width = max(len(line) for line in lines)
grid=[]
start_cell=None
exit_cell=None
for y in range(height):
row=[]
for x in range(width):
char=lines[y][x]
isWall = (char == '#')
isStart = (char == 'S')
isExit = (char == 'E')
cell=Cell(x, y, isWall, isStart, isExit)
if isStart:
start_cell =cell
if isExit:
exit_cell =cell
row.append(cell)
grid.append(row)
return Maze(grid, width, height, start_cell, exit_cell)
#Этап 3
class PathFindingStrategy(ABC):
@abstractmethod
def findPath(self,maze, start, exit):
pass
class BFS(PathFindingStrategy):
def findPath(self, maze, start, exit):
queue = deque([start])
traveled_path={start: None}
while queue:
current = queue.popleft()
if current==exit:
path=[]
while current is not None:
path.append(current)
current = traveled_path[current]
return path[::-1], len(traveled_path)
for neighbor in maze.getNeighbors(current):
if neighbor not in traveled_path:
traveled_path[neighbor] = current
queue.append(neighbor)
return [], len(traveled_path)
class DFS(PathFindingStrategy):
def findPath(self, maze, start, exit):
stack = [start]
traveled_path={start: None}
while stack:
current = stack.pop()
if current == exit:
path = []
while current is not None:
path.append(current)
current = traveled_path[current]
return path[::-1], len(traveled_path)
for neighbor in maze.getNeighbors(current):
if neighbor not in traveled_path:
traveled_path[neighbor] = current
stack.append(neighbor)
return [], len(traveled_path)
class AStar(PathFindingStrategy):
def findPath(self, maze, start, exit):
count = 0
open_set = [(0, count, start)]
traveled_path = {start: None}
g_score = {start: 0}
while open_set:
_,_,current = heapq.heappop(open_set)
if current == exit:
path = []
while current is not None:
path.append(current)
current = traveled_path[current]
return path[::-1], len(traveled_path)
for neighbor in maze.getNeighbors(current):
g_score_new = g_score[current]+1
if neighbor not in g_score or g_score_new < g_score[neighbor]:
traveled_path[neighbor] = current
g_score[neighbor] = g_score_new
f_score = g_score_new + abs(neighbor.x - exit.x) + abs(neighbor.y - exit.y)
count += 1
heapq.heappush(open_set, (f_score, count, neighbor))
return [],len(traveled_path)
#Этап 4
class SearchStats:
def __init__(self, time, visited_cells, path_length):
self.time = time
self.visited_cells = visited_cells
self.path_length = path_length
class MazeSolver:
def __init__(self, maze, strategy):
self.maze = maze
self.strategy = strategy
self.observers = []
def addObserver(self, observer):
self.observers.append(observer)
def setStrategy(self, strategy):
self.strategy = strategy
def solve(self):
start_cell = self.maze.start
exit_cell = self.maze.exit
start_time = time.perf_counter()
path, visited_cells = self.strategy.findPath(self.maze, start_cell, exit_cell)
end_time = time.perf_counter()
time_ms = (end_time - start_time) * 1000
path_length = len(path)
stats=SearchStats(time_ms, visited_cells, path_length)
event = Event("path_found", data=stats)
for observer in self.observers:
observer.update(event)
return stats
#Этап 5
#5.1
class Event:
def __init__(self, event_type, data=None):
self.event_type = event_type
self.data = data
class Observer(ABC):
@abstractmethod
def update(self, event):
pass
class ConsoleView(Observer):
def update(self, event):
if event.event_type == "path_found":
stats=event.data
print("Путь найден:")
print("Время выполнения:", stats.time)
print("Количество посещённых клеток:", stats.visited_cells)
print("Длина найденного пути:", stats.path_length)
if event.event_type == "move":
x, y = event.data
print(f"Игрок переместился в ячейку: {x}, {y}")
if event.event_type == "maze_loaded":
print("Загружен новый лабиринт")
def render(self, maze, path):
for y in range(maze.height):
row_str=""
for x in range(maze.width):
cell=maze.getCell(x, y)
if cell == maze.start:
row_str += "S"
elif cell == maze.exit:
row_str += "E"
elif cell in path:
row_str += "·"
elif cell.isWall:
row_str += "#"
else:
row_str += " "
print(row_str)
#Этап 6
mazes = ["10x10.txt","50x50.txt","100x100.txt","empty.txt","without_exit.txt"]
results =[["лабиринт",
"стратегия",
"время_мс",
"посещено_клеток",
"длина_пути"]]
strategies = {
"BFS": BFS(),
"DFS": DFS(),
"AStar": AStar()
}
builder = TextFileMazeBuilder()
n=10
directory = os.path.join("docs", "data")
for maze_name in mazes:
print(maze_name)
file_name=os.path.join(directory, maze_name)
maze = builder.buildFromFile(file_name)
viewer=ConsoleView()
for strategy_name, strategy in strategies.items():
total_time = 0.0
total_visited = 0
total_path_length = 0
solver = MazeSolver(maze, strategy)
for _ in range(n):
stats = solver.solve()
total_time += stats.time
total_visited += stats.visited_cells
total_path_length += stats.path_length
avg_time = total_time/n
avg_visited = total_visited/n
avg_path_length = total_path_length/n
print(f"{maze_name} стратегия: {strategy_name} время_мс: {avg_time} посещено_клеток: {avg_visited} длина_пути: {avg_path_length}")
results.append([maze_name, strategy_name, avg_time, avg_visited, avg_path_length])
path, _ = strategy.findPath(maze, maze.start, maze.exit)
viewer.render(maze, path)
csv_filename = os.path.join(directory, "maze_results.csv")
with open(csv_filename, "w", newline="", encoding="utf-8-sig") as f:
writer = csv.writer(f)
writer.writerows(results)
#Графики
df = pd.read_csv(csv_filename)
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(16, 6))
sns.barplot(data=df, x='лабиринт', y='время_мс', hue='стратегия', ax=ax1)
ax1.set_title('Время выполнения алгоритмов')
ax1.set_xlabel('Лабиринты')
ax1.set_ylabel('Время (мс)')
ax1.grid(axis='y', linestyle='--', alpha=0.7)
ax1.legend()
sns.barplot(data=df, x='лабиринт', y='посещено_клеток', hue='стратегия', ax=ax2)
ax2.set_title('Количество посещенных клеток')
ax2.set_xlabel('Лабиринты')
ax2.set_ylabel('Количество клеток')
ax2.grid(axis='y', linestyle='--', alpha=0.7)
ax2.legend()
plt.tight_layout()
sns.barplot(data=df, x='лабиринт', y='длина_пути', hue='стратегия', ax=ax3)
ax3.set_title('Длина пути')
ax3.set_xlabel('Лабиринты')
ax3.set_ylabel('Количество клеток')
ax3.grid(axis='y', linestyle='--', alpha=0.7)
ax3.legend()
plt.tight_layout()
img = os.path.join(directory, "maze_graphics.png")
plt.savefig(img, dpi=300)
plt.show()