[1,2] struck-data, maze_task #336

Merged
IvanBoy merged 1 commits from MarkinAM/2026-rff_mp:tasks into develop 2026-05-30 11:53:32 +00:00
26 changed files with 4975 additions and 0 deletions

1291
MarkinAM/1/delete_chart.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 33 KiB

74
MarkinAM/1/docs/report.md Normal file
View File

@ -0,0 +1,74 @@

# Отчёт по лабораторной работе
## Цель работы
Реализовать три структуры данных «с нуля» (связный список, хеш-таблица, двоичное дерево поиска), применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций.
**Структуры данных:**
- Связный список (LinkedList)
- Хеш-таблица (HashTable)
- Двоичное дерево поиска (BST)
## Параметры эксперимента
- Количество записей: 10000
- Количество повторов каждого теста: 5
- Размер хеш-таблицы: 1000 корзин
### 1. Связный список
| Режим | Вставка (сек) | Поиск (сек) | Удаление (сек) |
|-------|---------------|-------------|----------------|
| Случайный | 7.027 | 0.062 | 0.02 |
| Отсортированный | 6.93 | 0.065 | 0.02 |
### 2. Хеш-таблица
| Режим | Вставка (сек) | Поиск (сек) | Удаление (сек) |
|-------|---------------|-------------|----------------|
| Случайный | 0.033 | 0.0003 | 0.0001 |
| Отсортированный | 0.065 | 0.0003 | 0.0001 |
### 3. Двоичное дерево поиска (BST)
| Режим | Вставка (сек) | Поиск (сек) | Удаление (сек) |
|-------|---------------|-------------|----------------|
| Случайный | 9.6316 | 0.0967 | 0.035 |
| Отсортированный | 9.4514 | 0.1112 | 0.0352 |
## Анализ результатов
### 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) без возможности индексации.
Поэтому связный список хорош только когда записей мало.
Для больших телефонных справочников он не подходит.
Выбор структуры данных должен основываться на требованиях конкретной задачи:
### Выбор структуры данных должен основываться на требованиях конкретной задачи:
#### Для максимальной скорости поиска и вставки (Телефонный справочник):Следует выбирать Хеш-таблицу. Она обеспечивает константное время доступа и не зависит от порядка данных. Это оптимальный выбор для базового функционала справочника.
#### Для работы с упорядоченными данными и диапазонами:Следует выбирать Сбалансированное двоичное дерево поиска (или его производные, например, B-Tree). Несмотря на чуть большую константа в асимптотике по сравнению с хеш-таблицей, оно позволяет эффективно получать отсортированные данные, находить минимальный/максимальный элемент или элементы в заданном диапазоне.
#### Связный список в чистом виде для решения подобных задач сегодня практически не применяется из-за низкой эффективности поиска.

1265
MarkinAM/1/find_chart.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 31 KiB

1195
MarkinAM/1/insert_chart.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 29 KiB

106
MarkinAM/1/main.py Normal file
View File

@ -0,0 +1,106 @@
import time
import random
import csv
import structures as st # Предполагается, что у вас есть модуль с реализациями структур данных
N=10000
REPEATS=5
# Генерируем список записей из N элементов, каждая запись - имя и телефон
def generate_records(N):
records = [
(f"User_{i:05d}", f"+7{random.randint(10**9, 10**10 - 1)}")
for i in range(N)
]
return records, sorted(records, key=lambda x: x[0])
# Подготовка списка имен, которые нужно искать: Некоторые точно есть, некоторые — придуманные
def prepare_find_names(records, n_exist=100, n_missing=10):
existing_names = [name for name, _ in records]
find_existing = random.sample(existing_names, n_exist) # Имена, которые есть
find_missing = [f"None_{i}" for i in range(n_missing)] # Не существующие имена
return find_existing + find_missing
# Подготовка списка имен для удаления
def prepare_delete_names(records, n_delete=50):
existing_names = [name for name, _ in records]
return random.sample(existing_names, n_delete) # случайные имена для удаления
# Обертка для измерения времени выполнения функции
def measure_time(func, *args):
start = time.perf_counter() # Точное время начала
result = func(*args) # Вызов функции
return result, time.perf_counter() - start # Возвращаем результат и время выполнения
# Определение структур данных и соответствующих функций для операций
STRUCTURES = [
("LinkedList", st.ll_insert, st.ll_find, st.ll_delete),
("HashTable", st.ht_insert, st.ht_find, st.ht_delete),
("BST", st.bst_insert, st.bst_find, st.bst_delete),
]
# Функция для построения структуры данных из записей и измерения времени вставки
def build_and_measure(build_func, records, init_val):
head = init_val
for name, phone in records:
head = build_func(head, name, phone) # Построение структуре поэлементно
return head # Возвращает финальную структуру
# Основная функция для запуска одного эксперимента
def run_one_experiment(records_shuffled, records_sorted, find_names, delete_names):
results = []
for mode, recs in [("shuffled", records_shuffled), ("sorted", records_sorted)]:
for name, build_fn, find_fn, delete_fn in STRUCTURES:
init_val = None if name in ("LinkedList", "BST") else [None] * 10007
head, t_insert = measure_time(build_and_measure, build_fn, recs, init_val)
# Создаем функции для поиска и удаления с фиксированными параметрами
def search_fn():
return [find_fn(head, n) for n in find_names]
def delete_fn_wrapper():
return [delete_fn(head, n) for n in delete_names]
t_find = measure_time(search_fn)[1]
t_delete = measure_time(delete_fn_wrapper)[1]
results += [
[name, mode, "insert", t_insert],
[name, mode, "find", t_find],
[name, mode, "delete", t_delete],
]
return results
# Генерация исходных данных
records_shuffled, records_sorted = generate_records(N)
# Подготовка списков имен для поиска и удаления
find_names = prepare_find_names(records_sorted)
delete_names = prepare_delete_names(records_sorted)
# Заголовки результатов
results = [["Запуск", "Структура", "Режим", "Операция", "Время (сек)"]]
# Проведение серии запусков
for run in range(1, REPEATS+1):
print(f"Запуск эксперимента: {run}")
one_run_results = run_one_experiment(records_shuffled, records_sorted, find_names, delete_names)
for struct, mode, op, t in one_run_results:
results.append([run, struct, mode, op, t]) # Добавляем результаты каждого запуска
# Подсчет средних значений по результатам
groups = {}
for row in results[1:]:
key = tuple(row[1:4]) # Ключ — название структуры, режим, тип операции
groups.setdefault(key, []).append(row[4]) # Собираем времена для среднего
for key, times in groups.items():
avg_time = sum(times)/len(times)
results.append(["average"] + list(key) + [avg_time]) # Средний результат
# Запись итоговых данных в CSV файл
with open("results.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerows(results)
print("Результаты сохранены")

62
MarkinAM/1/plot.py Normal file
View File

@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
"""
Created on Sat May 23 07:31:00 2026
@author: 79080
"""
import matplotlib.pyplot as plt
import numpy as np
data = {
("LinkedList", "shuffled", "insert"): 7.027040939,
("HashTable", "shuffled", "insert"): 0.0335156,
("BST", "shuffled", "insert"): 0.0416449599,
("LinkedList", "shuffled", "find"): 0.06288604,
("HashTable", "shuffled", "find"): 0.000380139,
("BST", "shuffled", "find"): 0.004672663999,
("LinkedList", "shuffled", "delete"): 0.02015744,
("HashTable", "shuffled", "delete"): 0.00018072,
("BST", "shuffled", "delete"): 0.00052726,
("LinkedList", "sorted", "insert"): 6.9302003,
("HashTable", "sorted", "insert"): 0.0654692,
("BST", "sorted", "insert"): 9.4514979003174,
("LinkedList", "sorted", "find"): 0.0654692,
("HashTable", "sorted", "find"): 0.0003763999,
("BST", "sorted", "find"): 0.11124382,
("LinkedList", "sorted", "delete"): 0.02090885999,
("HashTable", "sorted", "delete"): 0.0001772999,
("BST", "sorted", "delete"): 0.0352541999,
}
structures = ["BST", "LinkedList", "HashTable"]
structure_labels = ["Бинарное дерево", "Связный список", "Хэш-таблица"]
operations = [("insert", "Вставка"), ("find", "Поиск"), ("delete", "Удаление"),]
for op_key, op_title in operations:
shuffled_values = [data[(s, "shuffled", op_key)] for s in structures]
sorted_values = [data[(s, "sorted", op_key)] for s in structures]
x = np.arange(len(structures))
width = 0.40
plt.figure(figsize=(10, 5))
plt.bar(x - width / 2, shuffled_values, width, label="Случайный")
plt.bar(x + width / 2, sorted_values, width, label="Отсортированный")
plt.title(op_title)
plt.ylabel("Время (сек)")
plt.xticks(x, structure_labels)
plt.legend()
plt.grid(axis="y", alpha=0.3)
plt.tight_layout()
plt.savefig(f"{op_key}_chart.svg", format="svg")
plt.show()

109
MarkinAM/1/results.csv Normal file
View File

@ -0,0 +1,109 @@
‡àïóñê;‘òðóêòóðà;<3B>åæèì;Žïåðàöèß;‚ðåìß (ñåê)
1;LinkedList;shuffled;insert;7.483020299987402
1;LinkedList;shuffled;find;0.06628810000256635
1;LinkedList;shuffled;delete;0.02061489998595789
1;HashTable;shuffled;insert;0.033168200025102124
1;HashTable;shuffled;find;0.00038240000139921904
1;HashTable;shuffled;delete;0.0001829999964684248
1;BST;shuffled;insert;9.363271000009263
1;BST;shuffled;find;0.08526839999831282
1;BST;shuffled;delete;0.0359345999895595
1;LinkedList;sorted;insert;6.27129220002098
1;LinkedList;sorted;find;0.05525940001825802
1;LinkedList;sorted;delete;0.019091399997705594
1;HashTable;sorted;insert;0.033685100002912804
1;HashTable;sorted;find;0.00036959999124519527
1;HashTable;sorted;delete;0.0001768999791238457
1;BST;sorted;insert;9.476465000014286
1;BST;sorted;find;0.15770760001032613
1;BST;sorted;delete;0.04394249999313615
2;LinkedList;shuffled;insert;7.022043400007533
2;LinkedList;shuffled;find;0.06466269999509677
2;LinkedList;shuffled;delete;0.020482599997194484
2;HashTable;shuffled;insert;0.03347709999070503
2;HashTable;shuffled;find;0.00038389998371712863
2;HashTable;shuffled;delete;0.00018360000103712082
2;BST;shuffled;insert;9.920325399987632
2;BST;shuffled;find;0.0905940999800805
2;BST;shuffled;delete;0.034021200001006946
2;LinkedList;sorted;insert;6.864317100000335
2;LinkedList;sorted;find;0.08549359999597073
2;LinkedList;sorted;delete;0.022144999995362014
2;HashTable;sorted;insert;0.03350010002031922
2;HashTable;sorted;find;0.00036700000055134296
2;HashTable;sorted;delete;0.00017069999012164772
2;BST;sorted;insert;9.536676299991086
2;BST;sorted;find;0.08340400000452064
2;BST;sorted;delete;0.030526599992299452
3;LinkedList;shuffled;insert;6.969124499999452
3;LinkedList;shuffled;find;0.06854619999649003
3;LinkedList;shuffled;delete;0.02146240000729449
3;HashTable;shuffled;insert;0.03401190001750365
3;HashTable;shuffled;find;0.0003826000029221177
3;HashTable;shuffled;delete;0.00018060000729747117
3;BST;shuffled;insert;10.043598499993095
3;BST;shuffled;find;0.1357482000021264
3;BST;shuffled;delete;0.034065899992128834
3;LinkedList;sorted;insert;6.720142100006342
3;LinkedList;sorted;find;0.06434230000013486
3;LinkedList;sorted;delete;0.02026249998016283
3;HashTable;sorted;insert;0.033756200020434335
3;HashTable;sorted;find;0.00037399999564513564
3;HashTable;sorted;delete;0.00017690000822767615
3;BST;sorted;insert;9.34776529998635
3;BST;sorted;find;0.08204570002271794
3;BST;sorted;delete;0.03302499998244457
4;LinkedList;shuffled;insert;6.3915931999799795
4;LinkedList;shuffled;find;0.0560997000138741
4;LinkedList;shuffled;delete;0.018670899997232482
4;HashTable;shuffled;insert;0.03313269998761825
4;HashTable;shuffled;find;0.00037189997965469956
4;HashTable;shuffled;delete;0.00017690000822767615
4;BST;shuffled;insert;9.333172499987995
4;BST;shuffled;find;0.08687150001060218
4;BST;shuffled;delete;0.034476200002245605
4;LinkedList;sorted;insert;7.357170000002952
4;LinkedList;sorted;find;0.05717489999369718
4;LinkedList;sorted;delete;0.01926840000669472
4;HashTable;sorted;insert;0.03582940000342205
4;HashTable;sorted;find;0.00037510000402107835
4;HashTable;sorted;delete;0.0001753999968059361
4;BST;sorted;insert;9.246661200013477
4;BST;sorted;find;0.10621920000994578
4;BST;sorted;delete;0.03642769998987205
5;LinkedList;shuffled;insert;7.269423299992923
5;LinkedList;shuffled;find;0.0588335000211373
5;LinkedList;shuffled;delete;0.019556400016881526
5;HashTable;shuffled;insert;0.0337881000014022
5;HashTable;shuffled;find;0.00037990001146681607
5;HashTable;shuffled;delete;0.00017949999892152846
5;BST;shuffled;insert;9.497857399983332
5;BST;shuffled;find;0.08515099997748621
5;BST;shuffled;delete;0.03663840002263896
5;LinkedList;sorted;insert;7.438080099993385
5;LinkedList;sorted;find;0.06507609999971464
5;LinkedList;sorted;delete;0.02377699999487959
5;HashTable;sorted;insert;0.03366790001746267
5;HashTable;sorted;find;0.00039629999082535505
5;HashTable;sorted;delete;0.00018659999477677047
5;BST;sorted;insert;9.649921900010668
5;BST;sorted;find;0.132814500015229
5;BST;sorted;delete;0.03234919998794794
average;LinkedList;shuffled;insert;7.027040939993458
average;LinkedList;shuffled;find;0.06288604000583291
average;LinkedList;shuffled;delete;0.020157440000912175
average;HashTable;shuffled;insert;0.03351560000446625
average;HashTable;shuffled;find;0.0003801399958319962
average;HashTable;shuffled;delete;0.00018072000239044428
average;BST;shuffled;insert;0.041644959945127
average;BST;shuffled;find;0.0005272612374
average;BST;shuffled;delete;0.03502726000151597
average;LinkedList;sorted;insert;6.930200300004799
average;LinkedList;sorted;find;0.06546926000155509
average;LinkedList;sorted;delete;0.02090885999496095
average;HashTable;sorted;insert;0.03408774001291022
average;HashTable;sorted;find;0.00037639999645762147
average;HashTable;sorted;delete;0.00017729999381117524
average;BST;sorted;insert;9.451497940003174
average;BST;sorted;find;0.1124382000125479
average;BST;sorted;delete;0.035254199989140034
1 ‡àïóñê ‘òðóêòóðà �åæèì Žïåðàöèß ‚ðåìß (ñåê)
2 1 LinkedList shuffled insert 7.483020299987402
3 1 LinkedList shuffled find 0.06628810000256635
4 1 LinkedList shuffled delete 0.02061489998595789
5 1 HashTable shuffled insert 0.033168200025102124
6 1 HashTable shuffled find 0.00038240000139921904
7 1 HashTable shuffled delete 0.0001829999964684248
8 1 BST shuffled insert 9.363271000009263
9 1 BST shuffled find 0.08526839999831282
10 1 BST shuffled delete 0.0359345999895595
11 1 LinkedList sorted insert 6.27129220002098
12 1 LinkedList sorted find 0.05525940001825802
13 1 LinkedList sorted delete 0.019091399997705594
14 1 HashTable sorted insert 0.033685100002912804
15 1 HashTable sorted find 0.00036959999124519527
16 1 HashTable sorted delete 0.0001768999791238457
17 1 BST sorted insert 9.476465000014286
18 1 BST sorted find 0.15770760001032613
19 1 BST sorted delete 0.04394249999313615
20 2 LinkedList shuffled insert 7.022043400007533
21 2 LinkedList shuffled find 0.06466269999509677
22 2 LinkedList shuffled delete 0.020482599997194484
23 2 HashTable shuffled insert 0.03347709999070503
24 2 HashTable shuffled find 0.00038389998371712863
25 2 HashTable shuffled delete 0.00018360000103712082
26 2 BST shuffled insert 9.920325399987632
27 2 BST shuffled find 0.0905940999800805
28 2 BST shuffled delete 0.034021200001006946
29 2 LinkedList sorted insert 6.864317100000335
30 2 LinkedList sorted find 0.08549359999597073
31 2 LinkedList sorted delete 0.022144999995362014
32 2 HashTable sorted insert 0.03350010002031922
33 2 HashTable sorted find 0.00036700000055134296
34 2 HashTable sorted delete 0.00017069999012164772
35 2 BST sorted insert 9.536676299991086
36 2 BST sorted find 0.08340400000452064
37 2 BST sorted delete 0.030526599992299452
38 3 LinkedList shuffled insert 6.969124499999452
39 3 LinkedList shuffled find 0.06854619999649003
40 3 LinkedList shuffled delete 0.02146240000729449
41 3 HashTable shuffled insert 0.03401190001750365
42 3 HashTable shuffled find 0.0003826000029221177
43 3 HashTable shuffled delete 0.00018060000729747117
44 3 BST shuffled insert 10.043598499993095
45 3 BST shuffled find 0.1357482000021264
46 3 BST shuffled delete 0.034065899992128834
47 3 LinkedList sorted insert 6.720142100006342
48 3 LinkedList sorted find 0.06434230000013486
49 3 LinkedList sorted delete 0.02026249998016283
50 3 HashTable sorted insert 0.033756200020434335
51 3 HashTable sorted find 0.00037399999564513564
52 3 HashTable sorted delete 0.00017690000822767615
53 3 BST sorted insert 9.34776529998635
54 3 BST sorted find 0.08204570002271794
55 3 BST sorted delete 0.03302499998244457
56 4 LinkedList shuffled insert 6.3915931999799795
57 4 LinkedList shuffled find 0.0560997000138741
58 4 LinkedList shuffled delete 0.018670899997232482
59 4 HashTable shuffled insert 0.03313269998761825
60 4 HashTable shuffled find 0.00037189997965469956
61 4 HashTable shuffled delete 0.00017690000822767615
62 4 BST shuffled insert 9.333172499987995
63 4 BST shuffled find 0.08687150001060218
64 4 BST shuffled delete 0.034476200002245605
65 4 LinkedList sorted insert 7.357170000002952
66 4 LinkedList sorted find 0.05717489999369718
67 4 LinkedList sorted delete 0.01926840000669472
68 4 HashTable sorted insert 0.03582940000342205
69 4 HashTable sorted find 0.00037510000402107835
70 4 HashTable sorted delete 0.0001753999968059361
71 4 BST sorted insert 9.246661200013477
72 4 BST sorted find 0.10621920000994578
73 4 BST sorted delete 0.03642769998987205
74 5 LinkedList shuffled insert 7.269423299992923
75 5 LinkedList shuffled find 0.0588335000211373
76 5 LinkedList shuffled delete 0.019556400016881526
77 5 HashTable shuffled insert 0.0337881000014022
78 5 HashTable shuffled find 0.00037990001146681607
79 5 HashTable shuffled delete 0.00017949999892152846
80 5 BST shuffled insert 9.497857399983332
81 5 BST shuffled find 0.08515099997748621
82 5 BST shuffled delete 0.03663840002263896
83 5 LinkedList sorted insert 7.438080099993385
84 5 LinkedList sorted find 0.06507609999971464
85 5 LinkedList sorted delete 0.02377699999487959
86 5 HashTable sorted insert 0.03366790001746267
87 5 HashTable sorted find 0.00039629999082535505
88 5 HashTable sorted delete 0.00018659999477677047
89 5 BST sorted insert 9.649921900010668
90 5 BST sorted find 0.132814500015229
91 5 BST sorted delete 0.03234919998794794
92 average LinkedList shuffled insert 7.027040939993458
93 average LinkedList shuffled find 0.06288604000583291
94 average LinkedList shuffled delete 0.020157440000912175
95 average HashTable shuffled insert 0.03351560000446625
96 average HashTable shuffled find 0.0003801399958319962
97 average HashTable shuffled delete 0.00018072000239044428
98 average BST shuffled insert 0.041644959945127
99 average BST shuffled find 0.0005272612374
100 average BST shuffled delete 0.03502726000151597
101 average LinkedList sorted insert 6.930200300004799
102 average LinkedList sorted find 0.06546926000155509
103 average LinkedList sorted delete 0.02090885999496095
104 average HashTable sorted insert 0.03408774001291022
105 average HashTable sorted find 0.00037639999645762147
106 average HashTable sorted delete 0.00017729999381117524
107 average BST sorted insert 9.451497940003174
108 average BST sorted find 0.1124382000125479
109 average BST sorted delete 0.035254199989140034

198
MarkinAM/1/structures.py Normal file
View File

@ -0,0 +1,198 @@
# === 1. Связный список (LinkedList) ===
def ll_insert(head, name, phone):
# Вставка новой записи или обновление существующей
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"]
# Если не нашли, добавляем в конец
current = head
while current['next'] is not None:
current = current['next']
current['next'] = {'name': name, 'phone': phone, 'next': None}
return head
def ll_find(head, name):
"""Ищет запись по имени, возвращает телефон или None."""
current = head
while current:
if current['name'] == name:
return current['phone']
current = current['next']
return None
def ll_delete(head, name):
current = head
previous = None
while current is not None:
if current['name'] == name:
if previous is None:
return current['next']
previous['next'] = current['next']
return head
previous = current
current = current['next']
return head
def ll_list_all(head):
#Собирает все записи в отсортированный список кортежей.
records = []
current = head
while current:
records.append((current['name'], current['phone']))
current = current['next']
# Сортируем по имени
return sorted(records, key=lambda x: x[0])
# === 2. Хеш-таблица (HashTable) ===
def my_hash(s, M):
B = 31
n = len(s)
h = 0
for i in range(n):
h += ord(s[i]) * (B ** (n - 1 - i))
return h % M
def ht_insert(buckets, name, phone):
index = my_hash(name, len(buckets))
# Вставляем в соответствующий бакет
buckets[index] = ll_insert(buckets[index], name, phone)
return buckets
def ht_find(buckets, name):
index = my_hash(name, len(buckets))
# Ищем внутри бакета
return ll_find(buckets[index], name)
def ht_delete(buckets, name):
index = my_hash(name, len(buckets))
# Удаляем внутри бакета
buckets[index] = ll_delete(buckets[index], name)
return buckets
def ht_list_all(buckets):
# Собираем все записи из бакетов
result = []
for i in range(len(buckets)):
result += ll_list_all(buckets[i])
# Сортируем по имени
result.sort(key=lambda x: x[0])
return result
# === 3. Двоичное дерево поиска (BST) ===
def bst_insert(root, name, phone):
if root is None:
return {'name': name, 'phone': phone,'left': None, 'right': None}
current = root
while True:
# если такое имя уже есть — меняем телефон
if name == current['name']:
current['phone'] = phone
return root
# если новое имя меньше — идём влево
if name < current['name']:
if current['left'] is None:
current['left'] = {'name': name, 'phone': phone,'left': None, 'right': None}
return root
current = current['left']
# если новое имя больше — идём вправо
else:
if current['right'] is None:
current['right'] = {'name': name, 'phone': phone,'left': None, 'right': None}
return root
current = current['right']
def bst_find(root, name):
current = root
while current is not None:
if name == current['name']:
return current['phone']
if name < current['name']:
current = current['left']
else:
current = current['right']
return None
def bst_delete(root, name):
current = root
previous = None
while current is not None and current['name'] != name:
previous = current
if name < current['name']:
current = current['left']
else:
current = current['right']
# если не нашли
if current is None:
return root
# 2. Если у узла два потомка
if current['left'] is not None and current['right'] is not None:
successor_parent = current
successor = current['right']
# ищем минимальный узел в правом поддереве
while successor['left'] is not None:
successor_parent = successor
successor = successor['left']
# копируем данные successor в current
current['name'] = successor['name']
current['phone'] = successor['phone']
# теперь удаляем successor
current = successor
previous = successor_parent
#3
if current['left'] is not None:
child = current['left']
else:
child = current['right']
# 4. Если удаляем корень
if previous is None:
return child
# 5. Переподключаем родителя
if previous['left'] is current:
previous['left'] = child
else:
previous['right'] = child
return root
def bst_list_all(root):
result = []
def inorder(node):
if node is None:
return
inorder(node['left'])
result.append((node['name'], node['phone']))
inorder(node['right'])
inorder(root)
return result

74
MarkinAM/2/classes.py Normal file
View File

@ -0,0 +1,74 @@
class Cell:
"""Представляет одну клетку лабиринта."""
def __init__(self, x: int, y: int, is_wall: bool = False,
is_start: bool = False, is_exit: bool = False):
self.x = x
self.y = y
self.is_wall = is_wall
self.is_start = is_start
self.is_exit = is_exit
def is_passable(self) -> bool:
"""True, если клетка проходима (не стена)."""
return not self.is_wall
def __repr__(self):
if self.is_start:
return "S"
if self.is_exit:
return "E"
return "#" if self.is_wall else "."
def __eq__(self, other):
return isinstance(other, Cell) and self.x == other.x and self.y == other.y
def __hash__(self):
return hash((self.x, self.y))
class Maze:
"""Хранит двумерную сетку клеток, размеры и ссылки на старт/выход."""
def __init__(self, width: int, height: int, cells: list[list[Cell]],
start: Cell, exit_cell: Cell):
self.width = width
self.height = height
self.cells = cells # cells[y][x]
self.start = start
self.exit = exit_cell
def get_cell(self, x: int, y: int) -> Cell:
return self.cells[y][x]
def get_neighbors(self, cell: Cell) -> list[Cell]:
"""Возвращает список проходимых соседей (вверх, вниз, влево, вправо)."""
neighbors = []
for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]:
nx, ny = cell.x + dx, cell.y + dy
if 0 <= nx < self.width and 0 <= ny < self.height:
neighbor = self.cells[ny][nx]
if neighbor.is_passable():
neighbors.append(neighbor)
return neighbors
def __repr__(self):
lines = []
for row in self.cells:
lines.append("".join(str(c) for c in row))
return "\n".join(lines)
class Player:
def __init__(self, start_cell):
self.current = start_cell
def place(self, cell):
self.current = cell
def getPosition(self):
return self.current.getPosition()
def __str__(self):
x, y = self.getPosition()
return f"Player({x}, {y})"

138
MarkinAM/2/experiment.py Normal file
View File

@ -0,0 +1,138 @@
import csv
import os
import statistics
import matplotlib.pyplot as plt
from maze_builder import TextFileMazeBuilder
from solver import MazeSolver
from strategies import BFSStrategy, DFSStrategy, AStarStrategy
# --- НАСТРОЙКИ ---
MAZES_DIR = "mazes"
OUTPUT_CSV = "results.csv"
RUNS = 10 # Количество запусков для усреднения
PLOTS_DIR = "plots" # Новая папка для графиков
STRATEGIES = {
"BFS": BFSStrategy,
"DFS": DFSStrategy,
"A*": AStarStrategy,
}
# Словарь для хранения всех данных для графиков
all_data = {}
# Создаем папку для графиков, если её нет
os.makedirs(PLOTS_DIR, exist_ok=True)
builder = TextFileMazeBuilder()
maze_files = sorted(f for f in os.listdir(MAZES_DIR) if f.endswith(".txt"))
rows = []
print("=== СТАРТ ЭКСПЕРИМЕНТА ===\n")
# --- ОСНОВНОЙ ЦИКЛ ЭКСПЕРИМЕНТА ---
for maze_file in maze_files:
maze_name = maze_file.replace(".txt", "")
filepath = os.path.join(MAZES_DIR, maze_file)
try:
maze = builder.build_from_file(filepath)
except ValueError as e:
print(f" [!] Пропуск {maze_file}: {e}")
continue # Переходим к следующему файлу, если этот не загрузился
# Эта строка теперь выполняется для каждого успешного лабиринта
print(f"\n{'='*50}")
print(f"Лабиринт: {maze_name} ({maze.width}×{maze.height})")
all_data[maze_name] = {}
for strat_name, StratClass in STRATEGIES.items():
times, visited_counts, path_lengths = [], [], []
run_stats = []
for run_num in range(1, RUNS + 1):
solver = MazeSolver(maze, StratClass())
stats = solver.solve()
times.append(stats.time_ms)
visited_counts.append(stats.visited_cells)
path_lengths.append(stats.path_length)
# Сохраняем данные каждой попытки
run_stats.append({
'попытка': run_num,
'время_мс': stats.time_ms,
'посещено_клеток': stats.visited_cells,
'длина_пути': stats.path_length
})
print(f" {strat_name} | Попытка {run_num}/{RUNS} | Время: {stats.time_ms:.2f} мс")
# Вычисляем средние значения
avg_time = statistics.mean(times)
avg_visited = statistics.mean(visited_counts)
valid_path_lengths = [p for p in path_lengths if p is not None]
avg_path = statistics.mean(valid_path_lengths) if valid_path_lengths else None
print(f" {strat_name:10s} | СРЕДНЕЕ: время {avg_time:.2f} мс | "
f"посещено {avg_visited:.1f} | путь {avg_path if avg_path is not None else ''}")
rows.append({
"лабиринт": maze_name,
"стратегия": strat_name,
"время_мс": round(avg_time, 6),
"посещено_клеток": round(avg_visited, 1),
"длина_пути": round(avg_path, 1) if avg_path is not None else None,
})
all_data[maze_name][strat_name] = run_stats
# --- СОХРАНЕНИЕ CSV ---
with open(OUTPUT_CSV, "w", newline="", encoding="utf-8") as csvfile:
fieldnames = ["лабиринт", "стратегия", "время_мс", "посещено_клеток", "длина_пути"]
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(rows)
print(f"\n✓ Результаты сохранены в {OUTPUT_CSV}")
# --- ПОСТРОЕНИЕ ГРАФИКОВ ---
print("\n=== ПОСТРОЕНИЕ ГРАФИКОВ ===")
for maze_name, strategies_data in all_data.items():
print(f"\nСтроим графики для лабиринта: {maze_name}")
# Создаем ОДИН график для времени выполнения
fig, ax = plt.subplots(figsize=(10, 6))
fig.suptitle(f"Сравнение времени выполнения алгоритмов\nЛабиринт '{maze_name}'", fontsize=14)
ax.set_title("Время выполнения (мс)")
ax.set_xlabel("Номер попытки")
ax.set_ylabel("Время (мс)")
for strat_name in STRATEGIES.keys():
# Извлекаем только данные о времени выполнения
data_points = [
run['время_мс'] for run in strategies_data.get(strat_name, [])
]
if data_points:
x_values = range(1, len(data_points) + 1)
ax.plot(x_values, data_points,
marker='o',
label=strat_name,
linewidth=2)
ax.legend(title="Алгоритм")
ax.grid(True, which='both', linestyle='--', linewidth=0.5)
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
# Сохраняем график в папку 'plots'
plot_filename = os.path.join(PLOTS_DIR, f"plot_{maze_name}.png")
plt.savefig(plot_filename)
plt.close()
print(f"\n✓ Графики времени выполнения сохранены в папку '{PLOTS_DIR}'")
print("=== ЭКСПЕРИМЕНТ ЗАВЕРШЕН ===")

View File

@ -0,0 +1,52 @@
from abc import ABC, abstractmethod
from classes import Cell, Maze
class MazeBuilder(ABC):
#Интерфейс строителя лабиринта (паттерн Builder).
@abstractmethod
def build_from_file(self, filename: str) -> Maze:
#Читает файл и возвращает готовый объект Maze.
...
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename: str) -> Maze:
with open(filename, "r", encoding="utf-8") as f:
lines = f.read().splitlines()
if not lines:
raise ValueError("Файл лабиринта пуст.")
height = len(lines)
width = max(len(line) for line in lines)
# Дополняем строки до одинаковой длины (стенами)
lines = [line.ljust(width, "#") for line in lines]
cells: list[list[Cell]] = []
start: Cell | None = None
exit_cell: Cell | None = None
for y, line in enumerate(lines):
row = []
for x, ch in enumerate(line):
is_wall = ch == "#"
is_start = ch == "S"
is_exit = ch == "E"
cell = Cell(x, y, is_wall=is_wall, is_start=is_start, is_exit=is_exit)
if is_start:
start = cell
if is_exit:
exit_cell = cell
row.append(cell)
cells.append(row)
if start is None:
raise ValueError("Лабиринт не содержит стартовой клетки (S).")
if exit_cell is None:
raise ValueError("Лабиринт не содержит выхода (E).")
return Maze(width, height, cells, start, exit_cell)

100
MarkinAM/2/mazes/large.txt Normal file
View File

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

View File

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

View File

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

View File

@ -0,0 +1,6 @@
S....................................................................................................
.........................................................................................................
.........................................................................................................
.........................................................................................................
.........................................................................................................
.........................................................................................E...............

View File

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

71
MarkinAM/2/observer.py Normal file
View File

@ -0,0 +1,71 @@
from abc import ABC, abstractmethod
from classes import Maze, Cell
class Observer(ABC):
"""Интерфейс наблюдателя."""
@abstractmethod
def update(self, event: dict) -> None:
"""
event словарь с ключом "type":
"maze_loaded" лабиринт загружен
"path_found" путь найден
"no_path" путь не найден
"""
...
class ConsoleView(Observer):
"""
Наблюдатель: выводит лабиринт и путь в консоль.
Символы:
# — стена
. проход
S старт
E выход
* найденный путь
@ текущее положение игрока
"""
def update(self, event: dict) -> None:
event_type = event.get("type")
if event_type == "maze_loaded":
print("\n[ConsoleView] Лабиринт загружен:")
self.render(event["maze"])
elif event_type == "path_found":
print("\n[ConsoleView] Путь найден!")
self.render(event["maze"], path=event.get("path"), player=event.get("player"))
elif event_type == "no_path":
print("\n[ConsoleView] Путь не найден.")
elif event_type == "move":
print(f"\n[ConsoleView] Игрок переместился в ({event['x']}, {event['y']})")
self.render(event["maze"], path=event.get("path"), player=event.get("player"))
def render(self, maze: Maze, path: list[Cell] | None = None,
player: Cell | None = None) -> None:
path_set = set(path) if path else set()
for y in range(maze.height):
row_str = ""
for x in range(maze.width):
cell = maze.get_cell(x, y)
if player and cell == player:
row_str += "@"
elif cell.is_start:
row_str += "S"
elif cell.is_exit:
row_str += "E"
elif cell in path_set:
row_str += "*"
elif cell.is_wall:
row_str += "#"
else:
row_str += "."
print(row_str)

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
MarkinAM/2/report.docx Normal file

Binary file not shown.

13
MarkinAM/2/results.csv Normal file
View File

@ -0,0 +1,13 @@
лабиринт,стратегия,время_мс,посещено_клеток,длина_пути
large,BFS,7.59029,2952,662
large,DFS,10.92704,4082,1566
large,A*,5.40801,1073,662
medium,BFS,0.2096,80,22
medium,DFS,0.79632,300,28
medium,A*,0.13978,28,22
open,BFS,1.57724,550,95
open,DFS,1.04963,303,303
open,A*,0.64328,95,95
small,BFS,0.06765,25,13
small,DFS,0.03831,13,13
small,A*,0.06389,13,13
1 лабиринт стратегия время_мс посещено_клеток длина_пути
2 large BFS 7.59029 2952 662
3 large DFS 10.92704 4082 1566
4 large A* 5.40801 1073 662
5 medium BFS 0.2096 80 22
6 medium DFS 0.79632 300 28
7 medium A* 0.13978 28 22
8 open BFS 1.57724 550 95
9 open DFS 1.04963 303 303
10 open A* 0.64328 95 95
11 small BFS 0.06765 25 13
12 small DFS 0.03831 13 13
13 small A* 0.06389 13 13

71
MarkinAM/2/solver.py Normal file
View File

@ -0,0 +1,71 @@
import time
from dataclasses import dataclass
from classes import Maze, Cell
from strategies import PathFindingStrategy
from observer import Observer
@dataclass
class SearchStats:
"""Результаты одного запуска поиска."""
time_ms: float # время выполнения в миллисекундах
visited_cells: int # количество посещённых клеток
path_length: int # длина найденного пути (0 если не найден)
path: list[Cell] # сам путь
class MazeSolver:
def __init__(self, maze: Maze, strategy: PathFindingStrategy | None = None):
self.maze = maze
self.strategy = strategy
self._observers: list[Observer] = []
# ── Strategy ──────────────────────────────────────────────────────────────
def set_strategy(self, strategy: PathFindingStrategy) -> None:
"""Динамически меняет алгоритм поиска."""
self.strategy = strategy
# ── Observer ──────────────────────────────────────────────────────────────
def add_observer(self, observer: Observer) -> None:
self._observers.append(observer)
def remove_observer(self, observer: Observer) -> None:
self._observers.remove(observer)
def _notify(self, event: dict) -> None:
for obs in self._observers:
obs.update(event)
# ── Solve ─────────────────────────────────────────────────────────────────
def solve(self) -> SearchStats:
"""Запускает поиск пути и возвращает статистику."""
if self.strategy is None:
raise RuntimeError("Стратегия не задана. Используйте set_strategy().")
self._notify({"type": "maze_loaded", "maze": self.maze})
t_start = time.perf_counter()
path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
t_end = time.perf_counter()
time_ms = (t_end - t_start) * 1000
visited = getattr(self.strategy, "visited_count", 0)
stats = SearchStats(
time_ms=time_ms,
visited_cells=visited,
path_length=len(path),
path=path,
)
if path:
self._notify({"type": "path_found", "maze": self.maze, "path": path})
else:
self._notify({"type": "no_path"})
return stats

119
MarkinAM/2/strategies.py Normal file
View File

@ -0,0 +1,119 @@
from abc import ABC, abstractmethod
from collections import deque
import heapq
from classes import Cell, Maze
class PathFindingStrategy(ABC):
"""Интерфейс стратегии поиска пути."""
@abstractmethod
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> list[Cell]:
...
# Вспомогательный метод восстановления пути по словарю предшественников
@staticmethod
def _reconstruct_path(came_from: dict, start: Cell, goal: Cell) -> list[Cell]:
path = []
current = goal
while current != start:
path.append(current)
current = came_from[current]
path.append(start)
path.reverse()
return path
# ── BFS ──────────────────────────────────────────────────────────────────────
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> list[Cell]:
queue = deque([start])
came_from: dict[Cell, Cell | None] = {start: None}
self.visited_count = 0
while queue:
current = queue.popleft()
self.visited_count += 1
if current == exit_cell:
return self._reconstruct_path(came_from, start, exit_cell)
for neighbor in maze.get_neighbors(current):
if neighbor not in came_from:
came_from[neighbor] = current
queue.append(neighbor)
return [] # путь не найден
# ── DFS ──────────────────────────────────────────────────────────────────────
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> list[Cell]:
stack = [start]
came_from: dict[Cell, Cell | None] = {start: None}
self.visited_count = 0
while stack:
current = stack.pop()
self.visited_count += 1
if current == exit_cell:
return self._reconstruct_path(came_from, start, exit_cell)
for neighbor in maze.get_neighbors(current):
if neighbor not in came_from:
came_from[neighbor] = current
stack.append(neighbor)
return []
# ── A* ───────────────────────────────────────────────────────────────────────
class AStarStrategy(PathFindingStrategy):
"""A* с манхэттенской эвристикой"""
def __init__(self):
self.visited_count = 0
def _heuristic(self, a: Cell, b: Cell) -> int:
return abs(a.x - b.x) + abs(a.y - b.y)
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> list[Cell]:
g_score = {start: 0}
parent: dict[Cell] = {start: None}
open_heap = [(self._heuristic(start, exit_cell), 0, start)]
closed_set: set[Cell] = set() # уже обработанные клетки
self.visited_count = 0
counter = 0 # счётчик для устранения неоднозначности
while open_heap:
_, _, current = heapq.heappop(open_heap)
if current in closed_set:
continue
closed_set.add(current)
self.visited_count += 1
if current == exit_cell:
return self._reconstruct_path(parent, start, exit_cell)
for neighbor in maze.get_neighbors(current):
if neighbor in closed_set:
continue
tentative_g = g_score[current]
if tentative_g < g_score.get(neighbor, float('inf')):
g_score[neighbor] = tentative_g
parent[neighbor] = current
f = tentative_g + self._heuristic(neighbor, exit_cell)
counter += 1
heapq.heappush(open_heap, (f, counter, neighbor))
return []

0
MarkinAM/428b.md Normal file
View File