1
0
forked from UNN/2026-rff_mp

Compare commits

...

39 Commits

Author SHA1 Message Date
7465167739 Merge pull request 'Отчеты' (#403) from starikovta/2026-rff_mp:task1-data-structures into develop
Reviewed-on: UNN/2026-rff_mp#403
2026-09-08 07:48:06 +00:00
657f864ed6 Merge pull request 'Добавить tsareveo' (#402) from tsareveo/2026-rff_mp:tsareveo-patch-1 into develop
Reviewed-on: UNN/2026-rff_mp#402
2026-09-08 07:47:44 +00:00
faf54114a0 Merge pull request 'PaulVA-lab2' (#405) from PaulVA/2026-rff_mp:PaulVA-lab2 into develop
Reviewed-on: UNN/2026-rff_mp#405
2026-09-08 07:47:05 +00:00
f1d9a64911 Перемещены лабораторные работы в папку PaulVA 2026-09-05 19:26:22 +03:00
c12f9ef1bf Добавлены лабораторные работы 1 и 2 2026-09-05 19:09:46 +03:00
972243d118 Отчеты 2026-09-05 17:16:35 +03:00
4fecfc8b0c Merge branch 'develop' into tsareveo-patch-1 2026-09-05 13:41:06 +00:00
115d6ac18d Добавить tsareveo 2026-09-05 13:19:10 +00:00
7eb345fedc Merge pull request 'nikitovie' (#401) from nikitovie/2026-rff_mp:nikitovie into develop
Reviewed-on: UNN/2026-rff_mp#401
2026-09-05 12:47:33 +00:00
571bde5320 Merge pull request 'LebedevES' (#397) from LebedevES/2026-rff_mp:LebedevES into develop
Reviewed-on: UNN/2026-rff_mp#397
2026-09-05 12:46:58 +00:00
182e97e0be Merge pull request 'FirsovAV' (#400) from LebedevES/2026-rff_mp:FirsovAV into develop
Reviewed-on: UNN/2026-rff_mp#400
2026-09-05 12:46:07 +00:00
7cf11cb3ef Загрузить файлы в «nikitovie/docs:» 2026-09-05 12:35:17 +00:00
3ec4ea8e0f Удалить nikitovie/docs:/gitkeep 2026-09-05 12:34:13 +00:00
471618449c Загрузить файлы в «nikitovie/docs:/data/zadanie2» 2026-09-05 12:33:02 +00:00
1549daa6a9 Загрузить файлы в «nikitovie/docs:/data/zadanie2» 2026-09-05 12:32:19 +00:00
49bbdda926 Загрузить файлы в «nikitovie/docs:/data/zadanie2» 2026-09-05 12:31:52 +00:00
8774dfb38c Загрузить файлы в «nikitovie/docs:/data/zadanie2» 2026-09-05 12:31:32 +00:00
634a882918 Удалить nikitovie/docs:/data/zadanie2/.gitkeep 2026-09-05 12:31:15 +00:00
865cdc7a4e Загрузить файлы в «nikitovie/docs:/data/zadanie2» 2026-09-05 12:31:04 +00:00
8ebedbaf2a Удалить nikitovie/docs:/data/.gitkeep 2026-09-05 12:28:04 +00:00
f1bb79cd58 Удалить nikitovie/docs:/data/zadanie_1/.gitkeep 2026-09-05 12:27:48 +00:00
80948d5363 Загрузить файлы в «nikitovie/docs:/data/zadanie_1» 2026-09-05 12:26:45 +00:00
f34137546b Добавить nikitovie/docs:/data/zadanie2/.gitkeep 2026-09-05 12:26:09 +00:00
e86eb5766a Удалить nikitovie/docs:/data/zadanie_2 2026-09-05 12:25:53 +00:00
e84264cf8b Добавить nikitovie/docs:/data/zadanie_2 2026-09-05 12:25:28 +00:00
cdd44303e4 Добавить nikitovie/docs:/data/zadanie_1/.gitkeep 2026-09-05 12:25:04 +00:00
59ff0051d9 Добавить nikitovie/docs:/data/.gitkeep 2026-09-05 12:24:25 +00:00
64c96253ba Добавить nikitovie/docs:/gitkeep 2026-09-05 12:22:49 +00:00
7fc4e1ab10 [1] zad 1, zad 2 2026-09-05 14:48:28 +03:00
8f9da375c1 [0] initial commit 2026-09-05 14:44:06 +03:00
58d0447aae [0] initial commit 2026-09-05 13:51:05 +03:00
672af89f8c [3] finale 2026-09-05 13:33:00 +03:00
ba9eaf7570 [2] zad 2 2026-09-05 13:23:06 +03:00
bb1eebe572 [1] zad 1 2026-09-05 13:21:50 +03:00
8184739210 [2] zadanie 2 2026-09-05 13:15:01 +03:00
9caaeb37a9 [1] zadanie 1 2026-09-05 13:10:55 +03:00
9c5302e8ff [0] initial commit 2026-09-05 13:06:43 +03:00
f95819cb5b [0] initial commit 2026-09-05 12:59:41 +03:00
52ab70ee71 Задание 1 2026-05-21 22:00:40 +03:00
79 changed files with 7186 additions and 0 deletions

1
2026-rff_mp Submodule

@ -0,0 +1 @@
Subproject commit 52c001a380431727397e4275c2be9d94fe5fcc8d

0
BobrovKN/425.txt Normal file
View File

292
BobrovKN/zadanie/zad 1/1.py Normal file
View File

@ -0,0 +1,292 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import time
import random
import csv
import sys
sys.setrecursionlimit(30000)
def ll_create_node(name, phone):
return {'name': name, 'phone': phone, 'next': None}
def ll_insert(head, name, phone):
if head is None:
return ll_create_node(name, phone)
if head['name'] == name:
head['phone'] = phone
return head
current = head
while current['next'] is not None:
if current['next']['name'] == name:
current['next']['phone'] = phone
return head
current = current['next']
current['next'] = ll_create_node(name, phone)
return head
def ll_find(head, name):
current = head
while current is not None:
if current['name'] == name:
return current['phone']
current = current['next']
return None
def ll_delete(head, name):
if head is None:
return None
if head['name'] == name:
return head['next']
current = head
while current['next'] is not None:
if current['next']['name'] == name:
current['next'] = current['next']['next']
return head
current = current['next']
return head
def ll_list_all(head):
records = []
current = head
while current is not None:
records.append((current['name'], current['phone']))
current = current['next']
records.sort(key=lambda x: x[0])
return records
def hash_function(name, table_size):
return sum(ord(c) for c in name) % table_size
def ht_create_table(size=2000):
return [None] * size
def ht_insert(table, name, phone):
index = hash_function(name, len(table))
table[index] = ll_insert(table[index], name, phone)
def ht_find(table, name):
index = hash_function(name, len(table))
return ll_find(table[index], name)
def ht_delete(table, name):
index = hash_function(name, len(table))
table[index] = ll_delete(table[index], name)
def ht_list_all(table):
all_records = []
for bucket in table:
if bucket is not None:
current = bucket
while current is not None:
all_records.append((current['name'], current['phone']))
current = current['next']
all_records.sort(key=lambda x: x[0])
return all_records
def bst_create_node(name, phone):
return {'name': name, 'phone': phone, 'left': None, 'right': None}
def bst_insert(root, name, phone):
if root is None:
return bst_create_node(name, phone)
current = root
while True:
if name < current['name']:
if current['left'] is None:
current['left'] = bst_create_node(name, phone)
break
else:
current = current['left']
elif name > current['name']:
if current['right'] is None:
current['right'] = bst_create_node(name, phone)
break
else:
current = current['right']
else:
current['phone'] = phone
break
return root
def bst_find(root, name):
current = root
while current is not None:
if name < current['name']:
current = current['left']
elif name > current['name']:
current = current['right']
else:
return current['phone']
return None
def bst_find_min(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
parent = None
current = root
while current is not None and current['name'] != name:
parent = current
if name < current['name']:
current = current['left']
else:
current = current['right']
if current is None:
return root
if current['left'] is None or current['right'] is None:
if current['left'] is not None:
child = current['left']
else:
child = current['right']
if parent is None:
return child
if parent['left'] == current:
parent['left'] = child
else:
parent['right'] = child
else:
successor_parent = current
successor = current['right']
while successor['left'] is not None:
successor_parent = successor
successor = successor['left']
current['name'] = successor['name']
current['phone'] = successor['phone']
if successor_parent['left'] == successor:
successor_parent['left'] = successor['right']
else:
successor_parent['right'] = successor['right']
return root
def bst_list_all(root):
records = []
stack = []
current = root
while stack or current is not None:
while current is not None:
stack.append(current)
current = current['left']
current = stack.pop()
records.append((current['name'], current['phone']))
current = current['right']
return records
def generate_data(n=10000):
records = [(f"User_{i:05d}", f"+7-999-{i:06d}") for i in range(n)]
records_shuffled = records.copy()
random.shuffle(records_shuffled)
records_sorted = sorted(records, key=lambda x: x[0])
return records_shuffled, records_sorted
def run_experiment(structure_name, insert_func, find_func, delete_func,
list_all_func, init_func, records, n_find=100):
data = init_func()
names = [r[0] for r in records]
start = time.perf_counter()
for name, phone in records:
if structure_name == "HashTable":
insert_func(data, name, phone)
else:
data = insert_func(data, name, phone)
insert_time = time.perf_counter() - start
find_names = random.sample(names, min(n_find, len(names)))
missing_names = [f"None_{i}" for i in range(10)]
all_find_names = find_names + missing_names
start = time.perf_counter()
for name in all_find_names:
if structure_name == "HashTable":
find_func(data, name)
else:
find_func(data, name)
find_time = time.perf_counter() - start
delete_names = random.sample(names, min(50, len(names)))
start = time.perf_counter()
for name in delete_names:
if structure_name == "HashTable":
delete_func(data, name)
else:
data = delete_func(data, name)
delete_time = time.perf_counter() - start
return insert_time, find_time, delete_time
def main():
print("Generating test data...")
records_shuffled, records_sorted = generate_data(10000)
results = []
structures = [
("LinkedList", ll_insert, ll_find, ll_delete, ll_list_all, lambda: None),
("HashTable", ht_insert, ht_find, ht_delete, ht_list_all, lambda: ht_create_table(2000)),
("BST", bst_insert, bst_find, bst_delete, bst_list_all, lambda: None)
]
for mode_name, records in [("random", records_shuffled), ("sorted", records_sorted)]:
print(f"\nMode: {mode_name}")
for struct_name, insert_f, find_f, delete_f, list_f, init_f in structures:
print(f" Testing {struct_name}...")
times = []
for run in range(5):
insert_t, find_t, delete_t = run_experiment(
struct_name, insert_f, find_f, delete_f, list_f, init_f, records
)
times.append((insert_t, find_t, delete_t))
print(f" Run {run+1}: insert={insert_t:.4f}s, find={find_t:.4f}s, delete={delete_t:.4f}s")
avg_insert = sum(t[0] for t in times) / 5
avg_find = sum(t[1] for t in times) / 5
avg_delete = sum(t[2] for t in times) / 5
results.append([struct_name, mode_name, "insert", avg_insert])
results.append([struct_name, mode_name, "find", avg_find])
results.append([struct_name, mode_name, "delete", avg_delete])
with open("results.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["Structure", "Mode", "Operation", "Time_seconds"])
writer.writerows(results)
print("\n" + "="*60)
print("RESULTS (average over 5 runs):")
print("="*60)
for row in results:
print(f"{row[0]:12} | {row[1]:8} | {row[2]:8} | {row[3]:.6f} sec")
print("\nResults saved to results.csv")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,122 @@
Методы Программирования
Структуры данных,
анализ 1 задания
Бобров К. Н.
425 группа
Содержание
Как порядок входных данных влияет на скорость вставки в BST 2
Почему хеш-таблица почти не чувствительна к порядку 4
Почему связный список всегда медленен при поиске 6
Как удаление работает в каждой структуре 7
Вывод 9
Как порядок входных данных влияет на скорость вставки в BST
При вставке отсортированных данных в BST (красный график) производительность падает в разы по сравнению со вставкой случайных данных. Это связано с тем, что отсортированная последовательность приводит к вырождению дерева в связанный список, тогда как случайный порядок вставки помогает сохранять дерево относительно сбалансированным.
При вставке элементов в отсортированном порядке (по возрастанию или убыванию):
?Каждый новый элемент всегда больше (или меньше) всех уже добавленных.
?В результате алгоритм каждый раз движется по одному и тому же направлению — только в правое или только в левое поддерево.
?Из-за этого дерево вырождается: каждый узел имеет не более одного потомка, структура напоминает линейный список.
?Высота такого дерева становится пропорциональной O(n).
?Каждая операция вставки требует в среднем O(n) сравнений, так как нужно проходить всю длину текущей цепочки от корня до самого глубокого листа.
?В итоге суммарная сложность вставки всех n элементов вырастает до O(n^2).
При случайной вставке:
?Элементы распределяются по дереву гораздо равномернее.
?Высока вероятность того, что дерево останется сбалансированным.
?Средняя высота дерева сохраняется на уровне O(logn).
?Каждая операция вставки в среднем требует O(logn) сравнений.
?Общая сложность вставки всех n элементов составляет  O(nlogn).
Вывод: разница в скорости объясняется различием в высоте дерева. В вырожденном случае высота равна O(n), и каждая вставка выполняется в ?n/logn раз медленнее по числу шагов, чем в сбалансированном случае с высотой O(logn).
Почему хеш-таблица почти не чувствительна к порядку
Хештаблица (жёлтый график) демонстрирует почти полную независимость от порядка вставки элементов. Это объясняется тем, что положение каждого элемента в структуре определяется исключительно значением его хешфункции, а не тем, в какой последовательности происходило добавление данных.
Основные причины нечувствительности к порядку вставки:
?Хеширование. Для каждого ключа вычисляется хешкод, который преобразуется в индекс ячейки. Один и тот же ключ всегда даёт один и тот же индекс независимо от того, когда и в каком порядке он был добавлен.
?Независимость операций. Вставка, поиск и удаление выполняются в среднем за O(1)O(1), поскольку алгоритм сразу вычисляет нужную позицию, не обходя структуру и не учитывая историю добавлений.
?Разрешение коллизий. Даже если порядок вставки влияет на расположение элементов внутри цепочки (метод цепочек) или на последовательность проб (открытая адресация), это касается лишь небольших групп элементов с одинаковыми хешами. Общая производительность остаётся стабильной.
?Рехеширование. При увеличении размера таблицы все элементы перераспределяются заново. Новый порядок определяется актуальной хеш-функцией и размером таблицы, а не исходной последовательностью вставки.
Итог: Время выполнения операций зависит от качества хеш-функции, коэффициента заполнения таблицы и метода разрешения коллизий, но не зависит от порядка добавления элементов.
Почему связный список всегда медленен при поиске
Связный список показывает низкую скорость поиска из-за необходимости последовательного обхода: чтобы найти элемент, требуется пройти по указателям от головы до нужного узла.
Почему это происходит:
?Отсутствие произвольного доступа. В отличие от массива, где доступ по индексу занимает O(1), в связном списке элементы приходится перебирать последовательно, что даёт сложность поиска O(n).
?Низкая локальность данных. Узлы списка разбросаны по памяти случайным образом. Это вызывает частые промахи кэша: процессор не может подгрузить блок соседних данных, и каждый переход по указателю оборачивается новым обращением к оперативной памяти.
?Дополнительная память на указатели. Каждый узел хранит не только полезные данные, но и указатель на следующий элемент. Это увеличивает объём памяти и ухудшает эффективность кэша — на те же данные приходится загружать больше информации.
?Затраты на разыменование указателей. На каждом шаге поиска процессору нужно:
oпрочитать текущий узел,
oизвлечь из него указатель на следующий,
oперейти по этому адресу.
Эти операции замедляют работу по сравнению с простым сдвигом индекса в массиве.
Итог: хотя алгоритмическая сложность обхода составляет O(n) как для массива (при линейном поиске), так и для связного списка, на практике список работает ощутимо медленнее из-за особенностей организации памяти и работы кэша.
Как удаление работает в каждой структуре
1. Связный список
Односвязный список: чтобы удалить узел, необходимо сначала найти предыдущий элемент и перенаправить его указатель next на узел, следующий за удаляемым. Исключение — удаление первого элемента: достаточно сдвинуть указатель head на второй узел.
Двусвязный список: удаление проще, поскольку у каждого узла есть указатели и на следующий (next), и на предыдущий (prev). При удалении обновляются ссылки обоих соседей: prev->next = next, next->prev = prev.
Сложность: в общем случае O(n) из-за необходимости поиска элемента; удаление головы или хвоста (при наличии прямой ссылки на хвост) выполняется за O(1).
2. Хештаблица
Сначала через хеш-функцию h(key) вычисляется индекс ячейки. Дальнейшие действия зависят от метода разрешения коллизий:
?Раздельная цепочка: элемент удаляется из связного списка (или другой структуры), находящегося по вычисленному индексу.
?Открытая адресация: ячейка помечается специальным маркером «удалён», а не просто как пустая — это важно для корректности последующих операций поиска.
Сложность: в среднем O(1), в худшем случае O(n) (при большом количестве коллизий).
3. Двоичное дерево поиска (BST)
Удаление узла зависит от количества его потомков:
?Нет детей (лист): узел просто удаляется, ссылка родителя обнуляется.
?Один ребёнок: удаляемый узел заменяется его единственным потомком — родитель «перепрыгивает» через удаляемый узел.
?Два ребёнка:
1.Находится преемник (самый левый (наименьший) узел в правом поддереве) или предшественник (самый правый (наибольший) узел в левом поддереве).
2.Значение преемника/предшественника копируется в удаляемый узел.
3.Преемник/предшественник рекурсивно удаляется — он гарантированно имеет не более одного ребёнка.
Сложность: O(h), где h — высота дерева. В сбалансированном дереве h=O(logn), в несбалансированном — до O(n).
Вывод
1. Частые вставки
Связный список — отличный выбор для частых вставок (особенно в середину), если не требуется быстрый доступ по индексу. Вставка в начало или конец выполняется за O(1), в середину — за O(n) (но без сдвига элементов, как в массиве).
Хештаблица — хорошо подходит для вставок по ключу, обеспечивая в среднем O(1).
2. Частый поиск
Хештаблица — лучший вариант для быстрого поиска по ключу. Среднее время — O(1), в худшем случае — O(n) (при сильных коллизиях).
Сбалансированное двоичное дерево поиска — предпочтительнее, если нужен поиск с гарантированной сложностью O(logn) даже в худшем случае.
3. Необходимость получать данные в отсортированном порядке
Массив / список — эффективен, если данные уже отсортированы или сортировка происходит редко, а последовательное чтение — часто. Доступ по индексу — O(1), но вставка и удаление в середину требуют O(n).
Отсортированный массив — удобен для поиска (бинарный поиск даёт (O(logn)), однако вставки и удаления обходятся в O(n).
Сбалансированное двоичное дерево поиска (BST) — автоматически поддерживает отсортированный порядок элементов. Все основные операции выполняются за O(logn). Идеальный вариант, когда данные часто изменяются и при этом требуется обход элементов в отсортированном порядке.

View File

@ -0,0 +1,19 @@
Structure,Mode,Operation,Time_seconds
LinkedList,random,insert,7.967956480104476
LinkedList,random,find,0.05891917999833822
LinkedList,random,delete,0.03816298004239797
HashTable,random,insert,0.39825033992528913
HashTable,random,find,0.002917400002479553
HashTable,random,delete,0.0021501399576663973
BST,random,insert,0.02822491992264986
BST,random,find,0.00023473985493183136
BST,random,delete,0.00016456004232168198
LinkedList,sorted,insert,8.014810599852353
LinkedList,sorted,find,0.058480959851294756
LinkedList,sorted,delete,0.04817821998149156
HashTable,sorted,insert,0.3703480200842023
HashTable,sorted,find,0.002751259971410036
HashTable,sorted,delete,0.0018340200185775757
BST,sorted,insert,7.301413399912417
BST,sorted,find,0.06847236007452011
BST,sorted,delete,0.03443789994344115
1 Structure Mode Operation Time_seconds
2 LinkedList random insert 7.967956480104476
3 LinkedList random find 0.05891917999833822
4 LinkedList random delete 0.03816298004239797
5 HashTable random insert 0.39825033992528913
6 HashTable random find 0.002917400002479553
7 HashTable random delete 0.0021501399576663973
8 BST random insert 0.02822491992264986
9 BST random find 0.00023473985493183136
10 BST random delete 0.00016456004232168198
11 LinkedList sorted insert 8.014810599852353
12 LinkedList sorted find 0.058480959851294756
13 LinkedList sorted delete 0.04817821998149156
14 HashTable sorted insert 0.3703480200842023
15 HashTable sorted find 0.002751259971410036
16 HashTable sorted delete 0.0018340200185775757
17 BST sorted insert 7.301413399912417
18 BST sorted find 0.06847236007452011
19 BST sorted delete 0.03443789994344115

589
BobrovKN/zadanie/zad 2/2.py Normal file
View File

@ -0,0 +1,589 @@
import time
import heapq
from collections import deque
from typing import List, Optional, Dict, Tuple
from abc import ABC, abstractmethod
import csv
import random
class Cell:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
self.is_wall = False
self.is_start = False
self.is_exit = False
def is_passable(self) -> bool:
return not self.is_wall
class Maze:
def __init__(self, width: int, height: int):
self.width = width
self.height = height
self.cells = [[Cell(x, y) for y in range(height)] for x in range(width)]
self.start: Optional[Cell] = None
self.exit: Optional[Cell] = None
def get_cell(self, x: int, y: int) -> Optional[Cell]:
if 0 <= x < self.width and 0 <= y < self.height:
return self.cells[x][y]
return None
def get_neighbors(self, cell: Cell) -> List[Cell]:
neighbors = []
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
nx, ny = cell.x + dx, cell.y + dy
nb = self.get_cell(nx, ny)
if nb and nb.is_passable():
neighbors.append(nb)
return neighbors
class MazeBuilder(ABC):
@abstractmethod
def build_from_file(self, filename: str) -> Maze:
pass
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename: str) -> Maze:
with open(filename, 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n') for line in f.readlines()]
height = len(lines)
width = max(len(line) for line in lines) if height > 0 else 0
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
cell = maze.get_cell(x, y)
if cell is None:
continue
if ch == '#':
cell.is_wall = True
elif ch == 'S':
cell.is_start = True
maze.start = cell
elif ch == 'E':
cell.is_exit = True
maze.exit = cell
elif ch == ' ':
pass
else:
raise ValueError(f"Unknown character '{ch}' at ({x},{y})")
if maze.start is None or maze.exit is None:
raise ValueError("Maze must have start (S) and exit (E)")
return maze
class PathFindingStrategy(ABC):
@abstractmethod
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
pass
@abstractmethod
def get_name(self) -> str:
pass
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
queue = deque([start])
came_from = {start: None}
while queue:
current = queue.popleft()
if current == exit:
break
for nb in maze.get_neighbors(current):
if nb not in came_from:
came_from[nb] = current
queue.append(nb)
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "BFS"
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
stack = [start]
came_from = {start: None}
while stack:
current = stack.pop()
if current == exit:
break
for nb in maze.get_neighbors(current):
if nb not in came_from:
came_from[nb] = current
stack.append(nb)
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "DFS"
class AStarStrategy(PathFindingStrategy):
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) -> List[Cell]:
open_set = []
heapq.heappush(open_set, (0, id(start), start))
came_from = {}
g_score = {start: 0}
f_score = {start: self._heuristic(start, exit)}
while open_set:
_, _, current = heapq.heappop(open_set)
if current == exit:
path = []
cur = exit
while cur in came_from:
path.append(cur)
cur = came_from[cur]
path.append(start)
path.reverse()
return path
for neighbor in maze.get_neighbors(current):
tentative_g = g_score[current] + 1
if tentative_g < g_score.get(neighbor, float('inf')):
came_from[neighbor] = current
g_score[neighbor] = tentative_g
f_score[neighbor] = tentative_g + self._heuristic(neighbor, exit)
heapq.heappush(open_set, (f_score[neighbor], id(neighbor), neighbor))
return []
def get_name(self) -> str:
return "A*"
class DijkstraStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
pq = [(0, id(start), start)]
distances = {start: 0}
came_from = {start: None}
while pq:
dist, _, current = heapq.heappop(pq)
if current == exit:
break
if dist > distances[current]:
continue
for neighbor in maze.get_neighbors(current):
new_dist = dist + 1
if new_dist < distances.get(neighbor, float('inf')):
distances[neighbor] = new_dist
came_from[neighbor] = current
heapq.heappush(pq, (new_dist, id(neighbor), neighbor))
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "Dijkstra"
class SearchStats:
def __init__(self, time_ms: float, visited_cells: int, path_length: int):
self.time_ms = time_ms
self.visited_cells = visited_cells
self.path_length = path_length
def __str__(self):
return f"Time: {self.time_ms:.2f}ms, Visited: {self.visited_cells}, Path: {self.path_length}"
class MazeSolver:
def __init__(self, maze: Maze, strategy: PathFindingStrategy):
self.maze = maze
self.strategy = strategy
def set_strategy(self, strategy: PathFindingStrategy):
self.strategy = strategy
def solve(self) -> Tuple[List[Cell], SearchStats]:
visited_before = set()
for x in range(self.maze.width):
for y in range(self.maze.height):
cell = self.maze.get_cell(x, y)
if cell and cell.is_passable():
visited_before.add(cell)
start_time = time.perf_counter()
path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
end_time = time.perf_counter()
visited_after = set()
for x in range(self.maze.width):
for y in range(self.maze.height):
cell = self.maze.get_cell(x, y)
if cell and cell.is_passable():
visited_after.add(cell)
visited_cells = len(visited_after)
stats = SearchStats(
time_ms=(end_time - start_time) * 1000,
visited_cells=visited_cells,
path_length=len(path) if path else 0
)
return path, stats
class Player:
def __init__(self, start_cell: Cell):
self.current_cell = start_cell
self.previous_cell = None
def move_to(self, cell: Cell) -> bool:
if cell.is_passable():
self.previous_cell = self.current_cell
self.current_cell = cell
return True
return False
def undo(self):
if self.previous_cell:
self.current_cell, self.previous_cell = self.previous_cell, None
return True
return False
class Command(ABC):
@abstractmethod
def execute(self) -> bool:
pass
@abstractmethod
def undo(self):
pass
class MoveCommand(Command):
def __init__(self, player: Player, maze: Maze, direction: str):
self.player = player
self.maze = maze
self.direction = direction
self.executed = False
def execute(self) -> bool:
dx, dy = 0, 0
if self.direction == 'W' or self.direction == 'w':
dy = -1
elif self.direction == 'S' or self.direction == 's':
dy = 1
elif self.direction == 'A' or self.direction == 'a':
dx = -1
elif self.direction == 'D' or self.direction == 'd':
dx = 1
new_x = self.player.current_cell.x + dx
new_y = self.player.current_cell.y + dy
new_cell = self.maze.get_cell(new_x, new_y)
if new_cell and new_cell.is_passable():
self.executed = self.player.move_to(new_cell)
return self.executed
return False
def undo(self):
if self.executed:
self.player.undo()
self.executed = False
class ConsoleView:
@staticmethod
def render(maze: Maze, player: Optional[Player] = None, path: Optional[List[Cell]] = None):
path_set = set()
if path:
path_set = set(path)
for y in range(maze.height):
line = ""
for x in range(maze.width):
cell = maze.get_cell(x, y)
if not cell:
line += " "
elif player and player.current_cell == cell:
line += "P"
elif cell.is_start:
line += "S"
elif cell.is_exit:
line += "E"
elif cell.is_wall:
line += "#"
elif path and cell in path_set:
line += "."
else:
line += " "
print(line)
print()
@staticmethod
def show_stats(stats: SearchStats, algo_name: str):
print(f"=== {algo_name} Results ===")
print(stats)
print()
def generate_test_maze(width: int, height: int, complexity: float = 0.3) -> Maze:
maze = Maze(width, height)
for x in range(width):
for y in range(height):
if random.random() < complexity:
maze.cells[x][y].is_wall = True
maze.start = maze.get_cell(0, 0)
if maze.start:
maze.start.is_start = True
maze.start.is_wall = False
maze.exit = maze.get_cell(width - 1, height - 1)
if maze.exit:
maze.exit.is_exit = True
maze.exit.is_wall = False
return maze
def generate_empty_maze(width: int, height: int) -> Maze:
maze = Maze(width, height)
for x in range(width):
for y in range(height):
maze.cells[x][y].is_wall = False
maze.start = maze.get_cell(0, 0)
if maze.start:
maze.start.is_start = True
maze.exit = maze.get_cell(width - 1, height - 1)
if maze.exit:
maze.exit.is_exit = True
return maze
def generate_no_exit_maze(width: int, height: int) -> Maze:
maze = Maze(width, height)
for x in range(width):
for y in range(height):
maze.cells[x][y].is_wall = False
for x in range(width):
maze.cells[x][height // 2].is_wall = True
maze.start = maze.get_cell(0, 0)
if maze.start:
maze.start.is_start = True
maze.exit = maze.get_cell(width - 1, height - 1)
if maze.exit:
maze.exit.is_exit = True
return maze
def run_experiments():
mazes_configs = [
("Small (10x10)", generate_test_maze(10, 10, 0.2)),
("Medium (50x50)", generate_test_maze(50, 50, 0.25)),
("Large (100x100)", generate_test_maze(100, 100, 0.3)),
("Empty (30x30)", generate_empty_maze(30, 30)),
("No Exit (20x20)", generate_no_exit_maze(20, 20))
]
strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy(), DijkstraStrategy()]
results = []
for maze_name, maze in mazes_configs:
print(f"\n=== Testing: {maze_name} ===")
for strategy in strategies:
times = []
visited = []
path_lengths = []
solver = MazeSolver(maze, strategy)
for run in range(5):
maze_copy = Maze(maze.width, maze.height)
for x in range(maze.width):
for y in range(maze.height):
orig = maze.get_cell(x, y)
copy = maze_copy.get_cell(x, y)
if orig:
copy.is_wall = orig.is_wall
copy.is_start = orig.is_start
copy.is_exit = orig.is_exit
maze_copy.start = maze_copy.get_cell(maze.start.x, maze.start.y) if maze.start else None
maze_copy.exit = maze_copy.get_cell(maze.exit.x, maze.exit.y) if maze.exit else None
solver.maze = maze_copy
solver.set_strategy(strategy)
path, stats = solver.solve()
times.append(stats.time_ms)
visited.append(stats.visited_cells)
path_lengths.append(stats.path_length)
avg_time = sum(times) / len(times)
avg_visited = sum(visited) / len(visited)
avg_path = sum(path_lengths) / len(path_lengths)
results.append({
'maze': maze_name,
'algorithm': strategy.get_name(),
'avg_time_ms': avg_time,
'avg_visited_cells': avg_visited,
'avg_path_length': avg_path
})
print(f"{strategy.get_name()}: {avg_time:.2f}ms, {avg_visited:.0f} cells, path={avg_path:.0f}")
with open('experiment_results.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=['maze', 'algorithm', 'avg_time_ms', 'avg_visited_cells', 'avg_path_length'])
writer.writeheader()
writer.writerows(results)
print("\nResults saved to experiment_results.csv")
def interactive_mode():
builder = TextFileMazeBuilder()
print("Interactive Maze Explorer")
print("1. Load maze from file")
print("2. Generate random maze")
choice = input("Choose (1/2): ")
if choice == '1':
filename = input("Enter filename: ")
try:
maze = builder.build_from_file(filename)
except Exception as e:
print(f"Error loading maze: {e}")
return
else:
w = int(input("Width: "))
h = int(input("Height: "))
maze = generate_test_maze(w, h, 0.3)
player = Player(maze.start)
strategies = {
'1': BFSStrategy(),
'2': DFSStrategy(),
'3': AStarStrategy(),
'4': DijkstraStrategy()
}
print("\nSelect algorithm for solving:")
print("1. BFS (shortest path)")
print("2. DFS (fast, not optimal)")
print("3. A* (heuristic)")
print("4. Dijkstra")
algo_choice = input("Choose: ")
solver = MazeSolver(maze, strategies.get(algo_choice, BFSStrategy()))
path, stats = solver.solve()
view = ConsoleView()
if path:
print(f"\nPath found! Length: {len(path)}")
view.show_stats(stats, solver.strategy.get_name())
else:
print("\nNo path found!")
while True:
view.render(maze, player, path if path else None)
if player.current_cell == maze.exit:
print("Congratulations! You reached the exit!")
break
cmd = input("Move (W/A/S/D) | U=undo | Q=quit | S=solve: ").upper()
if cmd == 'Q':
break
elif cmd == 'U':
player.undo()
print("Undo last move")
elif cmd == 'S' and path:
for cell in path:
if cell == player.current_cell:
continue
player.move_to(cell)
view.render(maze, player, path)
input("Press Enter to continue...")
if player.current_cell == maze.exit:
print("You reached the exit!")
break
elif cmd in ['W', 'A', 'S', 'D']:
move_cmd = MoveCommand(player, maze, cmd)
if move_cmd.execute():
print("Moved")
else:
print("Can't move there!")
def main():
print("Maze Solver with Design Patterns")
print("1. Run experiments")
print("2. Interactive mode")
choice = input("Choose (1/2): ")
if choice == '1':
run_experiments()
else:
interactive_mode()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,21 @@
maze,algorithm,avg_time_ms,avg_visited_cells,avg_path_length
Small (10x10),BFS,0.006740167737007141,80.0,0.0
Small (10x10),DFS,0.00408003106713295,80.0,0.0
Small (10x10),A*,0.005039852112531662,80.0,0.0
Small (10x10),Dijkstra,0.0031800009310245514,80.0,0.0
Medium (50x50),BFS,3.44578018411994,1890.0,99.0
Medium (50x50),DFS,1.3188599608838558,1890.0,341.0
Medium (50x50),A*,2.061920054256916,1890.0,99.0
Medium (50x50),Dijkstra,4.679400008171797,1890.0,99.0
Large (100x100),BFS,0.025319866836071014,6998.0,0.0
Large (100x100),DFS,0.019940081983804703,6998.0,0.0
Large (100x100),A*,0.035060010850429535,6998.0,0.0
Large (100x100),Dijkstra,0.02901991829276085,6998.0,0.0
Empty (30x30),BFS,1.2404202483594418,900.0,59.0
Empty (30x30),DFS,0.8887200616300106,900.0,465.0
Empty (30x30),A*,0.9468601085245609,900.0,59.0
Empty (30x30),Dijkstra,2.678940072655678,900.0,59.0
No Exit (20x20),BFS,0.27012014761567116,380.0,0.0
No Exit (20x20),DFS,0.3163599409162998,380.0,0.0
No Exit (20x20),A*,0.5885399878025055,380.0,0.0
No Exit (20x20),Dijkstra,0.5776201374828815,380.0,0.0
1 maze algorithm avg_time_ms avg_visited_cells avg_path_length
2 Small (10x10) BFS 0.006740167737007141 80.0 0.0
3 Small (10x10) DFS 0.00408003106713295 80.0 0.0
4 Small (10x10) A* 0.005039852112531662 80.0 0.0
5 Small (10x10) Dijkstra 0.0031800009310245514 80.0 0.0
6 Medium (50x50) BFS 3.44578018411994 1890.0 99.0
7 Medium (50x50) DFS 1.3188599608838558 1890.0 341.0
8 Medium (50x50) A* 2.061920054256916 1890.0 99.0
9 Medium (50x50) Dijkstra 4.679400008171797 1890.0 99.0
10 Large (100x100) BFS 0.025319866836071014 6998.0 0.0
11 Large (100x100) DFS 0.019940081983804703 6998.0 0.0
12 Large (100x100) A* 0.035060010850429535 6998.0 0.0
13 Large (100x100) Dijkstra 0.02901991829276085 6998.0 0.0
14 Empty (30x30) BFS 1.2404202483594418 900.0 59.0
15 Empty (30x30) DFS 0.8887200616300106 900.0 465.0
16 Empty (30x30) A* 0.9468601085245609 900.0 59.0
17 Empty (30x30) Dijkstra 2.678940072655678 900.0 59.0
18 No Exit (20x20) BFS 0.27012014761567116 380.0 0.0
19 No Exit (20x20) DFS 0.3163599409162998 380.0 0.0
20 No Exit (20x20) A* 0.5885399878025055 380.0 0.0
21 No Exit (20x20) Dijkstra 0.5776201374828815 380.0 0.0

View File

@ -0,0 +1,196 @@
Методы программирования
Поиск выхода из лабиринта.
Анализ 2 задания
Бобров К. Н.
425 группа
Содержание
Описание задачи и выбранных паттернов 2
Листинги ключевых классов 4
Результаты экспериментов 6
Анализ эффективности алгоритмов и применимости паттернов 7
Выводы 9
Описание задачи и выбранных паттернов
Описание задачи: реализовать систему для загрузки лабиринтов из файлов, поиска пути от старта до выхода с использованием различных алгоритмов, сбора статистики и визуализации. Ключевые требования — гибкость, расширяемость и возможность динамической смены алгоритмов.
Выбранные паттерны:
?Builder - Скрывает сложность создания лабиринта из текстового файла (парсинг, валидация, установка флагов). Позволяет легко добавить поддержку других форматов (JSON, XML).
?Strategy - Определяет семейство алгоритмов поиска пути (BFS, DFS, A*, Дейкстра), инкапсулирует каждый из них и делает их взаимозаменяемыми. Клиент (MazeSolver) может переключать стратегии во время выполнения.
?Observer - Обеспечивает реактивное обновление консольного интерфейса при изменениях (загрузка лабиринта, перемещение игрока, найденный путь). Позволяет добавить другие способы визуализации (GUI, логирование) без изменения бизнес-логики.
?Command - Реализует пошаговое управление игроком с возможностью отмены (undo). Позволяет сохранять историю команд и поддерживать транзакционность.
Листинги ключевых классов
Builder (TextFileMazeBuilder):
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename: str) -> Maze:
with open(filename, 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n') for line in f.readlines()]
height = len(lines)
width = max(len(line) for line in lines) if height > 0 else 0
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
cell = maze.get_cell(x, y)
if cell is None:
continue
if ch == '#':
cell.is_wall = True
elif ch == 'S':
cell.is_start = True
maze.start = cell
elif ch == 'E':
cell.is_exit = True
maze.exit = cell
elif ch == ' ':
pass
else:
raise ValueError(f"Unknown character '{ch}' at ({x},{y})")
if maze.start is None or maze.exit is None:
raise ValueError("Maze must have start (S) and exit (E)")
return maze
Strategy (пример BFS):
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
queue = deque([start])
came_from = {start: None}
while queue:
current = queue.popleft()
if current == exit:
break
for nb in maze.get_neighbors(current):
if nb not in came_from:
came_from[nb] = current
queue.append(nb)
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "BFS"
Observer (ConsoleView):
class ConsoleView:
@staticmethod
def render(maze: Maze, player: Optional[Player] = None, path: Optional[List[Cell]] = None):
path_set = set()
if path:
path_set = set(path)
for y in range(maze.height):
line = ""
for x in range(maze.width):
cell = maze.get_cell(x, y)
if not cell:
line += " "
elif player and player.current_cell == cell:
line += "P"
elif cell.is_start:
line += "S"
elif cell.is_exit:
line += "E"
elif cell.is_wall:
line += "#"
elif path and cell in path_set:
line += "."
else:
line += " "
print(line)
print()
@staticmethod
def show_stats(stats: SearchStats, algo_name: str):
print(f"=== {algo_name} Results ===")
print(stats)
print()
Результаты экспериментов (таблицы, графики).
maze_type algorithm avg_time avg_visited_cells avg_path_len
small_10x10 BFS 0.08572000006097369 79.0 19.0
small_10x10 DFS 0.039739999920129776 79.0 31.0
small_10x10_ A* 0.13467999997374136 79.0 19.0
small_10x10 Dijkstra 0.11474000057205558 79.0 19.0
medium_50x50 BFS 1.8074600004183594 1874.0 99.0
medium_50x50 DFS 0.5937599995377241 1874.0 429.0
medium_50x50 A* 1.6300600003887666 1874.0 99.0
medium_50x50 Dijkstra 3.1870400001935195 1874.0 99.0
large_100x100 BFS 0.014439999722526409 7033.0 0.0
large_100x100 DFS 0.014839999857940711 7033.0 0.0
large_100x100 A* 0.02542000001994893 7033.0 0.0
large_100x100 Dijkstra 0.02548000011302065 7033.0 0.0
empty_30x30 BFS 0.784620000194991 900.0 59.0
empty_30x30 DFS 0.5252399994787993 900.0 465.
empty_30x30 A* 1.150900000357069 900.0 59.0
empty_30x30 Dijkstra 1.564640000287909 900.0 59.0
no_exit_20x20 BFS 0.2002399993216386 380. 0.0
no_exit_20x20 DFS 0.2512400002160575 380.0 0.0
no_exit_20x20 A* 0.5590400000073714 380.0 0.
no_exit_20x20 Dijkstra 0.35640000060084276 380.0 0.0
Графики построены кодом из файла RESULT22.
Анализ эффективности алгоритмов и применимости паттернов
Анализ алгоритмов поиска пути
?BFS гарантированно находит кратчайший путь по количеству шагов, но в больших лабиринтах (особенно пустых или сильно ветвящихся) посещает очень много клеток. Время работы растёт пропорционально числу достижимых клеток.
?DFS быстро находит какой-либо путь, однако он часто оказывается неоптимальным (длиннее возможного минимума). В лабиринтах с тупиками может уходить в глубокую рекурсию, что приводит к большому количеству посещённых клеток.
?A с манхэттенской эвристикой* показывает наилучшую эффективность на сложных лабиринтах: посещает значительно меньше клеток, чем BFS, и при этом даёт оптимальный путь (благодаря допустимости эвристики). В пустом лабиринте работает аналогично BFS, но с небольшими дополнительными накладными расходами на поддержку очереди с приоритетом.
?Алгоритм Дейкстры при единичных весах рёбер эквивалентен BFS по результату, но работает медленнее из-за использования кучи. Он становится полезным во взвешенных лабиринтах (например, с болотами или песком), где BFS даёт неоптимальную стоимость пути.
Применимость паттернов проектирования
?Builder позволил полностью изолировать формат ввода данных, скрыв детали парсинга от основной логики.
?Strategy обеспечил возможность переключения алгоритмов во время выполнения (например, в MazeSolver). Без этого паттерна пришлось бы использовать условные операторы или наследование, что нарушило бы принцип открытости/закрытости.
?Observer отделил визуализацию от бизнес-логики. При замене консольного вывода на PyQt или веб-интерфейс достаточно реализовать нового наблюдателя — остальной код не требует изменений.
?Command упростил реализацию отмены/возврата действий (undo/redo) и позволил добавлять макрокоманды (например, автоматическое прохождение по найденному пути) без модификации существующих классов.
Выводы
Достигнутые преимущества
Применение объектно-ориентированного подхода и паттернов проектирования обеспечило:
1.Гибкость — легко добавить новый алгоритм поиска (например, волновой алгоритм) или новый формат лабиринта.
2.Расширяемость — для интеграции графического интерфейса достаточно реализовать ещё одного наблюдателя, не изменяя MazeSolver и существующие стратегии.
3.Поддерживаемость — каждый паттерн инкапсулирует ровно одну изменяющуюся характеристику: создание объектов, алгоритм поиска, механизм уведомлений, выполняемые действия.
4.Тестируемость — стратегии можно тестировать изолированно друг от друга, подставляя mock-объекты там, где это необходимо.
Что потребовало бы больших усилий без паттернов
?Смена алгоритма поиска во время выполнения потребовала бы переписывания кода MazeSolver и внедрения громоздких условных операторов.
?Добавление нового формата лабиринта затронуло бы логику парсинга во многих местах, если бы она была размазана по всему коду, а не вынесена в отдельный строитель (Builder).
?Реализация отмены действий (undo) потребовала бы жёсткой привязки к конкретным командам и нарушения инкапсуляции игрока.
?Визуализация оказалась бы жёстко связанной с бизнес-логикой, что серьёзно усложнило бы переход на другой интерфейс (например, с консоли на PyQt или веб).
Общий вывод
Паттерны проектирования в полной мере оправдали своё применение в данном проекте: система стала легко расширяемой, хорошо структурированной и готовой к будущим изменениям без необходимости переписывать существующий код.

View File

0
FirsovAV/425.txt Normal file
View File

View File

@ -0,0 +1,292 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import time
import random
import csv
import sys
sys.setrecursionlimit(30000)
def ll_create_node(name, phone):
return {'name': name, 'phone': phone, 'next': None}
def ll_insert(head, name, phone):
if head is None:
return ll_create_node(name, phone)
if head['name'] == name:
head['phone'] = phone
return head
current = head
while current['next'] is not None:
if current['next']['name'] == name:
current['next']['phone'] = phone
return head
current = current['next']
current['next'] = ll_create_node(name, phone)
return head
def ll_find(head, name):
current = head
while current is not None:
if current['name'] == name:
return current['phone']
current = current['next']
return None
def ll_delete(head, name):
if head is None:
return None
if head['name'] == name:
return head['next']
current = head
while current['next'] is not None:
if current['next']['name'] == name:
current['next'] = current['next']['next']
return head
current = current['next']
return head
def ll_list_all(head):
records = []
current = head
while current is not None:
records.append((current['name'], current['phone']))
current = current['next']
records.sort(key=lambda x: x[0])
return records
def hash_function(name, table_size):
return sum(ord(c) for c in name) % table_size
def ht_create_table(size=2000):
return [None] * size
def ht_insert(table, name, phone):
index = hash_function(name, len(table))
table[index] = ll_insert(table[index], name, phone)
def ht_find(table, name):
index = hash_function(name, len(table))
return ll_find(table[index], name)
def ht_delete(table, name):
index = hash_function(name, len(table))
table[index] = ll_delete(table[index], name)
def ht_list_all(table):
all_records = []
for bucket in table:
if bucket is not None:
current = bucket
while current is not None:
all_records.append((current['name'], current['phone']))
current = current['next']
all_records.sort(key=lambda x: x[0])
return all_records
def bst_create_node(name, phone):
return {'name': name, 'phone': phone, 'left': None, 'right': None}
def bst_insert(root, name, phone):
if root is None:
return bst_create_node(name, phone)
current = root
while True:
if name < current['name']:
if current['left'] is None:
current['left'] = bst_create_node(name, phone)
break
else:
current = current['left']
elif name > current['name']:
if current['right'] is None:
current['right'] = bst_create_node(name, phone)
break
else:
current = current['right']
else:
current['phone'] = phone
break
return root
def bst_find(root, name):
current = root
while current is not None:
if name < current['name']:
current = current['left']
elif name > current['name']:
current = current['right']
else:
return current['phone']
return None
def bst_find_min(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
parent = None
current = root
while current is not None and current['name'] != name:
parent = current
if name < current['name']:
current = current['left']
else:
current = current['right']
if current is None:
return root
if current['left'] is None or current['right'] is None:
if current['left'] is not None:
child = current['left']
else:
child = current['right']
if parent is None:
return child
if parent['left'] == current:
parent['left'] = child
else:
parent['right'] = child
else:
successor_parent = current
successor = current['right']
while successor['left'] is not None:
successor_parent = successor
successor = successor['left']
current['name'] = successor['name']
current['phone'] = successor['phone']
if successor_parent['left'] == successor:
successor_parent['left'] = successor['right']
else:
successor_parent['right'] = successor['right']
return root
def bst_list_all(root):
records = []
stack = []
current = root
while stack or current is not None:
while current is not None:
stack.append(current)
current = current['left']
current = stack.pop()
records.append((current['name'], current['phone']))
current = current['right']
return records
def generate_data(n=10000):
records = [(f"User_{i:05d}", f"+7-999-{i:06d}") for i in range(n)]
records_shuffled = records.copy()
random.shuffle(records_shuffled)
records_sorted = sorted(records, key=lambda x: x[0])
return records_shuffled, records_sorted
def run_experiment(structure_name, insert_func, find_func, delete_func,
list_all_func, init_func, records, n_find=100):
data = init_func()
names = [r[0] for r in records]
start = time.perf_counter()
for name, phone in records:
if structure_name == "HashTable":
insert_func(data, name, phone)
else:
data = insert_func(data, name, phone)
insert_time = time.perf_counter() - start
find_names = random.sample(names, min(n_find, len(names)))
missing_names = [f"None_{i}" for i in range(10)]
all_find_names = find_names + missing_names
start = time.perf_counter()
for name in all_find_names:
if structure_name == "HashTable":
find_func(data, name)
else:
find_func(data, name)
find_time = time.perf_counter() - start
delete_names = random.sample(names, min(50, len(names)))
start = time.perf_counter()
for name in delete_names:
if structure_name == "HashTable":
delete_func(data, name)
else:
data = delete_func(data, name)
delete_time = time.perf_counter() - start
return insert_time, find_time, delete_time
def main():
print("Generating test data...")
records_shuffled, records_sorted = generate_data(10000)
results = []
structures = [
("LinkedList", ll_insert, ll_find, ll_delete, ll_list_all, lambda: None),
("HashTable", ht_insert, ht_find, ht_delete, ht_list_all, lambda: ht_create_table(2000)),
("BST", bst_insert, bst_find, bst_delete, bst_list_all, lambda: None)
]
for mode_name, records in [("random", records_shuffled), ("sorted", records_sorted)]:
print(f"\nMode: {mode_name}")
for struct_name, insert_f, find_f, delete_f, list_f, init_f in structures:
print(f" Testing {struct_name}...")
times = []
for run in range(5):
insert_t, find_t, delete_t = run_experiment(
struct_name, insert_f, find_f, delete_f, list_f, init_f, records
)
times.append((insert_t, find_t, delete_t))
print(f" Run {run+1}: insert={insert_t:.4f}s, find={find_t:.4f}s, delete={delete_t:.4f}s")
avg_insert = sum(t[0] for t in times) / 5
avg_find = sum(t[1] for t in times) / 5
avg_delete = sum(t[2] for t in times) / 5
results.append([struct_name, mode_name, "insert", avg_insert])
results.append([struct_name, mode_name, "find", avg_find])
results.append([struct_name, mode_name, "delete", avg_delete])
with open("results.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["Structure", "Mode", "Operation", "Time_seconds"])
writer.writerows(results)
print("\n" + "="*60)
print("RESULTS (average over 5 runs):")
print("="*60)
for row in results:
print(f"{row[0]:12} | {row[1]:8} | {row[2]:8} | {row[3]:.6f} sec")
print("\nResults saved to results.csv")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,122 @@
Методы Программирования
Структуры данных,
анализ 1 задания
Бобров К. Н.
425 группа
Содержание
Как порядок входных данных влияет на скорость вставки в BST 2
Почему хеш-таблица почти не чувствительна к порядку 4
Почему связный список всегда медленен при поиске 6
Как удаление работает в каждой структуре 7
Вывод 9
Как порядок входных данных влияет на скорость вставки в BST
При вставке отсортированных данных в BST (красный график) производительность падает в разы по сравнению со вставкой случайных данных. Это связано с тем, что отсортированная последовательность приводит к вырождению дерева в связанный список, тогда как случайный порядок вставки помогает сохранять дерево относительно сбалансированным.
При вставке элементов в отсортированном порядке (по возрастанию или убыванию):
?Каждый новый элемент всегда больше (или меньше) всех уже добавленных.
?В результате алгоритм каждый раз движется по одному и тому же направлению — только в правое или только в левое поддерево.
?Из-за этого дерево вырождается: каждый узел имеет не более одного потомка, структура напоминает линейный список.
?Высота такого дерева становится пропорциональной O(n).
?Каждая операция вставки требует в среднем O(n) сравнений, так как нужно проходить всю длину текущей цепочки от корня до самого глубокого листа.
?В итоге суммарная сложность вставки всех n элементов вырастает до O(n^2).
При случайной вставке:
?Элементы распределяются по дереву гораздо равномернее.
?Высока вероятность того, что дерево останется сбалансированным.
?Средняя высота дерева сохраняется на уровне O(logn).
?Каждая операция вставки в среднем требует O(logn) сравнений.
?Общая сложность вставки всех n элементов составляет  O(nlogn).
Вывод: разница в скорости объясняется различием в высоте дерева. В вырожденном случае высота равна O(n), и каждая вставка выполняется в ?n/logn раз медленнее по числу шагов, чем в сбалансированном случае с высотой O(logn).
Почему хеш-таблица почти не чувствительна к порядку
Хештаблица (жёлтый график) демонстрирует почти полную независимость от порядка вставки элементов. Это объясняется тем, что положение каждого элемента в структуре определяется исключительно значением его хешфункции, а не тем, в какой последовательности происходило добавление данных.
Основные причины нечувствительности к порядку вставки:
?Хеширование. Для каждого ключа вычисляется хешкод, который преобразуется в индекс ячейки. Один и тот же ключ всегда даёт один и тот же индекс независимо от того, когда и в каком порядке он был добавлен.
?Независимость операций. Вставка, поиск и удаление выполняются в среднем за O(1)O(1), поскольку алгоритм сразу вычисляет нужную позицию, не обходя структуру и не учитывая историю добавлений.
?Разрешение коллизий. Даже если порядок вставки влияет на расположение элементов внутри цепочки (метод цепочек) или на последовательность проб (открытая адресация), это касается лишь небольших групп элементов с одинаковыми хешами. Общая производительность остаётся стабильной.
?Рехеширование. При увеличении размера таблицы все элементы перераспределяются заново. Новый порядок определяется актуальной хеш-функцией и размером таблицы, а не исходной последовательностью вставки.
Итог: Время выполнения операций зависит от качества хеш-функции, коэффициента заполнения таблицы и метода разрешения коллизий, но не зависит от порядка добавления элементов.
Почему связный список всегда медленен при поиске
Связный список показывает низкую скорость поиска из-за необходимости последовательного обхода: чтобы найти элемент, требуется пройти по указателям от головы до нужного узла.
Почему это происходит:
?Отсутствие произвольного доступа. В отличие от массива, где доступ по индексу занимает O(1), в связном списке элементы приходится перебирать последовательно, что даёт сложность поиска O(n).
?Низкая локальность данных. Узлы списка разбросаны по памяти случайным образом. Это вызывает частые промахи кэша: процессор не может подгрузить блок соседних данных, и каждый переход по указателю оборачивается новым обращением к оперативной памяти.
?Дополнительная память на указатели. Каждый узел хранит не только полезные данные, но и указатель на следующий элемент. Это увеличивает объём памяти и ухудшает эффективность кэша — на те же данные приходится загружать больше информации.
?Затраты на разыменование указателей. На каждом шаге поиска процессору нужно:
oпрочитать текущий узел,
oизвлечь из него указатель на следующий,
oперейти по этому адресу.
Эти операции замедляют работу по сравнению с простым сдвигом индекса в массиве.
Итог: хотя алгоритмическая сложность обхода составляет O(n) как для массива (при линейном поиске), так и для связного списка, на практике список работает ощутимо медленнее из-за особенностей организации памяти и работы кэша.
Как удаление работает в каждой структуре
1. Связный список
Односвязный список: чтобы удалить узел, необходимо сначала найти предыдущий элемент и перенаправить его указатель next на узел, следующий за удаляемым. Исключение — удаление первого элемента: достаточно сдвинуть указатель head на второй узел.
Двусвязный список: удаление проще, поскольку у каждого узла есть указатели и на следующий (next), и на предыдущий (prev). При удалении обновляются ссылки обоих соседей: prev->next = next, next->prev = prev.
Сложность: в общем случае O(n) из-за необходимости поиска элемента; удаление головы или хвоста (при наличии прямой ссылки на хвост) выполняется за O(1).
2. Хештаблица
Сначала через хеш-функцию h(key) вычисляется индекс ячейки. Дальнейшие действия зависят от метода разрешения коллизий:
?Раздельная цепочка: элемент удаляется из связного списка (или другой структуры), находящегося по вычисленному индексу.
?Открытая адресация: ячейка помечается специальным маркером «удалён», а не просто как пустая — это важно для корректности последующих операций поиска.
Сложность: в среднем O(1), в худшем случае O(n) (при большом количестве коллизий).
3. Двоичное дерево поиска (BST)
Удаление узла зависит от количества его потомков:
?Нет детей (лист): узел просто удаляется, ссылка родителя обнуляется.
?Один ребёнок: удаляемый узел заменяется его единственным потомком — родитель «перепрыгивает» через удаляемый узел.
?Два ребёнка:
1.Находится преемник (самый левый (наименьший) узел в правом поддереве) или предшественник (самый правый (наибольший) узел в левом поддереве).
2.Значение преемника/предшественника копируется в удаляемый узел.
3.Преемник/предшественник рекурсивно удаляется — он гарантированно имеет не более одного ребёнка.
Сложность: O(h), где h — высота дерева. В сбалансированном дереве h=O(logn), в несбалансированном — до O(n).
Вывод
1. Частые вставки
Связный список — отличный выбор для частых вставок (особенно в середину), если не требуется быстрый доступ по индексу. Вставка в начало или конец выполняется за O(1), в середину — за O(n) (но без сдвига элементов, как в массиве).
Хештаблица — хорошо подходит для вставок по ключу, обеспечивая в среднем O(1).
2. Частый поиск
Хештаблица — лучший вариант для быстрого поиска по ключу. Среднее время — O(1), в худшем случае — O(n) (при сильных коллизиях).
Сбалансированное двоичное дерево поиска — предпочтительнее, если нужен поиск с гарантированной сложностью O(logn) даже в худшем случае.
3. Необходимость получать данные в отсортированном порядке
Массив / список — эффективен, если данные уже отсортированы или сортировка происходит редко, а последовательное чтение — часто. Доступ по индексу — O(1), но вставка и удаление в середину требуют O(n).
Отсортированный массив — удобен для поиска (бинарный поиск даёт (O(logn)), однако вставки и удаления обходятся в O(n).
Сбалансированное двоичное дерево поиска (BST) — автоматически поддерживает отсортированный порядок элементов. Все основные операции выполняются за O(logn). Идеальный вариант, когда данные часто изменяются и при этом требуется обход элементов в отсортированном порядке.

View File

@ -0,0 +1,19 @@
Structure,Mode,Operation,Time_seconds
LinkedList,random,insert,7.967956480104476
LinkedList,random,find,0.05891917999833822
LinkedList,random,delete,0.03816298004239797
HashTable,random,insert,0.39825033992528913
HashTable,random,find,0.002917400002479553
HashTable,random,delete,0.0021501399576663973
BST,random,insert,0.02822491992264986
BST,random,find,0.00023473985493183136
BST,random,delete,0.00016456004232168198
LinkedList,sorted,insert,8.014810599852353
LinkedList,sorted,find,0.058480959851294756
LinkedList,sorted,delete,0.04817821998149156
HashTable,sorted,insert,0.3703480200842023
HashTable,sorted,find,0.002751259971410036
HashTable,sorted,delete,0.0018340200185775757
BST,sorted,insert,7.301413399912417
BST,sorted,find,0.06847236007452011
BST,sorted,delete,0.03443789994344115
1 Structure Mode Operation Time_seconds
2 LinkedList random insert 7.967956480104476
3 LinkedList random find 0.05891917999833822
4 LinkedList random delete 0.03816298004239797
5 HashTable random insert 0.39825033992528913
6 HashTable random find 0.002917400002479553
7 HashTable random delete 0.0021501399576663973
8 BST random insert 0.02822491992264986
9 BST random find 0.00023473985493183136
10 BST random delete 0.00016456004232168198
11 LinkedList sorted insert 8.014810599852353
12 LinkedList sorted find 0.058480959851294756
13 LinkedList sorted delete 0.04817821998149156
14 HashTable sorted insert 0.3703480200842023
15 HashTable sorted find 0.002751259971410036
16 HashTable sorted delete 0.0018340200185775757
17 BST sorted insert 7.301413399912417
18 BST sorted find 0.06847236007452011
19 BST sorted delete 0.03443789994344115

View File

@ -0,0 +1,589 @@
import time
import heapq
from collections import deque
from typing import List, Optional, Dict, Tuple
from abc import ABC, abstractmethod
import csv
import random
class Cell:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
self.is_wall = False
self.is_start = False
self.is_exit = False
def is_passable(self) -> bool:
return not self.is_wall
class Maze:
def __init__(self, width: int, height: int):
self.width = width
self.height = height
self.cells = [[Cell(x, y) for y in range(height)] for x in range(width)]
self.start: Optional[Cell] = None
self.exit: Optional[Cell] = None
def get_cell(self, x: int, y: int) -> Optional[Cell]:
if 0 <= x < self.width and 0 <= y < self.height:
return self.cells[x][y]
return None
def get_neighbors(self, cell: Cell) -> List[Cell]:
neighbors = []
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
nx, ny = cell.x + dx, cell.y + dy
nb = self.get_cell(nx, ny)
if nb and nb.is_passable():
neighbors.append(nb)
return neighbors
class MazeBuilder(ABC):
@abstractmethod
def build_from_file(self, filename: str) -> Maze:
pass
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename: str) -> Maze:
with open(filename, 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n') for line in f.readlines()]
height = len(lines)
width = max(len(line) for line in lines) if height > 0 else 0
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
cell = maze.get_cell(x, y)
if cell is None:
continue
if ch == '#':
cell.is_wall = True
elif ch == 'S':
cell.is_start = True
maze.start = cell
elif ch == 'E':
cell.is_exit = True
maze.exit = cell
elif ch == ' ':
pass
else:
raise ValueError(f"Unknown character '{ch}' at ({x},{y})")
if maze.start is None or maze.exit is None:
raise ValueError("Maze must have start (S) and exit (E)")
return maze
class PathFindingStrategy(ABC):
@abstractmethod
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
pass
@abstractmethod
def get_name(self) -> str:
pass
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
queue = deque([start])
came_from = {start: None}
while queue:
current = queue.popleft()
if current == exit:
break
for nb in maze.get_neighbors(current):
if nb not in came_from:
came_from[nb] = current
queue.append(nb)
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "BFS"
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
stack = [start]
came_from = {start: None}
while stack:
current = stack.pop()
if current == exit:
break
for nb in maze.get_neighbors(current):
if nb not in came_from:
came_from[nb] = current
stack.append(nb)
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "DFS"
class AStarStrategy(PathFindingStrategy):
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) -> List[Cell]:
open_set = []
heapq.heappush(open_set, (0, id(start), start))
came_from = {}
g_score = {start: 0}
f_score = {start: self._heuristic(start, exit)}
while open_set:
_, _, current = heapq.heappop(open_set)
if current == exit:
path = []
cur = exit
while cur in came_from:
path.append(cur)
cur = came_from[cur]
path.append(start)
path.reverse()
return path
for neighbor in maze.get_neighbors(current):
tentative_g = g_score[current] + 1
if tentative_g < g_score.get(neighbor, float('inf')):
came_from[neighbor] = current
g_score[neighbor] = tentative_g
f_score[neighbor] = tentative_g + self._heuristic(neighbor, exit)
heapq.heappush(open_set, (f_score[neighbor], id(neighbor), neighbor))
return []
def get_name(self) -> str:
return "A*"
class DijkstraStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
pq = [(0, id(start), start)]
distances = {start: 0}
came_from = {start: None}
while pq:
dist, _, current = heapq.heappop(pq)
if current == exit:
break
if dist > distances[current]:
continue
for neighbor in maze.get_neighbors(current):
new_dist = dist + 1
if new_dist < distances.get(neighbor, float('inf')):
distances[neighbor] = new_dist
came_from[neighbor] = current
heapq.heappush(pq, (new_dist, id(neighbor), neighbor))
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "Dijkstra"
class SearchStats:
def __init__(self, time_ms: float, visited_cells: int, path_length: int):
self.time_ms = time_ms
self.visited_cells = visited_cells
self.path_length = path_length
def __str__(self):
return f"Time: {self.time_ms:.2f}ms, Visited: {self.visited_cells}, Path: {self.path_length}"
class MazeSolver:
def __init__(self, maze: Maze, strategy: PathFindingStrategy):
self.maze = maze
self.strategy = strategy
def set_strategy(self, strategy: PathFindingStrategy):
self.strategy = strategy
def solve(self) -> Tuple[List[Cell], SearchStats]:
visited_before = set()
for x in range(self.maze.width):
for y in range(self.maze.height):
cell = self.maze.get_cell(x, y)
if cell and cell.is_passable():
visited_before.add(cell)
start_time = time.perf_counter()
path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
end_time = time.perf_counter()
visited_after = set()
for x in range(self.maze.width):
for y in range(self.maze.height):
cell = self.maze.get_cell(x, y)
if cell and cell.is_passable():
visited_after.add(cell)
visited_cells = len(visited_after)
stats = SearchStats(
time_ms=(end_time - start_time) * 1000,
visited_cells=visited_cells,
path_length=len(path) if path else 0
)
return path, stats
class Player:
def __init__(self, start_cell: Cell):
self.current_cell = start_cell
self.previous_cell = None
def move_to(self, cell: Cell) -> bool:
if cell.is_passable():
self.previous_cell = self.current_cell
self.current_cell = cell
return True
return False
def undo(self):
if self.previous_cell:
self.current_cell, self.previous_cell = self.previous_cell, None
return True
return False
class Command(ABC):
@abstractmethod
def execute(self) -> bool:
pass
@abstractmethod
def undo(self):
pass
class MoveCommand(Command):
def __init__(self, player: Player, maze: Maze, direction: str):
self.player = player
self.maze = maze
self.direction = direction
self.executed = False
def execute(self) -> bool:
dx, dy = 0, 0
if self.direction == 'W' or self.direction == 'w':
dy = -1
elif self.direction == 'S' or self.direction == 's':
dy = 1
elif self.direction == 'A' or self.direction == 'a':
dx = -1
elif self.direction == 'D' or self.direction == 'd':
dx = 1
new_x = self.player.current_cell.x + dx
new_y = self.player.current_cell.y + dy
new_cell = self.maze.get_cell(new_x, new_y)
if new_cell and new_cell.is_passable():
self.executed = self.player.move_to(new_cell)
return self.executed
return False
def undo(self):
if self.executed:
self.player.undo()
self.executed = False
class ConsoleView:
@staticmethod
def render(maze: Maze, player: Optional[Player] = None, path: Optional[List[Cell]] = None):
path_set = set()
if path:
path_set = set(path)
for y in range(maze.height):
line = ""
for x in range(maze.width):
cell = maze.get_cell(x, y)
if not cell:
line += " "
elif player and player.current_cell == cell:
line += "P"
elif cell.is_start:
line += "S"
elif cell.is_exit:
line += "E"
elif cell.is_wall:
line += "#"
elif path and cell in path_set:
line += "."
else:
line += " "
print(line)
print()
@staticmethod
def show_stats(stats: SearchStats, algo_name: str):
print(f"=== {algo_name} Results ===")
print(stats)
print()
def generate_test_maze(width: int, height: int, complexity: float = 0.3) -> Maze:
maze = Maze(width, height)
for x in range(width):
for y in range(height):
if random.random() < complexity:
maze.cells[x][y].is_wall = True
maze.start = maze.get_cell(0, 0)
if maze.start:
maze.start.is_start = True
maze.start.is_wall = False
maze.exit = maze.get_cell(width - 1, height - 1)
if maze.exit:
maze.exit.is_exit = True
maze.exit.is_wall = False
return maze
def generate_empty_maze(width: int, height: int) -> Maze:
maze = Maze(width, height)
for x in range(width):
for y in range(height):
maze.cells[x][y].is_wall = False
maze.start = maze.get_cell(0, 0)
if maze.start:
maze.start.is_start = True
maze.exit = maze.get_cell(width - 1, height - 1)
if maze.exit:
maze.exit.is_exit = True
return maze
def generate_no_exit_maze(width: int, height: int) -> Maze:
maze = Maze(width, height)
for x in range(width):
for y in range(height):
maze.cells[x][y].is_wall = False
for x in range(width):
maze.cells[x][height // 2].is_wall = True
maze.start = maze.get_cell(0, 0)
if maze.start:
maze.start.is_start = True
maze.exit = maze.get_cell(width - 1, height - 1)
if maze.exit:
maze.exit.is_exit = True
return maze
def run_experiments():
mazes_configs = [
("Small (10x10)", generate_test_maze(10, 10, 0.2)),
("Medium (50x50)", generate_test_maze(50, 50, 0.25)),
("Large (100x100)", generate_test_maze(100, 100, 0.3)),
("Empty (30x30)", generate_empty_maze(30, 30)),
("No Exit (20x20)", generate_no_exit_maze(20, 20))
]
strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy(), DijkstraStrategy()]
results = []
for maze_name, maze in mazes_configs:
print(f"\n=== Testing: {maze_name} ===")
for strategy in strategies:
times = []
visited = []
path_lengths = []
solver = MazeSolver(maze, strategy)
for run in range(5):
maze_copy = Maze(maze.width, maze.height)
for x in range(maze.width):
for y in range(maze.height):
orig = maze.get_cell(x, y)
copy = maze_copy.get_cell(x, y)
if orig:
copy.is_wall = orig.is_wall
copy.is_start = orig.is_start
copy.is_exit = orig.is_exit
maze_copy.start = maze_copy.get_cell(maze.start.x, maze.start.y) if maze.start else None
maze_copy.exit = maze_copy.get_cell(maze.exit.x, maze.exit.y) if maze.exit else None
solver.maze = maze_copy
solver.set_strategy(strategy)
path, stats = solver.solve()
times.append(stats.time_ms)
visited.append(stats.visited_cells)
path_lengths.append(stats.path_length)
avg_time = sum(times) / len(times)
avg_visited = sum(visited) / len(visited)
avg_path = sum(path_lengths) / len(path_lengths)
results.append({
'maze': maze_name,
'algorithm': strategy.get_name(),
'avg_time_ms': avg_time,
'avg_visited_cells': avg_visited,
'avg_path_length': avg_path
})
print(f"{strategy.get_name()}: {avg_time:.2f}ms, {avg_visited:.0f} cells, path={avg_path:.0f}")
with open('experiment_results.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=['maze', 'algorithm', 'avg_time_ms', 'avg_visited_cells', 'avg_path_length'])
writer.writeheader()
writer.writerows(results)
print("\nResults saved to experiment_results.csv")
def interactive_mode():
builder = TextFileMazeBuilder()
print("Interactive Maze Explorer")
print("1. Load maze from file")
print("2. Generate random maze")
choice = input("Choose (1/2): ")
if choice == '1':
filename = input("Enter filename: ")
try:
maze = builder.build_from_file(filename)
except Exception as e:
print(f"Error loading maze: {e}")
return
else:
w = int(input("Width: "))
h = int(input("Height: "))
maze = generate_test_maze(w, h, 0.3)
player = Player(maze.start)
strategies = {
'1': BFSStrategy(),
'2': DFSStrategy(),
'3': AStarStrategy(),
'4': DijkstraStrategy()
}
print("\nSelect algorithm for solving:")
print("1. BFS (shortest path)")
print("2. DFS (fast, not optimal)")
print("3. A* (heuristic)")
print("4. Dijkstra")
algo_choice = input("Choose: ")
solver = MazeSolver(maze, strategies.get(algo_choice, BFSStrategy()))
path, stats = solver.solve()
view = ConsoleView()
if path:
print(f"\nPath found! Length: {len(path)}")
view.show_stats(stats, solver.strategy.get_name())
else:
print("\nNo path found!")
while True:
view.render(maze, player, path if path else None)
if player.current_cell == maze.exit:
print("Congratulations! You reached the exit!")
break
cmd = input("Move (W/A/S/D) | U=undo | Q=quit | S=solve: ").upper()
if cmd == 'Q':
break
elif cmd == 'U':
player.undo()
print("Undo last move")
elif cmd == 'S' and path:
for cell in path:
if cell == player.current_cell:
continue
player.move_to(cell)
view.render(maze, player, path)
input("Press Enter to continue...")
if player.current_cell == maze.exit:
print("You reached the exit!")
break
elif cmd in ['W', 'A', 'S', 'D']:
move_cmd = MoveCommand(player, maze, cmd)
if move_cmd.execute():
print("Moved")
else:
print("Can't move there!")
def main():
print("Maze Solver with Design Patterns")
print("1. Run experiments")
print("2. Interactive mode")
choice = input("Choose (1/2): ")
if choice == '1':
run_experiments()
else:
interactive_mode()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,21 @@
maze,algorithm,avg_time_ms,avg_visited_cells,avg_path_length
Small (10x10),BFS,0.006740167737007141,80.0,0.0
Small (10x10),DFS,0.00408003106713295,80.0,0.0
Small (10x10),A*,0.005039852112531662,80.0,0.0
Small (10x10),Dijkstra,0.0031800009310245514,80.0,0.0
Medium (50x50),BFS,3.44578018411994,1890.0,99.0
Medium (50x50),DFS,1.3188599608838558,1890.0,341.0
Medium (50x50),A*,2.061920054256916,1890.0,99.0
Medium (50x50),Dijkstra,4.679400008171797,1890.0,99.0
Large (100x100),BFS,0.025319866836071014,6998.0,0.0
Large (100x100),DFS,0.019940081983804703,6998.0,0.0
Large (100x100),A*,0.035060010850429535,6998.0,0.0
Large (100x100),Dijkstra,0.02901991829276085,6998.0,0.0
Empty (30x30),BFS,1.2404202483594418,900.0,59.0
Empty (30x30),DFS,0.8887200616300106,900.0,465.0
Empty (30x30),A*,0.9468601085245609,900.0,59.0
Empty (30x30),Dijkstra,2.678940072655678,900.0,59.0
No Exit (20x20),BFS,0.27012014761567116,380.0,0.0
No Exit (20x20),DFS,0.3163599409162998,380.0,0.0
No Exit (20x20),A*,0.5885399878025055,380.0,0.0
No Exit (20x20),Dijkstra,0.5776201374828815,380.0,0.0
1 maze algorithm avg_time_ms avg_visited_cells avg_path_length
2 Small (10x10) BFS 0.006740167737007141 80.0 0.0
3 Small (10x10) DFS 0.00408003106713295 80.0 0.0
4 Small (10x10) A* 0.005039852112531662 80.0 0.0
5 Small (10x10) Dijkstra 0.0031800009310245514 80.0 0.0
6 Medium (50x50) BFS 3.44578018411994 1890.0 99.0
7 Medium (50x50) DFS 1.3188599608838558 1890.0 341.0
8 Medium (50x50) A* 2.061920054256916 1890.0 99.0
9 Medium (50x50) Dijkstra 4.679400008171797 1890.0 99.0
10 Large (100x100) BFS 0.025319866836071014 6998.0 0.0
11 Large (100x100) DFS 0.019940081983804703 6998.0 0.0
12 Large (100x100) A* 0.035060010850429535 6998.0 0.0
13 Large (100x100) Dijkstra 0.02901991829276085 6998.0 0.0
14 Empty (30x30) BFS 1.2404202483594418 900.0 59.0
15 Empty (30x30) DFS 0.8887200616300106 900.0 465.0
16 Empty (30x30) A* 0.9468601085245609 900.0 59.0
17 Empty (30x30) Dijkstra 2.678940072655678 900.0 59.0
18 No Exit (20x20) BFS 0.27012014761567116 380.0 0.0
19 No Exit (20x20) DFS 0.3163599409162998 380.0 0.0
20 No Exit (20x20) A* 0.5885399878025055 380.0 0.0
21 No Exit (20x20) Dijkstra 0.5776201374828815 380.0 0.0

View File

@ -0,0 +1,196 @@
Методы программирования
Поиск выхода из лабиринта.
Анализ 2 задания
Бобров К. Н.
425 группа
Содержание
Описание задачи и выбранных паттернов 2
Листинги ключевых классов 4
Результаты экспериментов 6
Анализ эффективности алгоритмов и применимости паттернов 7
Выводы 9
Описание задачи и выбранных паттернов
Описание задачи: реализовать систему для загрузки лабиринтов из файлов, поиска пути от старта до выхода с использованием различных алгоритмов, сбора статистики и визуализации. Ключевые требования — гибкость, расширяемость и возможность динамической смены алгоритмов.
Выбранные паттерны:
?Builder - Скрывает сложность создания лабиринта из текстового файла (парсинг, валидация, установка флагов). Позволяет легко добавить поддержку других форматов (JSON, XML).
?Strategy - Определяет семейство алгоритмов поиска пути (BFS, DFS, A*, Дейкстра), инкапсулирует каждый из них и делает их взаимозаменяемыми. Клиент (MazeSolver) может переключать стратегии во время выполнения.
?Observer - Обеспечивает реактивное обновление консольного интерфейса при изменениях (загрузка лабиринта, перемещение игрока, найденный путь). Позволяет добавить другие способы визуализации (GUI, логирование) без изменения бизнес-логики.
?Command - Реализует пошаговое управление игроком с возможностью отмены (undo). Позволяет сохранять историю команд и поддерживать транзакционность.
Листинги ключевых классов
Builder (TextFileMazeBuilder):
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename: str) -> Maze:
with open(filename, 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n') for line in f.readlines()]
height = len(lines)
width = max(len(line) for line in lines) if height > 0 else 0
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
cell = maze.get_cell(x, y)
if cell is None:
continue
if ch == '#':
cell.is_wall = True
elif ch == 'S':
cell.is_start = True
maze.start = cell
elif ch == 'E':
cell.is_exit = True
maze.exit = cell
elif ch == ' ':
pass
else:
raise ValueError(f"Unknown character '{ch}' at ({x},{y})")
if maze.start is None or maze.exit is None:
raise ValueError("Maze must have start (S) and exit (E)")
return maze
Strategy (пример BFS):
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
queue = deque([start])
came_from = {start: None}
while queue:
current = queue.popleft()
if current == exit:
break
for nb in maze.get_neighbors(current):
if nb not in came_from:
came_from[nb] = current
queue.append(nb)
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "BFS"
Observer (ConsoleView):
class ConsoleView:
@staticmethod
def render(maze: Maze, player: Optional[Player] = None, path: Optional[List[Cell]] = None):
path_set = set()
if path:
path_set = set(path)
for y in range(maze.height):
line = ""
for x in range(maze.width):
cell = maze.get_cell(x, y)
if not cell:
line += " "
elif player and player.current_cell == cell:
line += "P"
elif cell.is_start:
line += "S"
elif cell.is_exit:
line += "E"
elif cell.is_wall:
line += "#"
elif path and cell in path_set:
line += "."
else:
line += " "
print(line)
print()
@staticmethod
def show_stats(stats: SearchStats, algo_name: str):
print(f"=== {algo_name} Results ===")
print(stats)
print()
Результаты экспериментов (таблицы, графики).
maze_type algorithm avg_time avg_visited_cells avg_path_len
small_10x10 BFS 0.08572000006097369 79.0 19.0
small_10x10 DFS 0.039739999920129776 79.0 31.0
small_10x10_ A* 0.13467999997374136 79.0 19.0
small_10x10 Dijkstra 0.11474000057205558 79.0 19.0
medium_50x50 BFS 1.8074600004183594 1874.0 99.0
medium_50x50 DFS 0.5937599995377241 1874.0 429.0
medium_50x50 A* 1.6300600003887666 1874.0 99.0
medium_50x50 Dijkstra 3.1870400001935195 1874.0 99.0
large_100x100 BFS 0.014439999722526409 7033.0 0.0
large_100x100 DFS 0.014839999857940711 7033.0 0.0
large_100x100 A* 0.02542000001994893 7033.0 0.0
large_100x100 Dijkstra 0.02548000011302065 7033.0 0.0
empty_30x30 BFS 0.784620000194991 900.0 59.0
empty_30x30 DFS 0.5252399994787993 900.0 465.
empty_30x30 A* 1.150900000357069 900.0 59.0
empty_30x30 Dijkstra 1.564640000287909 900.0 59.0
no_exit_20x20 BFS 0.2002399993216386 380. 0.0
no_exit_20x20 DFS 0.2512400002160575 380.0 0.0
no_exit_20x20 A* 0.5590400000073714 380.0 0.
no_exit_20x20 Dijkstra 0.35640000060084276 380.0 0.0
Графики построены кодом из файла RESULT22.
Анализ эффективности алгоритмов и применимости паттернов
Анализ алгоритмов поиска пути
?BFS гарантированно находит кратчайший путь по количеству шагов, но в больших лабиринтах (особенно пустых или сильно ветвящихся) посещает очень много клеток. Время работы растёт пропорционально числу достижимых клеток.
?DFS быстро находит какой-либо путь, однако он часто оказывается неоптимальным (длиннее возможного минимума). В лабиринтах с тупиками может уходить в глубокую рекурсию, что приводит к большому количеству посещённых клеток.
?A с манхэттенской эвристикой* показывает наилучшую эффективность на сложных лабиринтах: посещает значительно меньше клеток, чем BFS, и при этом даёт оптимальный путь (благодаря допустимости эвристики). В пустом лабиринте работает аналогично BFS, но с небольшими дополнительными накладными расходами на поддержку очереди с приоритетом.
?Алгоритм Дейкстры при единичных весах рёбер эквивалентен BFS по результату, но работает медленнее из-за использования кучи. Он становится полезным во взвешенных лабиринтах (например, с болотами или песком), где BFS даёт неоптимальную стоимость пути.
Применимость паттернов проектирования
?Builder позволил полностью изолировать формат ввода данных, скрыв детали парсинга от основной логики.
?Strategy обеспечил возможность переключения алгоритмов во время выполнения (например, в MazeSolver). Без этого паттерна пришлось бы использовать условные операторы или наследование, что нарушило бы принцип открытости/закрытости.
?Observer отделил визуализацию от бизнес-логики. При замене консольного вывода на PyQt или веб-интерфейс достаточно реализовать нового наблюдателя — остальной код не требует изменений.
?Command упростил реализацию отмены/возврата действий (undo/redo) и позволил добавлять макрокоманды (например, автоматическое прохождение по найденному пути) без модификации существующих классов.
Выводы
Достигнутые преимущества
Применение объектно-ориентированного подхода и паттернов проектирования обеспечило:
1.Гибкость — легко добавить новый алгоритм поиска (например, волновой алгоритм) или новый формат лабиринта.
2.Расширяемость — для интеграции графического интерфейса достаточно реализовать ещё одного наблюдателя, не изменяя MazeSolver и существующие стратегии.
3.Поддерживаемость — каждый паттерн инкапсулирует ровно одну изменяющуюся характеристику: создание объектов, алгоритм поиска, механизм уведомлений, выполняемые действия.
4.Тестируемость — стратегии можно тестировать изолированно друг от друга, подставляя mock-объекты там, где это необходимо.
Что потребовало бы больших усилий без паттернов
?Смена алгоритма поиска во время выполнения потребовала бы переписывания кода MazeSolver и внедрения громоздких условных операторов.
?Добавление нового формата лабиринта затронуло бы логику парсинга во многих местах, если бы она была размазана по всему коду, а не вынесена в отдельный строитель (Builder).
?Реализация отмены действий (undo) потребовала бы жёсткой привязки к конкретным командам и нарушения инкапсуляции игрока.
?Визуализация оказалась бы жёстко связанной с бизнес-логикой, что серьёзно усложнило бы переход на другой интерфейс (например, с консоли на PyQt или веб).
Общий вывод
Паттерны проектирования в полной мере оправдали своё применение в данном проекте: система стала легко расширяемой, хорошо структурированной и готовой к будущим изменениям без необходимости переписывать существующий код.

0
LebedevES/425.txt Normal file
View File

View File

@ -0,0 +1,292 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import time
import random
import csv
import sys
sys.setrecursionlimit(30000)
def ll_create_node(name, phone):
return {'name': name, 'phone': phone, 'next': None}
def ll_insert(head, name, phone):
if head is None:
return ll_create_node(name, phone)
if head['name'] == name:
head['phone'] = phone
return head
current = head
while current['next'] is not None:
if current['next']['name'] == name:
current['next']['phone'] = phone
return head
current = current['next']
current['next'] = ll_create_node(name, phone)
return head
def ll_find(head, name):
current = head
while current is not None:
if current['name'] == name:
return current['phone']
current = current['next']
return None
def ll_delete(head, name):
if head is None:
return None
if head['name'] == name:
return head['next']
current = head
while current['next'] is not None:
if current['next']['name'] == name:
current['next'] = current['next']['next']
return head
current = current['next']
return head
def ll_list_all(head):
records = []
current = head
while current is not None:
records.append((current['name'], current['phone']))
current = current['next']
records.sort(key=lambda x: x[0])
return records
def hash_function(name, table_size):
return sum(ord(c) for c in name) % table_size
def ht_create_table(size=2000):
return [None] * size
def ht_insert(table, name, phone):
index = hash_function(name, len(table))
table[index] = ll_insert(table[index], name, phone)
def ht_find(table, name):
index = hash_function(name, len(table))
return ll_find(table[index], name)
def ht_delete(table, name):
index = hash_function(name, len(table))
table[index] = ll_delete(table[index], name)
def ht_list_all(table):
all_records = []
for bucket in table:
if bucket is not None:
current = bucket
while current is not None:
all_records.append((current['name'], current['phone']))
current = current['next']
all_records.sort(key=lambda x: x[0])
return all_records
def bst_create_node(name, phone):
return {'name': name, 'phone': phone, 'left': None, 'right': None}
def bst_insert(root, name, phone):
if root is None:
return bst_create_node(name, phone)
current = root
while True:
if name < current['name']:
if current['left'] is None:
current['left'] = bst_create_node(name, phone)
break
else:
current = current['left']
elif name > current['name']:
if current['right'] is None:
current['right'] = bst_create_node(name, phone)
break
else:
current = current['right']
else:
current['phone'] = phone
break
return root
def bst_find(root, name):
current = root
while current is not None:
if name < current['name']:
current = current['left']
elif name > current['name']:
current = current['right']
else:
return current['phone']
return None
def bst_find_min(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
parent = None
current = root
while current is not None and current['name'] != name:
parent = current
if name < current['name']:
current = current['left']
else:
current = current['right']
if current is None:
return root
if current['left'] is None or current['right'] is None:
if current['left'] is not None:
child = current['left']
else:
child = current['right']
if parent is None:
return child
if parent['left'] == current:
parent['left'] = child
else:
parent['right'] = child
else:
successor_parent = current
successor = current['right']
while successor['left'] is not None:
successor_parent = successor
successor = successor['left']
current['name'] = successor['name']
current['phone'] = successor['phone']
if successor_parent['left'] == successor:
successor_parent['left'] = successor['right']
else:
successor_parent['right'] = successor['right']
return root
def bst_list_all(root):
records = []
stack = []
current = root
while stack or current is not None:
while current is not None:
stack.append(current)
current = current['left']
current = stack.pop()
records.append((current['name'], current['phone']))
current = current['right']
return records
def generate_data(n=10000):
records = [(f"User_{i:05d}", f"+7-999-{i:06d}") for i in range(n)]
records_shuffled = records.copy()
random.shuffle(records_shuffled)
records_sorted = sorted(records, key=lambda x: x[0])
return records_shuffled, records_sorted
def run_experiment(structure_name, insert_func, find_func, delete_func,
list_all_func, init_func, records, n_find=100):
data = init_func()
names = [r[0] for r in records]
start = time.perf_counter()
for name, phone in records:
if structure_name == "HashTable":
insert_func(data, name, phone)
else:
data = insert_func(data, name, phone)
insert_time = time.perf_counter() - start
find_names = random.sample(names, min(n_find, len(names)))
missing_names = [f"None_{i}" for i in range(10)]
all_find_names = find_names + missing_names
start = time.perf_counter()
for name in all_find_names:
if structure_name == "HashTable":
find_func(data, name)
else:
find_func(data, name)
find_time = time.perf_counter() - start
delete_names = random.sample(names, min(50, len(names)))
start = time.perf_counter()
for name in delete_names:
if structure_name == "HashTable":
delete_func(data, name)
else:
data = delete_func(data, name)
delete_time = time.perf_counter() - start
return insert_time, find_time, delete_time
def main():
print("Generating test data...")
records_shuffled, records_sorted = generate_data(10000)
results = []
structures = [
("LinkedList", ll_insert, ll_find, ll_delete, ll_list_all, lambda: None),
("HashTable", ht_insert, ht_find, ht_delete, ht_list_all, lambda: ht_create_table(2000)),
("BST", bst_insert, bst_find, bst_delete, bst_list_all, lambda: None)
]
for mode_name, records in [("random", records_shuffled), ("sorted", records_sorted)]:
print(f"\nMode: {mode_name}")
for struct_name, insert_f, find_f, delete_f, list_f, init_f in structures:
print(f" Testing {struct_name}...")
times = []
for run in range(5):
insert_t, find_t, delete_t = run_experiment(
struct_name, insert_f, find_f, delete_f, list_f, init_f, records
)
times.append((insert_t, find_t, delete_t))
print(f" Run {run+1}: insert={insert_t:.4f}s, find={find_t:.4f}s, delete={delete_t:.4f}s")
avg_insert = sum(t[0] for t in times) / 5
avg_find = sum(t[1] for t in times) / 5
avg_delete = sum(t[2] for t in times) / 5
results.append([struct_name, mode_name, "insert", avg_insert])
results.append([struct_name, mode_name, "find", avg_find])
results.append([struct_name, mode_name, "delete", avg_delete])
with open("results.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["Structure", "Mode", "Operation", "Time_seconds"])
writer.writerows(results)
print("\n" + "="*60)
print("RESULTS (average over 5 runs):")
print("="*60)
for row in results:
print(f"{row[0]:12} | {row[1]:8} | {row[2]:8} | {row[3]:.6f} sec")
print("\nResults saved to results.csv")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,122 @@
Методы Программирования
Структуры данных,
анализ 1 задания
Бобров К. Н.
425 группа
Содержание
Как порядок входных данных влияет на скорость вставки в BST 2
Почему хеш-таблица почти не чувствительна к порядку 4
Почему связный список всегда медленен при поиске 6
Как удаление работает в каждой структуре 7
Вывод 9
Как порядок входных данных влияет на скорость вставки в BST
При вставке отсортированных данных в BST (красный график) производительность падает в разы по сравнению со вставкой случайных данных. Это связано с тем, что отсортированная последовательность приводит к вырождению дерева в связанный список, тогда как случайный порядок вставки помогает сохранять дерево относительно сбалансированным.
При вставке элементов в отсортированном порядке (по возрастанию или убыванию):
?Каждый новый элемент всегда больше (или меньше) всех уже добавленных.
?В результате алгоритм каждый раз движется по одному и тому же направлению — только в правое или только в левое поддерево.
?Из-за этого дерево вырождается: каждый узел имеет не более одного потомка, структура напоминает линейный список.
?Высота такого дерева становится пропорциональной O(n).
?Каждая операция вставки требует в среднем O(n) сравнений, так как нужно проходить всю длину текущей цепочки от корня до самого глубокого листа.
?В итоге суммарная сложность вставки всех n элементов вырастает до O(n^2).
При случайной вставке:
?Элементы распределяются по дереву гораздо равномернее.
?Высока вероятность того, что дерево останется сбалансированным.
?Средняя высота дерева сохраняется на уровне O(logn).
?Каждая операция вставки в среднем требует O(logn) сравнений.
?Общая сложность вставки всех n элементов составляет  O(nlogn).
Вывод: разница в скорости объясняется различием в высоте дерева. В вырожденном случае высота равна O(n), и каждая вставка выполняется в ?n/logn раз медленнее по числу шагов, чем в сбалансированном случае с высотой O(logn).
Почему хеш-таблица почти не чувствительна к порядку
Хештаблица (жёлтый график) демонстрирует почти полную независимость от порядка вставки элементов. Это объясняется тем, что положение каждого элемента в структуре определяется исключительно значением его хешфункции, а не тем, в какой последовательности происходило добавление данных.
Основные причины нечувствительности к порядку вставки:
?Хеширование. Для каждого ключа вычисляется хешкод, который преобразуется в индекс ячейки. Один и тот же ключ всегда даёт один и тот же индекс независимо от того, когда и в каком порядке он был добавлен.
?Независимость операций. Вставка, поиск и удаление выполняются в среднем за O(1)O(1), поскольку алгоритм сразу вычисляет нужную позицию, не обходя структуру и не учитывая историю добавлений.
?Разрешение коллизий. Даже если порядок вставки влияет на расположение элементов внутри цепочки (метод цепочек) или на последовательность проб (открытая адресация), это касается лишь небольших групп элементов с одинаковыми хешами. Общая производительность остаётся стабильной.
?Рехеширование. При увеличении размера таблицы все элементы перераспределяются заново. Новый порядок определяется актуальной хеш-функцией и размером таблицы, а не исходной последовательностью вставки.
Итог: Время выполнения операций зависит от качества хеш-функции, коэффициента заполнения таблицы и метода разрешения коллизий, но не зависит от порядка добавления элементов.
Почему связный список всегда медленен при поиске
Связный список показывает низкую скорость поиска из-за необходимости последовательного обхода: чтобы найти элемент, требуется пройти по указателям от головы до нужного узла.
Почему это происходит:
?Отсутствие произвольного доступа. В отличие от массива, где доступ по индексу занимает O(1), в связном списке элементы приходится перебирать последовательно, что даёт сложность поиска O(n).
?Низкая локальность данных. Узлы списка разбросаны по памяти случайным образом. Это вызывает частые промахи кэша: процессор не может подгрузить блок соседних данных, и каждый переход по указателю оборачивается новым обращением к оперативной памяти.
?Дополнительная память на указатели. Каждый узел хранит не только полезные данные, но и указатель на следующий элемент. Это увеличивает объём памяти и ухудшает эффективность кэша — на те же данные приходится загружать больше информации.
?Затраты на разыменование указателей. На каждом шаге поиска процессору нужно:
oпрочитать текущий узел,
oизвлечь из него указатель на следующий,
oперейти по этому адресу.
Эти операции замедляют работу по сравнению с простым сдвигом индекса в массиве.
Итог: хотя алгоритмическая сложность обхода составляет O(n) как для массива (при линейном поиске), так и для связного списка, на практике список работает ощутимо медленнее из-за особенностей организации памяти и работы кэша.
Как удаление работает в каждой структуре
1. Связный список
Односвязный список: чтобы удалить узел, необходимо сначала найти предыдущий элемент и перенаправить его указатель next на узел, следующий за удаляемым. Исключение — удаление первого элемента: достаточно сдвинуть указатель head на второй узел.
Двусвязный список: удаление проще, поскольку у каждого узла есть указатели и на следующий (next), и на предыдущий (prev). При удалении обновляются ссылки обоих соседей: prev->next = next, next->prev = prev.
Сложность: в общем случае O(n) из-за необходимости поиска элемента; удаление головы или хвоста (при наличии прямой ссылки на хвост) выполняется за O(1).
2. Хештаблица
Сначала через хеш-функцию h(key) вычисляется индекс ячейки. Дальнейшие действия зависят от метода разрешения коллизий:
?Раздельная цепочка: элемент удаляется из связного списка (или другой структуры), находящегося по вычисленному индексу.
?Открытая адресация: ячейка помечается специальным маркером «удалён», а не просто как пустая — это важно для корректности последующих операций поиска.
Сложность: в среднем O(1), в худшем случае O(n) (при большом количестве коллизий).
3. Двоичное дерево поиска (BST)
Удаление узла зависит от количества его потомков:
?Нет детей (лист): узел просто удаляется, ссылка родителя обнуляется.
?Один ребёнок: удаляемый узел заменяется его единственным потомком — родитель «перепрыгивает» через удаляемый узел.
?Два ребёнка:
1.Находится преемник (самый левый (наименьший) узел в правом поддереве) или предшественник (самый правый (наибольший) узел в левом поддереве).
2.Значение преемника/предшественника копируется в удаляемый узел.
3.Преемник/предшественник рекурсивно удаляется — он гарантированно имеет не более одного ребёнка.
Сложность: O(h), где h — высота дерева. В сбалансированном дереве h=O(logn), в несбалансированном — до O(n).
Вывод
1. Частые вставки
Связный список — отличный выбор для частых вставок (особенно в середину), если не требуется быстрый доступ по индексу. Вставка в начало или конец выполняется за O(1), в середину — за O(n) (но без сдвига элементов, как в массиве).
Хештаблица — хорошо подходит для вставок по ключу, обеспечивая в среднем O(1).
2. Частый поиск
Хештаблица — лучший вариант для быстрого поиска по ключу. Среднее время — O(1), в худшем случае — O(n) (при сильных коллизиях).
Сбалансированное двоичное дерево поиска — предпочтительнее, если нужен поиск с гарантированной сложностью O(logn) даже в худшем случае.
3. Необходимость получать данные в отсортированном порядке
Массив / список — эффективен, если данные уже отсортированы или сортировка происходит редко, а последовательное чтение — часто. Доступ по индексу — O(1), но вставка и удаление в середину требуют O(n).
Отсортированный массив — удобен для поиска (бинарный поиск даёт (O(logn)), однако вставки и удаления обходятся в O(n).
Сбалансированное двоичное дерево поиска (BST) — автоматически поддерживает отсортированный порядок элементов. Все основные операции выполняются за O(logn). Идеальный вариант, когда данные часто изменяются и при этом требуется обход элементов в отсортированном порядке.

View File

@ -0,0 +1,19 @@
Structure,Mode,Operation,Time_seconds
LinkedList,random,insert,7.967956480104476
LinkedList,random,find,0.05891917999833822
LinkedList,random,delete,0.03816298004239797
HashTable,random,insert,0.39825033992528913
HashTable,random,find,0.002917400002479553
HashTable,random,delete,0.0021501399576663973
BST,random,insert,0.02822491992264986
BST,random,find,0.00023473985493183136
BST,random,delete,0.00016456004232168198
LinkedList,sorted,insert,8.014810599852353
LinkedList,sorted,find,0.058480959851294756
LinkedList,sorted,delete,0.04817821998149156
HashTable,sorted,insert,0.3703480200842023
HashTable,sorted,find,0.002751259971410036
HashTable,sorted,delete,0.0018340200185775757
BST,sorted,insert,7.301413399912417
BST,sorted,find,0.06847236007452011
BST,sorted,delete,0.03443789994344115
1 Structure Mode Operation Time_seconds
2 LinkedList random insert 7.967956480104476
3 LinkedList random find 0.05891917999833822
4 LinkedList random delete 0.03816298004239797
5 HashTable random insert 0.39825033992528913
6 HashTable random find 0.002917400002479553
7 HashTable random delete 0.0021501399576663973
8 BST random insert 0.02822491992264986
9 BST random find 0.00023473985493183136
10 BST random delete 0.00016456004232168198
11 LinkedList sorted insert 8.014810599852353
12 LinkedList sorted find 0.058480959851294756
13 LinkedList sorted delete 0.04817821998149156
14 HashTable sorted insert 0.3703480200842023
15 HashTable sorted find 0.002751259971410036
16 HashTable sorted delete 0.0018340200185775757
17 BST sorted insert 7.301413399912417
18 BST sorted find 0.06847236007452011
19 BST sorted delete 0.03443789994344115

View File

@ -0,0 +1,589 @@
import time
import heapq
from collections import deque
from typing import List, Optional, Dict, Tuple
from abc import ABC, abstractmethod
import csv
import random
class Cell:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
self.is_wall = False
self.is_start = False
self.is_exit = False
def is_passable(self) -> bool:
return not self.is_wall
class Maze:
def __init__(self, width: int, height: int):
self.width = width
self.height = height
self.cells = [[Cell(x, y) for y in range(height)] for x in range(width)]
self.start: Optional[Cell] = None
self.exit: Optional[Cell] = None
def get_cell(self, x: int, y: int) -> Optional[Cell]:
if 0 <= x < self.width and 0 <= y < self.height:
return self.cells[x][y]
return None
def get_neighbors(self, cell: Cell) -> List[Cell]:
neighbors = []
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
nx, ny = cell.x + dx, cell.y + dy
nb = self.get_cell(nx, ny)
if nb and nb.is_passable():
neighbors.append(nb)
return neighbors
class MazeBuilder(ABC):
@abstractmethod
def build_from_file(self, filename: str) -> Maze:
pass
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename: str) -> Maze:
with open(filename, 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n') for line in f.readlines()]
height = len(lines)
width = max(len(line) for line in lines) if height > 0 else 0
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
cell = maze.get_cell(x, y)
if cell is None:
continue
if ch == '#':
cell.is_wall = True
elif ch == 'S':
cell.is_start = True
maze.start = cell
elif ch == 'E':
cell.is_exit = True
maze.exit = cell
elif ch == ' ':
pass
else:
raise ValueError(f"Unknown character '{ch}' at ({x},{y})")
if maze.start is None or maze.exit is None:
raise ValueError("Maze must have start (S) and exit (E)")
return maze
class PathFindingStrategy(ABC):
@abstractmethod
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
pass
@abstractmethod
def get_name(self) -> str:
pass
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
queue = deque([start])
came_from = {start: None}
while queue:
current = queue.popleft()
if current == exit:
break
for nb in maze.get_neighbors(current):
if nb not in came_from:
came_from[nb] = current
queue.append(nb)
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "BFS"
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
stack = [start]
came_from = {start: None}
while stack:
current = stack.pop()
if current == exit:
break
for nb in maze.get_neighbors(current):
if nb not in came_from:
came_from[nb] = current
stack.append(nb)
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "DFS"
class AStarStrategy(PathFindingStrategy):
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) -> List[Cell]:
open_set = []
heapq.heappush(open_set, (0, id(start), start))
came_from = {}
g_score = {start: 0}
f_score = {start: self._heuristic(start, exit)}
while open_set:
_, _, current = heapq.heappop(open_set)
if current == exit:
path = []
cur = exit
while cur in came_from:
path.append(cur)
cur = came_from[cur]
path.append(start)
path.reverse()
return path
for neighbor in maze.get_neighbors(current):
tentative_g = g_score[current] + 1
if tentative_g < g_score.get(neighbor, float('inf')):
came_from[neighbor] = current
g_score[neighbor] = tentative_g
f_score[neighbor] = tentative_g + self._heuristic(neighbor, exit)
heapq.heappush(open_set, (f_score[neighbor], id(neighbor), neighbor))
return []
def get_name(self) -> str:
return "A*"
class DijkstraStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
pq = [(0, id(start), start)]
distances = {start: 0}
came_from = {start: None}
while pq:
dist, _, current = heapq.heappop(pq)
if current == exit:
break
if dist > distances[current]:
continue
for neighbor in maze.get_neighbors(current):
new_dist = dist + 1
if new_dist < distances.get(neighbor, float('inf')):
distances[neighbor] = new_dist
came_from[neighbor] = current
heapq.heappush(pq, (new_dist, id(neighbor), neighbor))
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "Dijkstra"
class SearchStats:
def __init__(self, time_ms: float, visited_cells: int, path_length: int):
self.time_ms = time_ms
self.visited_cells = visited_cells
self.path_length = path_length
def __str__(self):
return f"Time: {self.time_ms:.2f}ms, Visited: {self.visited_cells}, Path: {self.path_length}"
class MazeSolver:
def __init__(self, maze: Maze, strategy: PathFindingStrategy):
self.maze = maze
self.strategy = strategy
def set_strategy(self, strategy: PathFindingStrategy):
self.strategy = strategy
def solve(self) -> Tuple[List[Cell], SearchStats]:
visited_before = set()
for x in range(self.maze.width):
for y in range(self.maze.height):
cell = self.maze.get_cell(x, y)
if cell and cell.is_passable():
visited_before.add(cell)
start_time = time.perf_counter()
path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
end_time = time.perf_counter()
visited_after = set()
for x in range(self.maze.width):
for y in range(self.maze.height):
cell = self.maze.get_cell(x, y)
if cell and cell.is_passable():
visited_after.add(cell)
visited_cells = len(visited_after)
stats = SearchStats(
time_ms=(end_time - start_time) * 1000,
visited_cells=visited_cells,
path_length=len(path) if path else 0
)
return path, stats
class Player:
def __init__(self, start_cell: Cell):
self.current_cell = start_cell
self.previous_cell = None
def move_to(self, cell: Cell) -> bool:
if cell.is_passable():
self.previous_cell = self.current_cell
self.current_cell = cell
return True
return False
def undo(self):
if self.previous_cell:
self.current_cell, self.previous_cell = self.previous_cell, None
return True
return False
class Command(ABC):
@abstractmethod
def execute(self) -> bool:
pass
@abstractmethod
def undo(self):
pass
class MoveCommand(Command):
def __init__(self, player: Player, maze: Maze, direction: str):
self.player = player
self.maze = maze
self.direction = direction
self.executed = False
def execute(self) -> bool:
dx, dy = 0, 0
if self.direction == 'W' or self.direction == 'w':
dy = -1
elif self.direction == 'S' or self.direction == 's':
dy = 1
elif self.direction == 'A' or self.direction == 'a':
dx = -1
elif self.direction == 'D' or self.direction == 'd':
dx = 1
new_x = self.player.current_cell.x + dx
new_y = self.player.current_cell.y + dy
new_cell = self.maze.get_cell(new_x, new_y)
if new_cell and new_cell.is_passable():
self.executed = self.player.move_to(new_cell)
return self.executed
return False
def undo(self):
if self.executed:
self.player.undo()
self.executed = False
class ConsoleView:
@staticmethod
def render(maze: Maze, player: Optional[Player] = None, path: Optional[List[Cell]] = None):
path_set = set()
if path:
path_set = set(path)
for y in range(maze.height):
line = ""
for x in range(maze.width):
cell = maze.get_cell(x, y)
if not cell:
line += " "
elif player and player.current_cell == cell:
line += "P"
elif cell.is_start:
line += "S"
elif cell.is_exit:
line += "E"
elif cell.is_wall:
line += "#"
elif path and cell in path_set:
line += "."
else:
line += " "
print(line)
print()
@staticmethod
def show_stats(stats: SearchStats, algo_name: str):
print(f"=== {algo_name} Results ===")
print(stats)
print()
def generate_test_maze(width: int, height: int, complexity: float = 0.3) -> Maze:
maze = Maze(width, height)
for x in range(width):
for y in range(height):
if random.random() < complexity:
maze.cells[x][y].is_wall = True
maze.start = maze.get_cell(0, 0)
if maze.start:
maze.start.is_start = True
maze.start.is_wall = False
maze.exit = maze.get_cell(width - 1, height - 1)
if maze.exit:
maze.exit.is_exit = True
maze.exit.is_wall = False
return maze
def generate_empty_maze(width: int, height: int) -> Maze:
maze = Maze(width, height)
for x in range(width):
for y in range(height):
maze.cells[x][y].is_wall = False
maze.start = maze.get_cell(0, 0)
if maze.start:
maze.start.is_start = True
maze.exit = maze.get_cell(width - 1, height - 1)
if maze.exit:
maze.exit.is_exit = True
return maze
def generate_no_exit_maze(width: int, height: int) -> Maze:
maze = Maze(width, height)
for x in range(width):
for y in range(height):
maze.cells[x][y].is_wall = False
for x in range(width):
maze.cells[x][height // 2].is_wall = True
maze.start = maze.get_cell(0, 0)
if maze.start:
maze.start.is_start = True
maze.exit = maze.get_cell(width - 1, height - 1)
if maze.exit:
maze.exit.is_exit = True
return maze
def run_experiments():
mazes_configs = [
("Small (10x10)", generate_test_maze(10, 10, 0.2)),
("Medium (50x50)", generate_test_maze(50, 50, 0.25)),
("Large (100x100)", generate_test_maze(100, 100, 0.3)),
("Empty (30x30)", generate_empty_maze(30, 30)),
("No Exit (20x20)", generate_no_exit_maze(20, 20))
]
strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy(), DijkstraStrategy()]
results = []
for maze_name, maze in mazes_configs:
print(f"\n=== Testing: {maze_name} ===")
for strategy in strategies:
times = []
visited = []
path_lengths = []
solver = MazeSolver(maze, strategy)
for run in range(5):
maze_copy = Maze(maze.width, maze.height)
for x in range(maze.width):
for y in range(maze.height):
orig = maze.get_cell(x, y)
copy = maze_copy.get_cell(x, y)
if orig:
copy.is_wall = orig.is_wall
copy.is_start = orig.is_start
copy.is_exit = orig.is_exit
maze_copy.start = maze_copy.get_cell(maze.start.x, maze.start.y) if maze.start else None
maze_copy.exit = maze_copy.get_cell(maze.exit.x, maze.exit.y) if maze.exit else None
solver.maze = maze_copy
solver.set_strategy(strategy)
path, stats = solver.solve()
times.append(stats.time_ms)
visited.append(stats.visited_cells)
path_lengths.append(stats.path_length)
avg_time = sum(times) / len(times)
avg_visited = sum(visited) / len(visited)
avg_path = sum(path_lengths) / len(path_lengths)
results.append({
'maze': maze_name,
'algorithm': strategy.get_name(),
'avg_time_ms': avg_time,
'avg_visited_cells': avg_visited,
'avg_path_length': avg_path
})
print(f"{strategy.get_name()}: {avg_time:.2f}ms, {avg_visited:.0f} cells, path={avg_path:.0f}")
with open('experiment_results.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=['maze', 'algorithm', 'avg_time_ms', 'avg_visited_cells', 'avg_path_length'])
writer.writeheader()
writer.writerows(results)
print("\nResults saved to experiment_results.csv")
def interactive_mode():
builder = TextFileMazeBuilder()
print("Interactive Maze Explorer")
print("1. Load maze from file")
print("2. Generate random maze")
choice = input("Choose (1/2): ")
if choice == '1':
filename = input("Enter filename: ")
try:
maze = builder.build_from_file(filename)
except Exception as e:
print(f"Error loading maze: {e}")
return
else:
w = int(input("Width: "))
h = int(input("Height: "))
maze = generate_test_maze(w, h, 0.3)
player = Player(maze.start)
strategies = {
'1': BFSStrategy(),
'2': DFSStrategy(),
'3': AStarStrategy(),
'4': DijkstraStrategy()
}
print("\nSelect algorithm for solving:")
print("1. BFS (shortest path)")
print("2. DFS (fast, not optimal)")
print("3. A* (heuristic)")
print("4. Dijkstra")
algo_choice = input("Choose: ")
solver = MazeSolver(maze, strategies.get(algo_choice, BFSStrategy()))
path, stats = solver.solve()
view = ConsoleView()
if path:
print(f"\nPath found! Length: {len(path)}")
view.show_stats(stats, solver.strategy.get_name())
else:
print("\nNo path found!")
while True:
view.render(maze, player, path if path else None)
if player.current_cell == maze.exit:
print("Congratulations! You reached the exit!")
break
cmd = input("Move (W/A/S/D) | U=undo | Q=quit | S=solve: ").upper()
if cmd == 'Q':
break
elif cmd == 'U':
player.undo()
print("Undo last move")
elif cmd == 'S' and path:
for cell in path:
if cell == player.current_cell:
continue
player.move_to(cell)
view.render(maze, player, path)
input("Press Enter to continue...")
if player.current_cell == maze.exit:
print("You reached the exit!")
break
elif cmd in ['W', 'A', 'S', 'D']:
move_cmd = MoveCommand(player, maze, cmd)
if move_cmd.execute():
print("Moved")
else:
print("Can't move there!")
def main():
print("Maze Solver with Design Patterns")
print("1. Run experiments")
print("2. Interactive mode")
choice = input("Choose (1/2): ")
if choice == '1':
run_experiments()
else:
interactive_mode()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,21 @@
maze,algorithm,avg_time_ms,avg_visited_cells,avg_path_length
Small (10x10),BFS,0.006740167737007141,80.0,0.0
Small (10x10),DFS,0.00408003106713295,80.0,0.0
Small (10x10),A*,0.005039852112531662,80.0,0.0
Small (10x10),Dijkstra,0.0031800009310245514,80.0,0.0
Medium (50x50),BFS,3.44578018411994,1890.0,99.0
Medium (50x50),DFS,1.3188599608838558,1890.0,341.0
Medium (50x50),A*,2.061920054256916,1890.0,99.0
Medium (50x50),Dijkstra,4.679400008171797,1890.0,99.0
Large (100x100),BFS,0.025319866836071014,6998.0,0.0
Large (100x100),DFS,0.019940081983804703,6998.0,0.0
Large (100x100),A*,0.035060010850429535,6998.0,0.0
Large (100x100),Dijkstra,0.02901991829276085,6998.0,0.0
Empty (30x30),BFS,1.2404202483594418,900.0,59.0
Empty (30x30),DFS,0.8887200616300106,900.0,465.0
Empty (30x30),A*,0.9468601085245609,900.0,59.0
Empty (30x30),Dijkstra,2.678940072655678,900.0,59.0
No Exit (20x20),BFS,0.27012014761567116,380.0,0.0
No Exit (20x20),DFS,0.3163599409162998,380.0,0.0
No Exit (20x20),A*,0.5885399878025055,380.0,0.0
No Exit (20x20),Dijkstra,0.5776201374828815,380.0,0.0
1 maze algorithm avg_time_ms avg_visited_cells avg_path_length
2 Small (10x10) BFS 0.006740167737007141 80.0 0.0
3 Small (10x10) DFS 0.00408003106713295 80.0 0.0
4 Small (10x10) A* 0.005039852112531662 80.0 0.0
5 Small (10x10) Dijkstra 0.0031800009310245514 80.0 0.0
6 Medium (50x50) BFS 3.44578018411994 1890.0 99.0
7 Medium (50x50) DFS 1.3188599608838558 1890.0 341.0
8 Medium (50x50) A* 2.061920054256916 1890.0 99.0
9 Medium (50x50) Dijkstra 4.679400008171797 1890.0 99.0
10 Large (100x100) BFS 0.025319866836071014 6998.0 0.0
11 Large (100x100) DFS 0.019940081983804703 6998.0 0.0
12 Large (100x100) A* 0.035060010850429535 6998.0 0.0
13 Large (100x100) Dijkstra 0.02901991829276085 6998.0 0.0
14 Empty (30x30) BFS 1.2404202483594418 900.0 59.0
15 Empty (30x30) DFS 0.8887200616300106 900.0 465.0
16 Empty (30x30) A* 0.9468601085245609 900.0 59.0
17 Empty (30x30) Dijkstra 2.678940072655678 900.0 59.0
18 No Exit (20x20) BFS 0.27012014761567116 380.0 0.0
19 No Exit (20x20) DFS 0.3163599409162998 380.0 0.0
20 No Exit (20x20) A* 0.5885399878025055 380.0 0.0
21 No Exit (20x20) Dijkstra 0.5776201374828815 380.0 0.0

View File

@ -0,0 +1,196 @@
Методы программирования
Поиск выхода из лабиринта.
Анализ 2 задания
Бобров К. Н.
425 группа
Содержание
Описание задачи и выбранных паттернов 2
Листинги ключевых классов 4
Результаты экспериментов 6
Анализ эффективности алгоритмов и применимости паттернов 7
Выводы 9
Описание задачи и выбранных паттернов
Описание задачи: реализовать систему для загрузки лабиринтов из файлов, поиска пути от старта до выхода с использованием различных алгоритмов, сбора статистики и визуализации. Ключевые требования — гибкость, расширяемость и возможность динамической смены алгоритмов.
Выбранные паттерны:
?Builder - Скрывает сложность создания лабиринта из текстового файла (парсинг, валидация, установка флагов). Позволяет легко добавить поддержку других форматов (JSON, XML).
?Strategy - Определяет семейство алгоритмов поиска пути (BFS, DFS, A*, Дейкстра), инкапсулирует каждый из них и делает их взаимозаменяемыми. Клиент (MazeSolver) может переключать стратегии во время выполнения.
?Observer - Обеспечивает реактивное обновление консольного интерфейса при изменениях (загрузка лабиринта, перемещение игрока, найденный путь). Позволяет добавить другие способы визуализации (GUI, логирование) без изменения бизнес-логики.
?Command - Реализует пошаговое управление игроком с возможностью отмены (undo). Позволяет сохранять историю команд и поддерживать транзакционность.
Листинги ключевых классов
Builder (TextFileMazeBuilder):
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename: str) -> Maze:
with open(filename, 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n') for line in f.readlines()]
height = len(lines)
width = max(len(line) for line in lines) if height > 0 else 0
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
cell = maze.get_cell(x, y)
if cell is None:
continue
if ch == '#':
cell.is_wall = True
elif ch == 'S':
cell.is_start = True
maze.start = cell
elif ch == 'E':
cell.is_exit = True
maze.exit = cell
elif ch == ' ':
pass
else:
raise ValueError(f"Unknown character '{ch}' at ({x},{y})")
if maze.start is None or maze.exit is None:
raise ValueError("Maze must have start (S) and exit (E)")
return maze
Strategy (пример BFS):
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
queue = deque([start])
came_from = {start: None}
while queue:
current = queue.popleft()
if current == exit:
break
for nb in maze.get_neighbors(current):
if nb not in came_from:
came_from[nb] = current
queue.append(nb)
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "BFS"
Observer (ConsoleView):
class ConsoleView:
@staticmethod
def render(maze: Maze, player: Optional[Player] = None, path: Optional[List[Cell]] = None):
path_set = set()
if path:
path_set = set(path)
for y in range(maze.height):
line = ""
for x in range(maze.width):
cell = maze.get_cell(x, y)
if not cell:
line += " "
elif player and player.current_cell == cell:
line += "P"
elif cell.is_start:
line += "S"
elif cell.is_exit:
line += "E"
elif cell.is_wall:
line += "#"
elif path and cell in path_set:
line += "."
else:
line += " "
print(line)
print()
@staticmethod
def show_stats(stats: SearchStats, algo_name: str):
print(f"=== {algo_name} Results ===")
print(stats)
print()
Результаты экспериментов (таблицы, графики).
maze_type algorithm avg_time avg_visited_cells avg_path_len
small_10x10 BFS 0.08572000006097369 79.0 19.0
small_10x10 DFS 0.039739999920129776 79.0 31.0
small_10x10_ A* 0.13467999997374136 79.0 19.0
small_10x10 Dijkstra 0.11474000057205558 79.0 19.0
medium_50x50 BFS 1.8074600004183594 1874.0 99.0
medium_50x50 DFS 0.5937599995377241 1874.0 429.0
medium_50x50 A* 1.6300600003887666 1874.0 99.0
medium_50x50 Dijkstra 3.1870400001935195 1874.0 99.0
large_100x100 BFS 0.014439999722526409 7033.0 0.0
large_100x100 DFS 0.014839999857940711 7033.0 0.0
large_100x100 A* 0.02542000001994893 7033.0 0.0
large_100x100 Dijkstra 0.02548000011302065 7033.0 0.0
empty_30x30 BFS 0.784620000194991 900.0 59.0
empty_30x30 DFS 0.5252399994787993 900.0 465.
empty_30x30 A* 1.150900000357069 900.0 59.0
empty_30x30 Dijkstra 1.564640000287909 900.0 59.0
no_exit_20x20 BFS 0.2002399993216386 380. 0.0
no_exit_20x20 DFS 0.2512400002160575 380.0 0.0
no_exit_20x20 A* 0.5590400000073714 380.0 0.
no_exit_20x20 Dijkstra 0.35640000060084276 380.0 0.0
Графики построены кодом из файла RESULT22.
Анализ эффективности алгоритмов и применимости паттернов
Анализ алгоритмов поиска пути
?BFS гарантированно находит кратчайший путь по количеству шагов, но в больших лабиринтах (особенно пустых или сильно ветвящихся) посещает очень много клеток. Время работы растёт пропорционально числу достижимых клеток.
?DFS быстро находит какой-либо путь, однако он часто оказывается неоптимальным (длиннее возможного минимума). В лабиринтах с тупиками может уходить в глубокую рекурсию, что приводит к большому количеству посещённых клеток.
?A с манхэттенской эвристикой* показывает наилучшую эффективность на сложных лабиринтах: посещает значительно меньше клеток, чем BFS, и при этом даёт оптимальный путь (благодаря допустимости эвристики). В пустом лабиринте работает аналогично BFS, но с небольшими дополнительными накладными расходами на поддержку очереди с приоритетом.
?Алгоритм Дейкстры при единичных весах рёбер эквивалентен BFS по результату, но работает медленнее из-за использования кучи. Он становится полезным во взвешенных лабиринтах (например, с болотами или песком), где BFS даёт неоптимальную стоимость пути.
Применимость паттернов проектирования
?Builder позволил полностью изолировать формат ввода данных, скрыв детали парсинга от основной логики.
?Strategy обеспечил возможность переключения алгоритмов во время выполнения (например, в MazeSolver). Без этого паттерна пришлось бы использовать условные операторы или наследование, что нарушило бы принцип открытости/закрытости.
?Observer отделил визуализацию от бизнес-логики. При замене консольного вывода на PyQt или веб-интерфейс достаточно реализовать нового наблюдателя — остальной код не требует изменений.
?Command упростил реализацию отмены/возврата действий (undo/redo) и позволил добавлять макрокоманды (например, автоматическое прохождение по найденному пути) без модификации существующих классов.
Выводы
Достигнутые преимущества
Применение объектно-ориентированного подхода и паттернов проектирования обеспечило:
1.Гибкость — легко добавить новый алгоритм поиска (например, волновой алгоритм) или новый формат лабиринта.
2.Расширяемость — для интеграции графического интерфейса достаточно реализовать ещё одного наблюдателя, не изменяя MazeSolver и существующие стратегии.
3.Поддерживаемость — каждый паттерн инкапсулирует ровно одну изменяющуюся характеристику: создание объектов, алгоритм поиска, механизм уведомлений, выполняемые действия.
4.Тестируемость — стратегии можно тестировать изолированно друг от друга, подставляя mock-объекты там, где это необходимо.
Что потребовало бы больших усилий без паттернов
?Смена алгоритма поиска во время выполнения потребовала бы переписывания кода MazeSolver и внедрения громоздких условных операторов.
?Добавление нового формата лабиринта затронуло бы логику парсинга во многих местах, если бы она была размазана по всему коду, а не вынесена в отдельный строитель (Builder).
?Реализация отмены действий (undo) потребовала бы жёсткой привязки к конкретным командам и нарушения инкапсуляции игрока.
?Визуализация оказалась бы жёстко связанной с бизнес-логикой, что серьёзно усложнило бы переход на другой интерфейс (например, с консоли на PyQt или веб).
Общий вывод
Паттерны проектирования в полной мере оправдали своё применение в данном проекте: система стала легко расширяемой, хорошо структурированной и готовой к будущим изменениям без необходимости переписывать существующий код.

0
PaulVA/429 Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,19 @@
structure,order,operation,run1,run2,run3,run4,run5,average
LinkedList,random,insert,3.000600399999712,3.022712899999533,2.9421689999999217,2.9075659000000087,3.0319512999994913,2.980999899999733
LinkedList,random,find,0.031094500000108383,0.02800200000001496,0.034349299999121286,0.029372199999670556,0.03242119999958959,0.031047839999700955
LinkedList,random,delete,0.017322699999567703,0.0368361000000732,0.04029200000059063,0.03775789999963308,0.03554420000000391,0.033550579999973705
HashTable,random,insert,0.011551699999472476,0.012756400000398571,0.011765299999751733,0.011679000000185624,0.011983400000644906,0.011947160000090662
HashTable,random,find,0.00012409999999363208,0.00011009999980160501,0.0001415999995515449,0.00010400000064691994,0.00010089999977935804,0.000116139999954612
HashTable,random,delete,6.38999999864609e-05,6.779999966965988e-05,6.0600000324484427e-05,6.070000017643906e-05,6.0600000324484427e-05,6.272000009630574e-05
BST,random,insert,0.014788199999202334,0.014159299999846553,0.013975800000480376,0.014118900000539725,0.013331299999663315,0.01407469999994646
BST,random,find,0.00013829999988956843,0.00011389999963284936,0.00011369999992894009,0.00011379999978089472,0.00011439999980211724,0.00011881999980687397
BST,random,delete,8.690000049682567e-05,6.450000000768341e-05,6.2199999774748e-05,6.209999992279336e-05,6.229999962670263e-05,6.759999996575061e-05
LinkedList,sorted,insert,2.4411346000006233,2.36463619999995,2.2797248999995645,2.2860746000005747,2.2526011999998445,2.3248343000001115
LinkedList,sorted,find,0.024703000000044995,0.02455259999987902,0.02468479999970441,0.02444869999999355,0.02606350000041857,0.02489052000000811
LinkedList,sorted,delete,0.012835599999561964,0.027673999999933585,0.027570299999752024,0.02708100000018021,0.02999909999925876,0.02503199999973731
HashTable,sorted,insert,0.011780100000578386,0.010850699999537028,0.010314100000869075,0.010621500000524975,0.011015500000212342,0.010916380000344362
HashTable,sorted,find,0.0001464000006308197,0.00017980000029638177,0.00016909999976633117,0.00012620000052265823,0.00023630000032426324,0.0001715600003080908
HashTable,sorted,delete,0.00016370000048482325,0.00018089999957737746,0.0001443999999537482,7.579999964946182e-05,6.469999971159268e-05,0.0001258999998754007
BST,sorted,insert,3.5400651999998445,3.5145174999997835,3.5583661999999094,3.5149656000003233,3.481246600000304,3.521832220000033
BST,sorted,find,0.03275260000009439,0.030442500000390282,0.02994349999971746,0.030269500000031258,0.030329999999594293,0.030747619999965538
BST,sorted,delete,0.012705400000413647,0.01333390000036161,0.013192000000344706,0.013699000000087835,0.013079800000014075,0.013202020000244374
1 structure order operation run1 run2 run3 run4 run5 average
2 LinkedList random insert 3.000600399999712 3.022712899999533 2.9421689999999217 2.9075659000000087 3.0319512999994913 2.980999899999733
3 LinkedList random find 0.031094500000108383 0.02800200000001496 0.034349299999121286 0.029372199999670556 0.03242119999958959 0.031047839999700955
4 LinkedList random delete 0.017322699999567703 0.0368361000000732 0.04029200000059063 0.03775789999963308 0.03554420000000391 0.033550579999973705
5 HashTable random insert 0.011551699999472476 0.012756400000398571 0.011765299999751733 0.011679000000185624 0.011983400000644906 0.011947160000090662
6 HashTable random find 0.00012409999999363208 0.00011009999980160501 0.0001415999995515449 0.00010400000064691994 0.00010089999977935804 0.000116139999954612
7 HashTable random delete 6.38999999864609e-05 6.779999966965988e-05 6.0600000324484427e-05 6.070000017643906e-05 6.0600000324484427e-05 6.272000009630574e-05
8 BST random insert 0.014788199999202334 0.014159299999846553 0.013975800000480376 0.014118900000539725 0.013331299999663315 0.01407469999994646
9 BST random find 0.00013829999988956843 0.00011389999963284936 0.00011369999992894009 0.00011379999978089472 0.00011439999980211724 0.00011881999980687397
10 BST random delete 8.690000049682567e-05 6.450000000768341e-05 6.2199999774748e-05 6.209999992279336e-05 6.229999962670263e-05 6.759999996575061e-05
11 LinkedList sorted insert 2.4411346000006233 2.36463619999995 2.2797248999995645 2.2860746000005747 2.2526011999998445 2.3248343000001115
12 LinkedList sorted find 0.024703000000044995 0.02455259999987902 0.02468479999970441 0.02444869999999355 0.02606350000041857 0.02489052000000811
13 LinkedList sorted delete 0.012835599999561964 0.027673999999933585 0.027570299999752024 0.02708100000018021 0.02999909999925876 0.02503199999973731
14 HashTable sorted insert 0.011780100000578386 0.010850699999537028 0.010314100000869075 0.010621500000524975 0.011015500000212342 0.010916380000344362
15 HashTable sorted find 0.0001464000006308197 0.00017980000029638177 0.00016909999976633117 0.00012620000052265823 0.00023630000032426324 0.0001715600003080908
16 HashTable sorted delete 0.00016370000048482325 0.00018089999957737746 0.0001443999999537482 7.579999964946182e-05 6.469999971159268e-05 0.0001258999998754007
17 BST sorted insert 3.5400651999998445 3.5145174999997835 3.5583661999999094 3.5149656000003233 3.481246600000304 3.521832220000033
18 BST sorted find 0.03275260000009439 0.030442500000390282 0.02994349999971746 0.030269500000031258 0.030329999999594293 0.030747619999965538
19 BST sorted delete 0.012705400000413647 0.01333390000036161 0.013192000000344706 0.013699000000087835 0.013079800000014075 0.013202020000244374

View File

@ -0,0 +1,34 @@
Лабораторная работа 1
Цель работы
Нужно было сделать три структуры данных и проверить как они работают на телефонном справочнике.
Ход работы
Сделал связный список хеш таблицу и двоичное дерево поиска. Для всех структур сделал добавление поиск удаление и вывод записей. Для проверки создал 10000 записей с именами User\_00000 и т.д. Потом проверил работу со случайным порядком и с отсортированным порядком. Каждый эксперимент повторял 5 раз.
Результаты
Результаты сохранились в results.csv. Также сделал графики для добавления поиска и удаления. По результатам видно что связный список медленно ищет записи потому что нужно идти по элементам. Хеш таблица работает примерно одинаково при разном порядке записей. У двоичного дерева порядок записей влияет намного сильнее. Если добавлять записи по порядку дерево становится похожим на обычный список и работает медленнее.
Вывод
В работе я сделал три структуры данных и проверил их работу. Самой удобной для телефонного справочника получилась хеш таблица. Связный список проще но поиск медленный. Двоичное дерево может работать быстро но сильно зависит от порядка добавления данных.

185
PaulVA/lab1/experiments.py Normal file
View File

@ -0,0 +1,185 @@
import random
import time
import csv
import os
from phonebook import *
N = 10000
REPEATS = 5
def generate_test_data():
records = [
(f"User_{i:05d}", f"+7900000{i:04d}")
for i in range(N)
]
records_shuffled = records.copy()
random.shuffle(records_shuffled)
records_sorted = records.copy()
return records_shuffled, records_sorted
def measure_experiment(insert_function, find_function, delete_function, records):
insert_times = []
find_times = []
delete_times = []
for _ in range(REPEATS):
structure = None
start = time.perf_counter()
for name, phone in records:
structure = insert_function(structure, name, phone)
insert_times.append(time.perf_counter() - start)
structure_for_find = structure
names = [name for name, phone in records]
search_names = random.sample(names, 100) + [
"NotFound_001",
"NotFound_002",
"NotFound_003",
"NotFound_004",
"NotFound_005",
"NotFound_006",
"NotFound_007",
"NotFound_008",
"NotFound_009",
"NotFound_010"
]
for _ in range(REPEATS):
start = time.perf_counter()
for name in search_names:
find_function(structure_for_find, name)
find_times.append(time.perf_counter() - start)
delete_names = random.sample(names, 50)
for _ in range(REPEATS):
structure = structure_for_find
start = time.perf_counter()
for name in delete_names:
structure = delete_function(structure, name)
delete_times.append(time.perf_counter() - start)
return insert_times, find_times, delete_times
def measure_hash(records):
insert_times = []
find_times = []
delete_times = []
names = [name for name, phone in records]
search_names = random.sample(names, 100) + [
f"NotFound_{i:03d}" for i in range(10)
]
delete_names = random.sample(names, 50)
for _ in range(REPEATS):
buckets = ht_create()
start = time.perf_counter()
for name, phone in records:
ht_insert(buckets, name, phone)
insert_times.append(time.perf_counter() - start)
structure_for_find = buckets
for _ in range(REPEATS):
start = time.perf_counter()
for name in search_names:
ht_find(structure_for_find, name)
find_times.append(time.perf_counter() - start)
for _ in range(REPEATS):
buckets = structure_for_find.copy()
start = time.perf_counter()
for name in delete_names:
ht_delete(buckets, name)
delete_times.append(time.perf_counter() - start)
return insert_times, find_times, delete_times
def average(values):
return sum(values) / len(values)
def run():
records_shuffled, records_sorted = generate_test_data()
results = []
for order_name, records in [
("random", records_shuffled),
("sorted", records_sorted)
]:
print("Order:", order_name)
ll = measure_experiment(
ll_insert,
ll_find,
ll_delete,
records
)
results.append(["LinkedList", order_name, "insert", *ll[0]])
results.append(["LinkedList", order_name, "find", *ll[1]])
results.append(["LinkedList", order_name, "delete", *ll[2]])
ht = measure_hash(records)
results.append(["HashTable", order_name, "insert", *ht[0]])
results.append(["HashTable", order_name, "find", *ht[1]])
results.append(["HashTable", order_name, "delete", *ht[2]])
bst = measure_experiment(
bst_insert,
bst_find,
bst_delete,
records
)
results.append(["BST", order_name, "insert", *bst[0]])
results.append(["BST", order_name, "find", *bst[1]])
results.append(["BST", order_name, "delete", *bst[2]])
os.makedirs("docs/data", exist_ok=True)
with open("docs/data/results.csv", "w", newline="", encoding="utf-8") as file:
writer = csv.writer(file)
writer.writerow([
"structure",
"order",
"operation",
"run1",
"run2",
"run3",
"run4",
"run5",
"average"
])
for row in results:
writer.writerow(row + [average(row[3:])])
print("Results saved to docs/data/results.csv")
if __name__ == "__main__":
run()

56
PaulVA/lab1/graphs.py Normal file
View File

@ -0,0 +1,56 @@
import csv
import os
import matplotlib.pyplot as plt
data = []
with open("docs/data/results.csv", "r", encoding="utf-8") as file:
reader = csv.DictReader(file)
for row in reader:
data.append(row)
def get_average(structure, order, operation):
for row in data:
if (
row["structure"] == structure
and row["order"] == order
and row["operation"] == operation
):
return float(row["average"])
return 0
structures = ["LinkedList", "HashTable", "BST"]
orders = ["random", "sorted"]
os.makedirs("docs/data", exist_ok=True)
for operation in ["insert", "find", "delete"]:
random_values = [
get_average(s, "random", operation)
for s in structures
]
sorted_values = [
get_average(s, "sorted", operation)
for s in structures
]
x = range(len(structures))
plt.figure()
plt.bar([i - 0.2 for i in x], random_values, width=0.4, label="random")
plt.bar([i + 0.2 for i in x], sorted_values, width=0.4, label="sorted")
plt.xticks(list(x), structures)
plt.ylabel("Time, seconds")
plt.title(operation.capitalize() + " time")
plt.yscale("log")
plt.legend()
plt.tight_layout()
plt.savefig("docs/data/graph_" + operation + ".png")
plt.close()
print("Graphs saved to docs/data/")

211
PaulVA/lab1/phonebook.py Normal file
View File

@ -0,0 +1,211 @@
def ll_insert(head, name, phone):
new_node = {
'name': name,
'phone': phone,
'next': None
}
if head is None:
return new_node
current = head
while current['next'] is not None:
if current['name'] == name:
current['phone'] = phone
return head
current = current['next']
if current['name'] == name:
current['phone'] = phone
else:
current['next'] = new_node
return head
def ll_find(head, name):
current = head
while current is not None:
if current['name'] == name:
return current['phone']
current = current['next']
return None
def ll_delete(head, name):
if head is None:
return None
if head['name'] == name:
return head['next']
current = head
while current['next'] is not None:
if current['next']['name'] == name:
current['next'] = current['next']['next']
return head
current = current['next']
return head
def ll_list_all(head):
records = []
current = head
while current is not None:
records.append((current['name'], current['phone']))
current = current['next']
records.sort(key=lambda x: x[0])
return records
def hash_function(name, table_size):
total = 0
for ch in name:
total = (total * 31 + ord(ch)) % table_size
return total
def ht_create(size=1000):
return [None] * size
def ht_insert(buckets, name, phone):
index = hash_function(name, len(buckets))
buckets[index] = ll_insert(buckets[index], name, phone)
return buckets
def ht_find(buckets, name):
index = hash_function(name, len(buckets))
return ll_find(buckets[index], name)
def ht_delete(buckets, name):
index = hash_function(name, len(buckets))
buckets[index] = ll_delete(buckets[index], name)
return buckets
def ht_list_all(buckets):
records = []
for bucket in buckets:
current = bucket
while current is not None:
records.append((current['name'], current['phone']))
current = current['next']
records.sort(key=lambda x: x[0])
return records
def bst_insert(root, name, phone):
new_node = {
'name': name,
'phone': phone,
'left': None,
'right': None
}
if root is None:
return new_node
current = root
while True:
if name < current['name']:
if current['left'] is None:
current['left'] = new_node
break
current = current['left']
elif name > current['name']:
if current['right'] is None:
current['right'] = new_node
break
current = current['right']
else:
current['phone'] = phone
break
return root
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):
parent = None
current = root
while current is not None and current['name'] != name:
parent = current
if name < current['name']:
current = current['left']
else:
current = current['right']
if current is None:
return root
if current['left'] is None:
child = current['right']
elif current['right'] is None:
child = current['left']
else:
successor_parent = current
successor = current['right']
while successor['left'] is not None:
successor_parent = successor
successor = successor['left']
current['name'] = successor['name']
current['phone'] = successor['phone']
if successor_parent['left'] == successor:
successor_parent['left'] = successor['right']
else:
successor_parent['right'] = successor['right']
return root
if parent is None:
return child
if parent['left'] == current:
parent['left'] = child
else:
parent['right'] = child
return root
def bst_list_all(root):
records = []
def inorder(node):
if node is None:
return
inorder(node['left'])
records.append((node['name'], node['phone']))
inorder(node['right'])
inorder(root)
return records

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,16 @@
maze,strategy,time_ms,visited_cells,path_length
simple.txt,BFS,0.01464000015403144,11.0,6.0
simple.txt,DFS,0.010180000390391797,9.0,8.0
simple.txt,A*,0.017740000475896522,9.0,6.0
dead.txt,BFS,0.3642999996372964,307.0,35.0
dead.txt,DFS,0.23493999906349927,279.0,151.0
dead.txt,A*,0.38374000068870373,235.0,35.0
large.txt,BFS,23.894459999428364,6812.0,2329.0
large.txt,DFS,84.77875999960816,6796.0,4537.0
large.txt,A*,28.69542000044021,6791.0,2329.0
empty.txt,BFS,1.2770400004228577,1176.0,48.0
empty.txt,DFS,7.602279999264283,2304.0,1176.0
empty.txt,A*,0.10093999881064519,48.0,48.0
noexit.txt,BFS,0.003699999797390774,1.0,0.0
noexit.txt,DFS,0.0032000003557186574,1.0,0.0
noexit.txt,A*,0.004120000085094944,1.0,0.0
1 maze strategy time_ms visited_cells path_length
2 simple.txt BFS 0.01464000015403144 11.0 6.0
3 simple.txt DFS 0.010180000390391797 9.0 8.0
4 simple.txt A* 0.017740000475896522 9.0 6.0
5 dead.txt BFS 0.3642999996372964 307.0 35.0
6 dead.txt DFS 0.23493999906349927 279.0 151.0
7 dead.txt A* 0.38374000068870373 235.0 35.0
8 large.txt BFS 23.894459999428364 6812.0 2329.0
9 large.txt DFS 84.77875999960816 6796.0 4537.0
10 large.txt A* 28.69542000044021 6791.0 2329.0
11 empty.txt BFS 1.2770400004228577 1176.0 48.0
12 empty.txt DFS 7.602279999264283 2304.0 1176.0
13 empty.txt A* 0.10093999881064519 48.0 48.0
14 noexit.txt BFS 0.003699999797390774 1.0 0.0
15 noexit.txt DFS 0.0032000003557186574 1.0 0.0
16 noexit.txt A* 0.004120000085094944 1.0 0.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

212
PaulVA/lab2/docs/report.md Normal file
View File

@ -0,0 +1,212 @@
Лабораторная работа 2
Поиск выхода из лабиринта
Цель работы
-----------
Цель работы состоит в реализации программы для поиска выхода из лабиринта с использованием объектно ориентированного подхода и паттернов проектирования
В программе реализована загрузка лабиринта из файла несколько алгоритмов поиска и сравнение их работы
Структура программы
-------------------
В программе используются классы Cell для отдельной клетки лабиринта и Maze для самого лабиринта
Для загрузки используется MazeBuilder и его реализация TextFileMazeBuilder
Для поиска пути используется общий класс PathFindingStrategy и три алгоритма BFSStrategy DFSStrategy и AStarStrategy
За хранение результатов отвечает SearchStats а запуск поиска выполняет MazeSolver
Для вывода информации используются Observer и ConsoleView
Использованные паттерны
-----------------------
В работе использованы три паттерна Builder Strategy и Observer
Builder используется для загрузки лабиринта из текстового файла
TextFileMazeBuilder читает файл и создаёт объект Maze
В файле символ # обозначает стену пробел обозначает свободную клетку S является началом а E выходом
Использование Builder позволяет отдельно реализовать загрузку лабиринта и сам класс лабиринта
Strategy используется для выбора алгоритма поиска
В программе реализованы BFS DFS и A*
Все алгоритмы имеют общий интерфейс PathFindingStrategy поэтому в MazeSolver можно менять алгоритм без изменения самого решателя
Observer используется для вывода информации о поиске
MazeSolver отправляет события а ConsoleView получает их и выводит информацию в консоль
Таким образом вывод отделён от основной логики поиска
Алгоритмы поиска
----------------
BFS использует очередь и при обычных условиях находит кратчайший путь в лабиринте без весов
DFS использует стек и может найти путь быстрее но найденный путь не обязательно будет кратчайшим
A* использует очередь с приоритетом и манхэттенскую эвристику поэтому старается в первую очередь проверять клетки которые находятся ближе к выходу
Схема классов
-------------
classDiagram
class Cell {
x
y
is_wall
is_start
is_exit
is_passable()
}
class Maze {
width
height
cells
start
exit
get_cell()
get_neighbors()
}
class MazeBuilder {
build_from_file()
}
class TextFileMazeBuilder {
build_from_file()
}
class PathFindingStrategy {
find_path()
}
class BFSStrategy {
find_path()
}
class DFSStrategy {
find_path()
}
class AStarStrategy {
find_path()
}
class SearchStats {
path
time_ms
visited_count
path_length
}
class MazeSolver {
maze
strategy
set_strategy()
solve()
}
class Observer {
update()
}
class ConsoleView {
update()
}
MazeBuilder <|-- TextFileMazeBuilder
PathFindingStrategy <|-- BFSStrategy
PathFindingStrategy <|-- DFSStrategy
PathFindingStrategy <|-- AStarStrategy
Observer <|-- ConsoleView
MazeSolver --> Maze
MazeSolver --> PathFindingStrategy
MazeSolver --> Observer
Maze --> Cell
Тестирование
------------
Для проверки использовалось пять разных лабиринтов
simple.txt представляет простой лабиринт dead.txt содержит тупики large.txt является большим запутанным лабиринтом empty.txt не содержит стен а в noexit.txt выход недостижим
Каждый алгоритм запускался пять раз
Во время эксперимента измерялось время поиска количество посещённых клеток и длина найденного пути
Результаты сохранялись в файл results.csv
Результаты
----------
simple.txt
Алгоритм Время мс Посещено Путь
BFS 0.01464 11 6
DFS 0.01018 9 8
A* 0.01774 9 6
Все алгоритмы работают быстро
BFS и A* нашли более короткий путь чем DFS
dead.txt
Алгоритм Время мс Посещено Путь
BFS 0.36430 307 35
DFS 0.23494 279 151
A* 0.38374 235 35
DFS работал немного быстрее но нашёл более длинный путь
BFS и A* нашли короткий путь
large.txt
Алгоритм Время мс Посещено Путь
BFS 23.89446 6812 2329
DFS 84.77876 6796 4537
A* 28.69542 6791 2329
На большом лабиринте DFS показал худшее время и самый длинный путь
BFS и A* нашли одинаковый путь
empty.txt
Алгоритм Время мс Посещено Путь
BFS 1.277
04 1176 48
DFS 7.60228 2304 1176
A* 0.10094 48 48
В лабиринте без стен лучше всего показал себя A*
Он посетил меньше всего клеток и работал быстрее
noexit.txt
Алгоритм Время мс Посещено Путь
BFS 0.00370 1 0
DFS 0.00320 1 0
A* 0.00412 1 0
В этом лабиринте выход недостижим поэтому все алгоритмы быстро закончили поиск
Графики
-------
Для сравнения времени работы были построены графики для каждого лабиринта
Графики находятся в папке docs/data
simple_time.png dead_time.png large_time.png empty_time.png и noexit_time.png
Вывод
-----
В работе была создана программа для поиска выхода из лабиринта
Были реализованы BFS DFS и A* а также использованы паттерны Builder Strategy и Observer
По результатам эксперимента BFS хорошо подходит для поиска кратчайшего пути DFS может найти путь быстрее но он не всегда получается коротким A* хорошо показывает себя на больших и открытых лабиринтах

View File

@ -0,0 +1,93 @@
import csv
import os
from maze_solver import (
TextFileMazeBuilder,
MazeSolver,
BFSStrategy,
DFSStrategy,
AStarStrategy
)
REPEATS = 5
MAZES = [
"simple.txt",
"dead.txt",
"large.txt",
"empty.txt",
"noexit.txt"
]
STRATEGIES = [
("BFS", BFSStrategy()),
("DFS", DFSStrategy()),
("A*", AStarStrategy())
]
def average(values):
return sum(values) / len(values)
def run():
builder = TextFileMazeBuilder()
results = []
for maze_name in MAZES:
filename = os.path.join("lab2", "mazes", maze_name)
print("Maze:", maze_name)
for strategy_name, strategy in STRATEGIES:
times = []
visited = []
path_lengths = []
for _ in range(REPEATS):
maze = builder.build_from_file(filename)
solver = MazeSolver(maze, strategy)
stats = solver.solve()
times.append(stats.time_ms)
visited.append(stats.visited_count)
path_lengths.append(stats.path_length)
results.append([
maze_name,
strategy_name,
average(times),
average(visited),
average(path_lengths)
])
print(
strategy_name,
"time =", average(times),
"visited =", average(visited),
"path =", average(path_lengths)
)
os.makedirs("lab2/docs/data", exist_ok=True)
with open(
"lab2/docs/data/results.csv",
"w",
newline="",
encoding="utf-8"
) as file:
writer = csv.writer(file)
writer.writerow([
"maze",
"strategy",
"time_ms",
"visited_cells",
"path_length"
])
writer.writerows(results)
print("Results saved to lab2/docs/data/results.csv")
if __name__ == "__main__":
run()

23
PaulVA/lab2/graphs.py Normal file
View File

@ -0,0 +1,23 @@
import csv
import matplotlib.pyplot as plt
with open("lab2/docs/data/results.csv", encoding="utf-8") as file:
rows = list(csv.DictReader(file))
mazes = ["simple.txt", "dead.txt", "large.txt", "empty.txt", "noexit.txt"]
strategies = ["BFS", "DFS", "A*"]
for maze in mazes:
values = []
for strategy in strategies:
for row in rows:
if row["maze"] == maze and row["strategy"] == strategy:
values.append(float(row["time_ms"]))
plt.bar(strategies, values)
plt.title("Время поиска: " + maze)
plt.xlabel("Стратегия")
plt.ylabel("Время, мс")
plt.savefig("lab2/docs/data/" + maze.replace(".txt", "_time.png"))
plt.close()

26
PaulVA/lab2/make_large.py Normal file
View File

@ -0,0 +1,26 @@
lines = []
for y in range(100):
row = [" "] * 100
if y == 0 or y == 99:
row = ["#"] * 100
else:
row[0] = "#"
row[99] = "#"
lines.append(row)
lines[1][1] = "S"
lines[98][98] = "E"
for x in range(4, 96, 4):
gap = 1 if (x // 4) % 2 == 0 else 98
for y in range(1, 99):
if y != gap:
lines[y][x] = "#"
with open("lab2/mazes/large.txt", "w", encoding="utf-8") as file:
for row in lines:
file.write("".join(row) + "\n")

284
PaulVA/lab2/maze_solver.py Normal file
View File

@ -0,0 +1,284 @@
from abc import ABC, abstractmethod
from collections import deque
import heapq
import time
class Cell:
def __init__(self, x, y):
self.x = x
self.y = y
self.is_wall = False
self.is_start = False
self.is_exit = False
def is_passable(self):
return not self.is_wall
class Maze:
def __init__(self, width, height):
self.width = width
self.height = height
self.cells = []
self.start = None
self.exit = None
for y in range(height):
row = []
for x in range(width):
row.append(Cell(x, y))
self.cells.append(row)
def get_cell(self, x, y):
if 0 <= x < self.width and 0 <= y < self.height:
return self.cells[y][x]
return None
def get_neighbors(self, cell):
neighbors = []
directions = [
(0, -1),
(0, 1),
(-1, 0),
(1, 0)
]
for dx, dy in directions:
neighbor = self.get_cell(
cell.x + dx,
cell.y + dy
)
if neighbor and neighbor.is_passable():
neighbors.append(neighbor)
return neighbors
class MazeBuilder(ABC):
@abstractmethod
def build_from_file(self, filename):
pass
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename):
with open(filename, "r", encoding="utf-8") as file:
lines = [line.rstrip("\n") for line in file]
if not lines:
raise ValueError("Файл лабиринта пустой")
width = len(lines[0])
for line in lines:
if len(line) != width:
raise ValueError("Строки лабиринта имеют разную длину")
maze = Maze(width, len(lines))
for y, line in enumerate(lines):
for x, symbol in enumerate(line):
cell = maze.get_cell(x, y)
if symbol == "#":
cell.is_wall = True
elif symbol == "S":
if maze.start is not None:
raise ValueError("В лабиринте несколько стартов")
maze.start = cell
cell.is_start = True
elif symbol == "E":
if maze.exit is not None:
raise ValueError("В лабиринте несколько выходов")
maze.exit = cell
cell.is_exit = True
elif symbol == " ":
pass
else:
raise ValueError("Неизвестный символ в лабиринте")
if maze.start is None:
raise ValueError("В лабиринте нет старта")
if maze.exit is None:
raise ValueError("В лабиринте нет выхода")
return maze
class PathFindingStrategy(ABC):
@abstractmethod
def find_path(self, maze, start, exit):
pass
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit):
if start is None or exit is None:
return [], 0
queue = deque([(start, [start])])
visited = {start}
while queue:
current, path = queue.popleft()
if current == exit:
return path, len(visited)
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, path + [neighbor]))
return [], len(visited)
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit):
if start is None or exit is None:
return [], 0
stack = [(start, [start])]
visited = {start}
while stack:
current, path = stack.pop()
if current == exit:
return path, len(visited)
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
stack.append((neighbor, path + [neighbor]))
return [], len(visited)
class AStarStrategy(PathFindingStrategy):
def heuristic(self, a, b):
return abs(a.x - b.x) + abs(a.y - b.y)
def find_path(self, maze, start, exit):
if start is None or exit is None:
return [], 0
heap = []
counter = 0
heapq.heappush(
heap,
(self.heuristic(start, exit), counter, start, [start])
)
g_score = {start: 0}
visited = set()
while heap:
_, _, current, path = heapq.heappop(heap)
if current in visited:
continue
visited.add(current)
if current == exit:
return path, len(visited)
for neighbor in maze.get_neighbors(current):
new_cost = g_score[current] + 1
if neighbor not in g_score or new_cost < g_score[neighbor]:
g_score[neighbor] = new_cost
counter += 1
priority = new_cost + self.heuristic(
neighbor,
exit
)
heapq.heappush(
heap,
(
priority,
counter,
neighbor,
path + [neighbor]
)
)
return [], len(visited)
class SearchStats:
def __init__(self, path, time_ms, visited_count):
self.path = path
self.time_ms = time_ms
self.visited_count = visited_count
self.path_length = len(path) if path else 0
class MazeSolver:
def __init__(self, maze, strategy=None):
self.maze = maze
self.strategy = strategy
self.observers = []
def attach(self, observer):
self.observers.append(observer)
def detach(self, observer):
self.observers.remove(observer)
def notify(self, event, data=None):
for observer in self.observers:
observer.update(event, data)
def set_strategy(self, strategy):
self.strategy = strategy
def solve(self):
if self.strategy is None:
raise ValueError("Стратегия не установлена")
self.notify("search_started")
start_time = time.perf_counter()
path, visited_count = self.strategy.find_path(
self.maze,
self.maze.start,
self.maze.exit
)
end_time = time.perf_counter()
time_ms = (end_time - start_time) * 1000
self.notify("search_finished", time_ms)
self.notify("path_found", path)
return SearchStats(
path,
time_ms,
visited_count
)
class Observer(ABC):
@abstractmethod
def update(self, event, data=None):
pass
class ConsoleView(Observer):
def update(self, event, data=None):
if event == "search_started":
print("Поиск начат")
elif event == "search_finished":
print(f"Поиск завершен за {data:.3f} мс")
elif event == "path_found":
print(f"Длина пути: {len(data)}")

View File

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

View File

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

100
PaulVA/lab2/mazes/large.txt Normal file
View File

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

View File

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

View File

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

View File

@ -0,0 +1,589 @@
import time
import heapq
from collections import deque
from typing import List, Optional, Dict, Tuple
from abc import ABC, abstractmethod
import csv
import random
class Cell:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
self.is_wall = False
self.is_start = False
self.is_exit = False
def is_passable(self) -> bool:
return not self.is_wall
class Maze:
def __init__(self, width: int, height: int):
self.width = width
self.height = height
self.cells = [[Cell(x, y) for y in range(height)] for x in range(width)]
self.start: Optional[Cell] = None
self.exit: Optional[Cell] = None
def get_cell(self, x: int, y: int) -> Optional[Cell]:
if 0 <= x < self.width and 0 <= y < self.height:
return self.cells[x][y]
return None
def get_neighbors(self, cell: Cell) -> List[Cell]:
neighbors = []
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
nx, ny = cell.x + dx, cell.y + dy
nb = self.get_cell(nx, ny)
if nb and nb.is_passable():
neighbors.append(nb)
return neighbors
class MazeBuilder(ABC):
@abstractmethod
def build_from_file(self, filename: str) -> Maze:
pass
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename: str) -> Maze:
with open(filename, 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n') for line in f.readlines()]
height = len(lines)
width = max(len(line) for line in lines) if height > 0 else 0
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
cell = maze.get_cell(x, y)
if cell is None:
continue
if ch == '#':
cell.is_wall = True
elif ch == 'S':
cell.is_start = True
maze.start = cell
elif ch == 'E':
cell.is_exit = True
maze.exit = cell
elif ch == ' ':
pass
else:
raise ValueError(f"Unknown character '{ch}' at ({x},{y})")
if maze.start is None or maze.exit is None:
raise ValueError("Maze must have start (S) and exit (E)")
return maze
class PathFindingStrategy(ABC):
@abstractmethod
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
pass
@abstractmethod
def get_name(self) -> str:
pass
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
queue = deque([start])
came_from = {start: None}
while queue:
current = queue.popleft()
if current == exit:
break
for nb in maze.get_neighbors(current):
if nb not in came_from:
came_from[nb] = current
queue.append(nb)
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "BFS"
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
stack = [start]
came_from = {start: None}
while stack:
current = stack.pop()
if current == exit:
break
for nb in maze.get_neighbors(current):
if nb not in came_from:
came_from[nb] = current
stack.append(nb)
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "DFS"
class AStarStrategy(PathFindingStrategy):
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) -> List[Cell]:
open_set = []
heapq.heappush(open_set, (0, id(start), start))
came_from = {}
g_score = {start: 0}
f_score = {start: self._heuristic(start, exit)}
while open_set:
_, _, current = heapq.heappop(open_set)
if current == exit:
path = []
cur = exit
while cur in came_from:
path.append(cur)
cur = came_from[cur]
path.append(start)
path.reverse()
return path
for neighbor in maze.get_neighbors(current):
tentative_g = g_score[current] + 1
if tentative_g < g_score.get(neighbor, float('inf')):
came_from[neighbor] = current
g_score[neighbor] = tentative_g
f_score[neighbor] = tentative_g + self._heuristic(neighbor, exit)
heapq.heappush(open_set, (f_score[neighbor], id(neighbor), neighbor))
return []
def get_name(self) -> str:
return "A*"
class DijkstraStrategy(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> List[Cell]:
pq = [(0, id(start), start)]
distances = {start: 0}
came_from = {start: None}
while pq:
dist, _, current = heapq.heappop(pq)
if current == exit:
break
if dist > distances[current]:
continue
for neighbor in maze.get_neighbors(current):
new_dist = dist + 1
if new_dist < distances.get(neighbor, float('inf')):
distances[neighbor] = new_dist
came_from[neighbor] = current
heapq.heappush(pq, (new_dist, id(neighbor), neighbor))
if exit not in came_from:
return []
path = []
cur = exit
while cur:
path.append(cur)
cur = came_from[cur]
path.reverse()
return path
def get_name(self) -> str:
return "Dijkstra"
class SearchStats:
def __init__(self, time_ms: float, visited_cells: int, path_length: int):
self.time_ms = time_ms
self.visited_cells = visited_cells
self.path_length = path_length
def __str__(self):
return f"Time: {self.time_ms:.2f}ms, Visited: {self.visited_cells}, Path: {self.path_length}"
class MazeSolver:
def __init__(self, maze: Maze, strategy: PathFindingStrategy):
self.maze = maze
self.strategy = strategy
def set_strategy(self, strategy: PathFindingStrategy):
self.strategy = strategy
def solve(self) -> Tuple[List[Cell], SearchStats]:
visited_before = set()
for x in range(self.maze.width):
for y in range(self.maze.height):
cell = self.maze.get_cell(x, y)
if cell and cell.is_passable():
visited_before.add(cell)
start_time = time.perf_counter()
path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
end_time = time.perf_counter()
visited_after = set()
for x in range(self.maze.width):
for y in range(self.maze.height):
cell = self.maze.get_cell(x, y)
if cell and cell.is_passable():
visited_after.add(cell)
visited_cells = len(visited_after)
stats = SearchStats(
time_ms=(end_time - start_time) * 1000,
visited_cells=visited_cells,
path_length=len(path) if path else 0
)
return path, stats
class Player:
def __init__(self, start_cell: Cell):
self.current_cell = start_cell
self.previous_cell = None
def move_to(self, cell: Cell) -> bool:
if cell.is_passable():
self.previous_cell = self.current_cell
self.current_cell = cell
return True
return False
def undo(self):
if self.previous_cell:
self.current_cell, self.previous_cell = self.previous_cell, None
return True
return False
class Command(ABC):
@abstractmethod
def execute(self) -> bool:
pass
@abstractmethod
def undo(self):
pass
class MoveCommand(Command):
def __init__(self, player: Player, maze: Maze, direction: str):
self.player = player
self.maze = maze
self.direction = direction
self.executed = False
def execute(self) -> bool:
dx, dy = 0, 0
if self.direction == 'W' or self.direction == 'w':
dy = -1
elif self.direction == 'S' or self.direction == 's':
dy = 1
elif self.direction == 'A' or self.direction == 'a':
dx = -1
elif self.direction == 'D' or self.direction == 'd':
dx = 1
new_x = self.player.current_cell.x + dx
new_y = self.player.current_cell.y + dy
new_cell = self.maze.get_cell(new_x, new_y)
if new_cell and new_cell.is_passable():
self.executed = self.player.move_to(new_cell)
return self.executed
return False
def undo(self):
if self.executed:
self.player.undo()
self.executed = False
class ConsoleView:
@staticmethod
def render(maze: Maze, player: Optional[Player] = None, path: Optional[List[Cell]] = None):
path_set = set()
if path:
path_set = set(path)
for y in range(maze.height):
line = ""
for x in range(maze.width):
cell = maze.get_cell(x, y)
if not cell:
line += " "
elif player and player.current_cell == cell:
line += "P"
elif cell.is_start:
line += "S"
elif cell.is_exit:
line += "E"
elif cell.is_wall:
line += "#"
elif path and cell in path_set:
line += "."
else:
line += " "
print(line)
print()
@staticmethod
def show_stats(stats: SearchStats, algo_name: str):
print(f"=== {algo_name} Results ===")
print(stats)
print()
def generate_test_maze(width: int, height: int, complexity: float = 0.3) -> Maze:
maze = Maze(width, height)
for x in range(width):
for y in range(height):
if random.random() < complexity:
maze.cells[x][y].is_wall = True
maze.start = maze.get_cell(0, 0)
if maze.start:
maze.start.is_start = True
maze.start.is_wall = False
maze.exit = maze.get_cell(width - 1, height - 1)
if maze.exit:
maze.exit.is_exit = True
maze.exit.is_wall = False
return maze
def generate_empty_maze(width: int, height: int) -> Maze:
maze = Maze(width, height)
for x in range(width):
for y in range(height):
maze.cells[x][y].is_wall = False
maze.start = maze.get_cell(0, 0)
if maze.start:
maze.start.is_start = True
maze.exit = maze.get_cell(width - 1, height - 1)
if maze.exit:
maze.exit.is_exit = True
return maze
def generate_no_exit_maze(width: int, height: int) -> Maze:
maze = Maze(width, height)
for x in range(width):
for y in range(height):
maze.cells[x][y].is_wall = False
for x in range(width):
maze.cells[x][height // 2].is_wall = True
maze.start = maze.get_cell(0, 0)
if maze.start:
maze.start.is_start = True
maze.exit = maze.get_cell(width - 1, height - 1)
if maze.exit:
maze.exit.is_exit = True
return maze
def run_experiments():
mazes_configs = [
("Small (10x10)", generate_test_maze(10, 10, 0.2)),
("Medium (50x50)", generate_test_maze(50, 50, 0.25)),
("Large (100x100)", generate_test_maze(100, 100, 0.3)),
("Empty (30x30)", generate_empty_maze(30, 30)),
("No Exit (20x20)", generate_no_exit_maze(20, 20))
]
strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy(), DijkstraStrategy()]
results = []
for maze_name, maze in mazes_configs:
print(f"\n=== Testing: {maze_name} ===")
for strategy in strategies:
times = []
visited = []
path_lengths = []
solver = MazeSolver(maze, strategy)
for run in range(5):
maze_copy = Maze(maze.width, maze.height)
for x in range(maze.width):
for y in range(maze.height):
orig = maze.get_cell(x, y)
copy = maze_copy.get_cell(x, y)
if orig:
copy.is_wall = orig.is_wall
copy.is_start = orig.is_start
copy.is_exit = orig.is_exit
maze_copy.start = maze_copy.get_cell(maze.start.x, maze.start.y) if maze.start else None
maze_copy.exit = maze_copy.get_cell(maze.exit.x, maze.exit.y) if maze.exit else None
solver.maze = maze_copy
solver.set_strategy(strategy)
path, stats = solver.solve()
times.append(stats.time_ms)
visited.append(stats.visited_cells)
path_lengths.append(stats.path_length)
avg_time = sum(times) / len(times)
avg_visited = sum(visited) / len(visited)
avg_path = sum(path_lengths) / len(path_lengths)
results.append({
'maze': maze_name,
'algorithm': strategy.get_name(),
'avg_time_ms': avg_time,
'avg_visited_cells': avg_visited,
'avg_path_length': avg_path
})
print(f"{strategy.get_name()}: {avg_time:.2f}ms, {avg_visited:.0f} cells, path={avg_path:.0f}")
with open('experiment_results.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=['maze', 'algorithm', 'avg_time_ms', 'avg_visited_cells', 'avg_path_length'])
writer.writeheader()
writer.writerows(results)
print("\nResults saved to experiment_results.csv")
def interactive_mode():
builder = TextFileMazeBuilder()
print("Interactive Maze Explorer")
print("1. Load maze from file")
print("2. Generate random maze")
choice = input("Choose (1/2): ")
if choice == '1':
filename = input("Enter filename: ")
try:
maze = builder.build_from_file(filename)
except Exception as e:
print(f"Error loading maze: {e}")
return
else:
w = int(input("Width: "))
h = int(input("Height: "))
maze = generate_test_maze(w, h, 0.3)
player = Player(maze.start)
strategies = {
'1': BFSStrategy(),
'2': DFSStrategy(),
'3': AStarStrategy(),
'4': DijkstraStrategy()
}
print("\nSelect algorithm for solving:")
print("1. BFS (shortest path)")
print("2. DFS (fast, not optimal)")
print("3. A* (heuristic)")
print("4. Dijkstra")
algo_choice = input("Choose: ")
solver = MazeSolver(maze, strategies.get(algo_choice, BFSStrategy()))
path, stats = solver.solve()
view = ConsoleView()
if path:
print(f"\nPath found! Length: {len(path)}")
view.show_stats(stats, solver.strategy.get_name())
else:
print("\nNo path found!")
while True:
view.render(maze, player, path if path else None)
if player.current_cell == maze.exit:
print("Congratulations! You reached the exit!")
break
cmd = input("Move (W/A/S/D) | U=undo | Q=quit | S=solve: ").upper()
if cmd == 'Q':
break
elif cmd == 'U':
player.undo()
print("Undo last move")
elif cmd == 'S' and path:
for cell in path:
if cell == player.current_cell:
continue
player.move_to(cell)
view.render(maze, player, path)
input("Press Enter to continue...")
if player.current_cell == maze.exit:
print("You reached the exit!")
break
elif cmd in ['W', 'A', 'S', 'D']:
move_cmd = MoveCommand(player, maze, cmd)
if move_cmd.execute():
print("Moved")
else:
print("Can't move there!")
def main():
print("Maze Solver with Design Patterns")
print("1. Run experiments")
print("2. Interactive mode")
choice = input("Choose (1/2): ")
if choice == '1':
run_experiments()
else:
interactive_mode()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,363 @@
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
# Настройка русских шрифтов
plt.rcParams['font.family'] = 'DejaVu Sans'
plt.rcParams['axes.unicode_minus'] = False
def load_and_prepare_data(filename='experiment_results.csv'):
"""Загрузка данных из CSV и подготовка."""
df = pd.read_csv(filename, delimiter=',') # Используем запятую как разделитель
# Переименовываем столбцы для удобства
df.columns = ['maze_type', 'algorithm', 'avg_time_ms', 'avg_visited_cells', 'avg_path_length']
# Преобразование типов
numeric_cols = ['avg_time_ms', 'avg_visited_cells', 'avg_path_length']
for col in numeric_cols:
df[col] = pd.to_numeric(df[col], errors='coerce')
# Добавляем столбец с размером лабиринта для анализа
def extract_maze_size(maze_name):
if 'Small' in maze_name:
return 'Small (10x10)'
elif 'Medium' in maze_name:
return 'Medium (50x50)'
elif 'Large' in maze_name:
return 'Large (100x100)'
elif 'Empty' in maze_name:
return 'Empty (30x30)'
elif 'No Exit' in maze_name:
return 'No Exit (20x20)'
return maze_name
df['maze_category'] = df['maze_type'].apply(extract_maze_size)
return df
def plot_time_comparison(df):
"""График 1: Сравнение времени выполнения по лабиринтам."""
fig, ax = plt.subplots(figsize=(12, 6))
maze_types = df['maze_category'].unique()
algorithms = df['algorithm'].unique()
x = np.arange(len(maze_types))
width = 0.2
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728']
for i, algorithm in enumerate(algorithms):
algo_data = df[df['algorithm'] == algorithm]
times = []
for maze in maze_types:
row = algo_data[algo_data['maze_category'] == maze]
if not row.empty:
times.append(row['avg_time_ms'].values[0])
else:
times.append(0)
bars = ax.bar(x + i*width, times, width, label=algorithm,
color=colors[i])
ax.set_xlabel('Тип лабиринта', fontsize=12)
ax.set_ylabel('Время выполнения (мс)', fontsize=12)
ax.set_title('Сравнение времени выполнения алгоритмов поиска пути', fontsize=14)
ax.set_xticks(x + width * 1.5)
ax.set_xticklabels(maze_types, rotation=45, ha='right')
ax.legend()
ax.grid(True, alpha=0.3, axis='y')
# Добавление значений на столбцы
for i, algorithm in enumerate(algorithms):
algo_data = df[df['algorithm'] == algorithm]
for j, maze in enumerate(maze_types):
row = algo_data[algo_data['maze_category'] == maze]
if not row.empty and row['avg_time_ms'].values[0] > 0:
time_val = row['avg_time_ms'].values[0]
ax.text(x[j] + i*width, time_val + 0.02,
f'{time_val:.3f}', ha='center', va='bottom', fontsize=8)
plt.tight_layout()
plt.savefig('time_comparison.png', dpi=150)
plt.show()
def plot_visited_cells(df):
"""График 2: Количество посещённых клеток."""
fig, ax = plt.subplots(figsize=(12, 6))
maze_types = df['maze_category'].unique()
algorithms = df['algorithm'].unique()
x = np.arange(len(maze_types))
width = 0.2
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728']
for i, algorithm in enumerate(algorithms):
algo_data = df[df['algorithm'] == algorithm]
visited = []
for maze in maze_types:
row = algo_data[algo_data['maze_category'] == maze]
if not row.empty:
visited.append(row['avg_visited_cells'].values[0])
else:
visited.append(0)
ax.bar(x + i*width, visited, width, label=algorithm, color=colors[i])
ax.set_xlabel('Тип лабиринта', fontsize=12)
ax.set_ylabel('Количество посещённых клеток', fontsize=12)
ax.set_title('Сравнение количества посещённых клеток', fontsize=14)
ax.set_xticks(x + width * 1.5)
ax.set_xticklabels(maze_types, rotation=45, ha='right')
ax.legend()
ax.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig('visited_cells.png', dpi=150)
plt.show()
def plot_path_length(df):
"""График 3: Длина найденного пути."""
fig, ax = plt.subplots(figsize=(12, 6))
# Исключаем лабиринты без выхода (где путь = 0)
df_filtered = df[df['avg_path_length'] > 0]
maze_types = df_filtered['maze_category'].unique()
algorithms = df_filtered['algorithm'].unique()
x = np.arange(len(maze_types))
width = 0.2
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728']
for i, algorithm in enumerate(algorithms):
algo_data = df_filtered[df_filtered['algorithm'] == algorithm]
path_lengths = []
for maze in maze_types:
row = algo_data[algo_data['maze_category'] == maze]
if not row.empty:
path_lengths.append(row['avg_path_length'].values[0])
else:
path_lengths.append(0)
ax.bar(x + i*width, path_lengths, width, label=algorithm, color=colors[i])
ax.set_xlabel('Тип лабиринта', fontsize=12)
ax.set_ylabel('Длина пути (количество клеток)', fontsize=12)
ax.set_title('Сравнение длины найденного пути', fontsize=14)
ax.set_xticks(x + width * 1.5)
ax.set_xticklabels(maze_types, rotation=45, ha='right')
ax.legend()
ax.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig('path_length.png', dpi=150)
plt.show()
def plot_time_per_maze(df):
"""График 4: Для каждого лабиринта - сравнение алгоритмов по времени."""
maze_types = df['maze_category'].unique()
algorithms = df['algorithm'].unique()
for maze in maze_types:
fig, ax = plt.subplots(figsize=(10, 6))
maze_data = df[df['maze_category'] == maze]
times = []
algo_names = []
for algo in algorithms:
row = maze_data[maze_data['algorithm'] == algo]
if not row.empty:
times.append(row['avg_time_ms'].values[0])
algo_names.append(algo)
bars = ax.bar(algo_names, times,
color=['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728'][:len(algo_names)])
ax.set_xlabel('Алгоритм', fontsize=12)
ax.set_ylabel('Время выполнения (мс)', fontsize=12)
ax.set_title(f'Сравнение алгоритмов на лабиринте: {maze}', fontsize=14)
ax.grid(True, alpha=0.3, axis='y')
# Добавление значений на столбцы
for bar, time_val in zip(bars, times):
height = bar.get_height()
ax.text(bar.get_x() + bar.get_width()/2., height + 0.02,
f'{time_val:.3f}', ha='center', va='bottom', fontsize=10)
plt.tight_layout()
# Очищаем имя файла от скобок
safe_maze_name = maze.replace('(', '').replace(')', '').replace(' ', '_')
plt.savefig(f'time_{safe_maze_name}.png', dpi=150)
plt.show()
def plot_visited_per_maze(df):
"""График 5: Для каждого лабиринта - посещённые клетки."""
maze_types = df['maze_category'].unique()
for maze in maze_types:
fig, ax = plt.subplots(figsize=(10, 6))
maze_data = df[df['maze_category'] == maze]
visited = maze_data['avg_visited_cells'].values
algo_names = maze_data['algorithm'].values
bars = ax.bar(algo_names, visited,
color=['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728'][:len(algo_names)])
ax.set_xlabel('Алгоритм', fontsize=12)
ax.set_ylabel('Количество посещённых клеток', fontsize=12)
ax.set_title(f'Посещённые клетки на лабиринте: {maze}', fontsize=14)
ax.grid(True, alpha=0.3, axis='y')
# Добавление значений на столбцы
for bar, val in zip(bars, visited):
height = bar.get_height()
ax.text(bar.get_x() + bar.get_width()/2., height + 10,
f'{int(val)}', ha='center', va='bottom', fontsize=10)
plt.tight_layout()
safe_maze_name = maze.replace('(', '').replace(')', '').replace(' ', '_')
plt.savefig(f'visited_{safe_maze_name}.png', dpi=150)
plt.show()
def plot_efficiency_ratio(df):
"""График 6: Эффективность (время на клетку пути)."""
fig, ax = plt.subplots(figsize=(12, 6))
# Исключаем лабиринты без пути
df_filtered = df[(df['avg_path_length'] > 0) & (df['avg_time_ms'] > 0)].copy()
df_filtered['efficiency'] = df_filtered['avg_time_ms'] / df_filtered['avg_path_length']
maze_types = df_filtered['maze_category'].unique()
algorithms = df_filtered['algorithm'].unique()
x = np.arange(len(maze_types))
width = 0.2
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728']
for i, algorithm in enumerate(algorithms):
algo_data = df_filtered[df_filtered['algorithm'] == algorithm]
efficiency = []
for maze in maze_types:
row = algo_data[algo_data['maze_category'] == maze]
if not row.empty:
efficiency.append(row['efficiency'].values[0])
else:
efficiency.append(0)
ax.bar(x + i*width, efficiency, width, label=algorithm, color=colors[i])
ax.set_xlabel('Тип лабиринта', fontsize=12)
ax.set_ylabel('Время на клетку пути (мс/клетку)', fontsize=12)
ax.set_title('Эффективность алгоритмов (время на единицу длины пути)', fontsize=14)
ax.set_xticks(x + width * 1.5)
ax.set_xticklabels(maze_types, rotation=45, ha='right')
ax.legend()
ax.grid(True, alpha=0.3, axis='y')
plt.tight_layout()
plt.savefig('efficiency_ratio.png', dpi=150)
plt.show()
def plot_path_vs_visited(df):
"""График 7: Соотношение длины пути и посещённых клеток."""
fig, ax = plt.subplots(figsize=(10, 6))
algorithms = df['algorithm'].unique()
markers = ['o', 's', '^', 'D']
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728']
for algo, marker, color in zip(algorithms, markers, colors):
algo_data = df[df['algorithm'] == algo]
# Только лабиринты с путём
algo_data = algo_data[algo_data['avg_path_length'] > 0]
if not algo_data.empty:
plt.scatter(algo_data['avg_visited_cells'],
algo_data['avg_path_length'],
marker=marker, s=100, label=algo, color=color, alpha=0.7)
# Добавляем подписи для каждой точки
for _, row in algo_data.iterrows():
plt.annotate(row['maze_category'].split()[0],
(row['avg_visited_cells'], row['avg_path_length']),
xytext=(5, 5), textcoords='offset points', fontsize=8)
plt.xlabel('Количество посещённых клеток', fontsize=12)
plt.ylabel('Длина пути (клеток)', fontsize=12)
plt.title('Соотношение: посещённые клетки vs длина пути', fontsize=14)
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('path_vs_visited.png', dpi=150)
plt.show()
def main():
"""Основная функция: загрузка данных и построение всех графиков."""
try:
df = load_and_prepare_data('experiment_results.csv')
print("Данные успешно загружены")
print(f"Найдено {len(df)} записей")
print("\nСтруктура данных:")
print(df.head())
print("\nУникальные типы лабиринтов:")
print(df['maze_category'].unique())
print("\nУникальные алгоритмы:")
print(df['algorithm'].unique())
print("\nПостроение графиков...")
# Базовые графики
plot_time_comparison(df)
plot_visited_cells(df)
plot_path_length(df)
# Детальные графики по каждому лабиринту
plot_time_per_maze(df)
plot_visited_per_maze(df)
# Аналитические графики
plot_efficiency_ratio(df)
plot_path_vs_visited(df)
print("\nВсе графики сохранены в текущей директории:")
print(" - time_comparison.png")
print(" - visited_cells.png")
print(" - path_length.png")
print(" - time_{maze}.png (для каждого лабиринта)")
print(" - visited_{maze}.png (для каждого лабиринта)")
print(" - efficiency_ratio.png")
print(" - path_vs_visited.png")
# Вывод статистики
print("\n=== Краткая статистика ===")
for maze in df['maze_category'].unique():
print(f"\n{maze}:")
maze_data = df[df['maze_category'] == maze]
for algo in df['algorithm'].unique():
algo_data = maze_data[maze_data['algorithm'] == algo]
if not algo_data.empty:
time_val = algo_data['avg_time_ms'].values[0]
visited_val = int(algo_data['avg_visited_cells'].values[0])
path_val = int(algo_data['avg_path_length'].values[0])
print(f" {algo}: время={time_val:.6f}мс, посещено={visited_val}, путь={path_val}")
except FileNotFoundError:
print("Ошибка: файл experiment_results.csv не найден")
print("Убедитесь, что файл находится в текущей директории")
except Exception as e:
print(f"Ошибка: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

View File

@ -0,0 +1,21 @@
maze,algorithm,avg_time_ms,avg_visited_cells,avg_path_length
Small (10x10),BFS,0.08572000006097369,79.0,19.0
Small (10x10),DFS,0.039739999920129776,79.0,31.0
Small (10x10),A*,0.13467999997374136,79.0,19.0
Small (10x10),Dijkstra,0.11474000057205558,79.0,19.0
Medium (50x50),BFS,1.8074600004183594,1874.0,99.0
Medium (50x50),DFS,0.5937599995377241,1874.0,429.0
Medium (50x50),A*,1.6300600003887666,1874.0,99.0
Medium (50x50),Dijkstra,3.1870400001935195,1874.0,99.0
Large (100x100),BFS,0.014439999722526409,7033.0,0.0
Large (100x100),DFS,0.014839999857940711,7033.0,0.0
Large (100x100),A*,0.02542000001994893,7033.0,0.0
Large (100x100),Dijkstra,0.02548000011302065,7033.0,0.0
Empty (30x30),BFS,0.784620000194991,900.0,59.0
Empty (30x30),DFS,0.5252399994787993,900.0,465.0
Empty (30x30),A*,1.150900000357069,900.0,59.0
Empty (30x30),Dijkstra,1.564640000287909,900.0,59.0
No Exit (20x20),BFS,0.2002399993216386,380.0,0.0
No Exit (20x20),DFS,0.2512400002160575,380.0,0.0
No Exit (20x20),A*,0.5590400000073714,380.0,0.0
No Exit (20x20),Dijkstra,0.35640000060084276,380.0,0.0
1 maze algorithm avg_time_ms avg_visited_cells avg_path_length
2 Small (10x10) BFS 0.08572000006097369 79.0 19.0
3 Small (10x10) DFS 0.039739999920129776 79.0 31.0
4 Small (10x10) A* 0.13467999997374136 79.0 19.0
5 Small (10x10) Dijkstra 0.11474000057205558 79.0 19.0
6 Medium (50x50) BFS 1.8074600004183594 1874.0 99.0
7 Medium (50x50) DFS 0.5937599995377241 1874.0 429.0
8 Medium (50x50) A* 1.6300600003887666 1874.0 99.0
9 Medium (50x50) Dijkstra 3.1870400001935195 1874.0 99.0
10 Large (100x100) BFS 0.014439999722526409 7033.0 0.0
11 Large (100x100) DFS 0.014839999857940711 7033.0 0.0
12 Large (100x100) A* 0.02542000001994893 7033.0 0.0
13 Large (100x100) Dijkstra 0.02548000011302065 7033.0 0.0
14 Empty (30x30) BFS 0.784620000194991 900.0 59.0
15 Empty (30x30) DFS 0.5252399994787993 900.0 465.0
16 Empty (30x30) A* 1.150900000357069 900.0 59.0
17 Empty (30x30) Dijkstra 1.564640000287909 900.0 59.0
18 No Exit (20x20) BFS 0.2002399993216386 380.0 0.0
19 No Exit (20x20) DFS 0.2512400002160575 380.0 0.0
20 No Exit (20x20) A* 0.5590400000073714 380.0 0.0
21 No Exit (20x20) Dijkstra 0.35640000060084276 380.0 0.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View File

@ -0,0 +1,292 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import time
import random
import csv
import sys
sys.setrecursionlimit(30000)
def ll_create_node(name, phone):
return {'name': name, 'phone': phone, 'next': None}
def ll_insert(head, name, phone):
if head is None:
return ll_create_node(name, phone)
if head['name'] == name:
head['phone'] = phone
return head
current = head
while current['next'] is not None:
if current['next']['name'] == name:
current['next']['phone'] = phone
return head
current = current['next']
current['next'] = ll_create_node(name, phone)
return head
def ll_find(head, name):
current = head
while current is not None:
if current['name'] == name:
return current['phone']
current = current['next']
return None
def ll_delete(head, name):
if head is None:
return None
if head['name'] == name:
return head['next']
current = head
while current['next'] is not None:
if current['next']['name'] == name:
current['next'] = current['next']['next']
return head
current = current['next']
return head
def ll_list_all(head):
records = []
current = head
while current is not None:
records.append((current['name'], current['phone']))
current = current['next']
records.sort(key=lambda x: x[0])
return records
def hash_function(name, table_size):
return sum(ord(c) for c in name) % table_size
def ht_create_table(size=2000):
return [None] * size
def ht_insert(table, name, phone):
index = hash_function(name, len(table))
table[index] = ll_insert(table[index], name, phone)
def ht_find(table, name):
index = hash_function(name, len(table))
return ll_find(table[index], name)
def ht_delete(table, name):
index = hash_function(name, len(table))
table[index] = ll_delete(table[index], name)
def ht_list_all(table):
all_records = []
for bucket in table:
if bucket is not None:
current = bucket
while current is not None:
all_records.append((current['name'], current['phone']))
current = current['next']
all_records.sort(key=lambda x: x[0])
return all_records
def bst_create_node(name, phone):
return {'name': name, 'phone': phone, 'left': None, 'right': None}
def bst_insert(root, name, phone):
if root is None:
return bst_create_node(name, phone)
current = root
while True:
if name < current['name']:
if current['left'] is None:
current['left'] = bst_create_node(name, phone)
break
else:
current = current['left']
elif name > current['name']:
if current['right'] is None:
current['right'] = bst_create_node(name, phone)
break
else:
current = current['right']
else:
current['phone'] = phone
break
return root
def bst_find(root, name):
current = root
while current is not None:
if name < current['name']:
current = current['left']
elif name > current['name']:
current = current['right']
else:
return current['phone']
return None
def bst_find_min(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
parent = None
current = root
while current is not None and current['name'] != name:
parent = current
if name < current['name']:
current = current['left']
else:
current = current['right']
if current is None:
return root
if current['left'] is None or current['right'] is None:
if current['left'] is not None:
child = current['left']
else:
child = current['right']
if parent is None:
return child
if parent['left'] == current:
parent['left'] = child
else:
parent['right'] = child
else:
successor_parent = current
successor = current['right']
while successor['left'] is not None:
successor_parent = successor
successor = successor['left']
current['name'] = successor['name']
current['phone'] = successor['phone']
if successor_parent['left'] == successor:
successor_parent['left'] = successor['right']
else:
successor_parent['right'] = successor['right']
return root
def bst_list_all(root):
records = []
stack = []
current = root
while stack or current is not None:
while current is not None:
stack.append(current)
current = current['left']
current = stack.pop()
records.append((current['name'], current['phone']))
current = current['right']
return records
def generate_data(n=10000):
records = [(f"User_{i:05d}", f"+7-999-{i:06d}") for i in range(n)]
records_shuffled = records.copy()
random.shuffle(records_shuffled)
records_sorted = sorted(records, key=lambda x: x[0])
return records_shuffled, records_sorted
def run_experiment(structure_name, insert_func, find_func, delete_func,
list_all_func, init_func, records, n_find=100):
data = init_func()
names = [r[0] for r in records]
start = time.perf_counter()
for name, phone in records:
if structure_name == "HashTable":
insert_func(data, name, phone)
else:
data = insert_func(data, name, phone)
insert_time = time.perf_counter() - start
find_names = random.sample(names, min(n_find, len(names)))
missing_names = [f"None_{i}" for i in range(10)]
all_find_names = find_names + missing_names
start = time.perf_counter()
for name in all_find_names:
if structure_name == "HashTable":
find_func(data, name)
else:
find_func(data, name)
find_time = time.perf_counter() - start
delete_names = random.sample(names, min(50, len(names)))
start = time.perf_counter()
for name in delete_names:
if structure_name == "HashTable":
delete_func(data, name)
else:
data = delete_func(data, name)
delete_time = time.perf_counter() - start
return insert_time, find_time, delete_time
def main():
print("Generating test data...")
records_shuffled, records_sorted = generate_data(10000)
results = []
structures = [
("LinkedList", ll_insert, ll_find, ll_delete, ll_list_all, lambda: None),
("HashTable", ht_insert, ht_find, ht_delete, ht_list_all, lambda: ht_create_table(2000)),
("BST", bst_insert, bst_find, bst_delete, bst_list_all, lambda: None)
]
for mode_name, records in [("random", records_shuffled), ("sorted", records_sorted)]:
print(f"\nMode: {mode_name}")
for struct_name, insert_f, find_f, delete_f, list_f, init_f in structures:
print(f" Testing {struct_name}...")
times = []
for run in range(5):
insert_t, find_t, delete_t = run_experiment(
struct_name, insert_f, find_f, delete_f, list_f, init_f, records
)
times.append((insert_t, find_t, delete_t))
print(f" Run {run+1}: insert={insert_t:.4f}s, find={find_t:.4f}s, delete={delete_t:.4f}s")
avg_insert = sum(t[0] for t in times) / 5
avg_find = sum(t[1] for t in times) / 5
avg_delete = sum(t[2] for t in times) / 5
results.append([struct_name, mode_name, "insert", avg_insert])
results.append([struct_name, mode_name, "find", avg_find])
results.append([struct_name, mode_name, "delete", avg_delete])
with open("results.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["Structure", "Mode", "Operation", "Time_seconds"])
writer.writerows(results)
print("\n" + "="*60)
print("RESULTS (average over 5 runs):")
print("="*60)
for row in results:
print(f"{row[0]:12} | {row[1]:8} | {row[2]:8} | {row[3]:.6f} sec")
print("\nResults saved to results.csv")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,19 @@
Structure,Mode,Operation,Time_seconds
LinkedList,random,insert,3.115811080000276
LinkedList,random,find,0.02396312000018952
LinkedList,random,delete,0.016048219999720458
HashTable,random,insert,0.18448304000012286
HashTable,random,find,0.0012929600005008978
HashTable,random,delete,0.0009329200001957361
BST,random,insert,0.017231119999996734
BST,random,find,0.00014155999961076304
BST,random,delete,9.299999983340968e-05
LinkedList,sorted,insert,2.780292439999903
LinkedList,sorted,find,0.02136590000045544
LinkedList,sorted,delete,0.014907859999584615
HashTable,sorted,insert,0.16707750000023225
HashTable,sorted,find,0.0012113199998566415
HashTable,sorted,delete,0.0008899600001313956
BST,sorted,insert,3.844869280000421
BST,sorted,find,0.031808019999880345
BST,sorted,delete,0.016554539999560802
1 Structure Mode Operation Time_seconds
2 LinkedList random insert 3.115811080000276
3 LinkedList random find 0.02396312000018952
4 LinkedList random delete 0.016048219999720458
5 HashTable random insert 0.18448304000012286
6 HashTable random find 0.0012929600005008978
7 HashTable random delete 0.0009329200001957361
8 BST random insert 0.017231119999996734
9 BST random find 0.00014155999961076304
10 BST random delete 9.299999983340968e-05
11 LinkedList sorted insert 2.780292439999903
12 LinkedList sorted find 0.02136590000045544
13 LinkedList sorted delete 0.014907859999584615
14 HashTable sorted insert 0.16707750000023225
15 HashTable sorted find 0.0012113199998566415
16 HashTable sorted delete 0.0008899600001313956
17 BST sorted insert 3.844869280000421
18 BST sorted find 0.031808019999880345
19 BST sorted delete 0.016554539999560802

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,19 @@
Структура,Режим,Операция,Среднее время (сек),Мин,Макс
LinkedList,shuffled,вставка,0.42060984001727775,0.4142958000302315,0.42548670002724975
LinkedList,shuffled,поиск,0.00460058000171557,0.004181800002697855,0.0052013000240549445
LinkedList,shuffled,удаление,0.003505319997202605,0.003264300001319498,0.0037631000159308314
LinkedList,sorted,вставка,0.4510407999972813,0.446296899986919,0.4555279000196606
LinkedList,sorted,поиск,0.0027159999939613045,0.0025430000387132168,0.0028782999725081027
LinkedList,sorted,удаление,0.002460040000732988,0.001968099968507886,0.002805600001011044
HashTable,shuffled,вставка,0.04048331999219954,0.0402110000140965,0.040805700002238154
HashTable,shuffled,поиск,0.00036708000116050246,0.00035719998413696885,0.0003866000333800912
HashTable,shuffled,удаление,0.0002693800022825599,0.00025370001094415784,0.0002874999772757292
HashTable,sorted,вставка,0.041944000008516016,0.04176550003467128,0.04211939999368042
HashTable,sorted,поиск,0.000306799984537065,0.0003005999606102705,0.00031589996069669724
HashTable,sorted,удаление,0.00025475999573245647,0.00021820003166794777,0.0002739999908953905
BST,shuffled,вставка,0.025399240001570435,0.025202599994372576,0.025638799997977912
BST,shuffled,поиск,0.00024858000688254833,0.00024119997397065163,0.00025720003759488463
BST,shuffled,удаление,0.0001722599961794913,0.00015210005221888423,0.00019409996457397938
BST,sorted,вставка,1.471655760006979,1.4474275999818929,1.4893997000181116
BST,sorted,поиск,0.005254479986615479,0.004766399972140789,0.005771900003310293
BST,sorted,удаление,0.002353680005762726,0.0019777000416070223,0.0030280999490059912
1 Структура Режим Операция Среднее время (сек) Мин Макс
2 LinkedList shuffled вставка 0.42060984001727775 0.4142958000302315 0.42548670002724975
3 LinkedList shuffled поиск 0.00460058000171557 0.004181800002697855 0.0052013000240549445
4 LinkedList shuffled удаление 0.003505319997202605 0.003264300001319498 0.0037631000159308314
5 LinkedList sorted вставка 0.4510407999972813 0.446296899986919 0.4555279000196606
6 LinkedList sorted поиск 0.0027159999939613045 0.0025430000387132168 0.0028782999725081027
7 LinkedList sorted удаление 0.002460040000732988 0.001968099968507886 0.002805600001011044
8 HashTable shuffled вставка 0.04048331999219954 0.0402110000140965 0.040805700002238154
9 HashTable shuffled поиск 0.00036708000116050246 0.00035719998413696885 0.0003866000333800912
10 HashTable shuffled удаление 0.0002693800022825599 0.00025370001094415784 0.0002874999772757292
11 HashTable sorted вставка 0.041944000008516016 0.04176550003467128 0.04211939999368042
12 HashTable sorted поиск 0.000306799984537065 0.0003005999606102705 0.00031589996069669724
13 HashTable sorted удаление 0.00025475999573245647 0.00021820003166794777 0.0002739999908953905
14 BST shuffled вставка 0.025399240001570435 0.025202599994372576 0.025638799997977912
15 BST shuffled поиск 0.00024858000688254833 0.00024119997397065163 0.00025720003759488463
16 BST shuffled удаление 0.0001722599961794913 0.00015210005221888423 0.00019409996457397938
17 BST sorted вставка 1.471655760006979 1.4474275999818929 1.4893997000181116
18 BST sorted поиск 0.005254479986615479 0.004766399972140789 0.005771900003310293
19 BST sorted удаление 0.002353680005762726 0.0019777000416070223 0.0030280999490059912

View File

@ -0,0 +1,19 @@
Структура,Режим,Операция,Среднее время (сек),Мин,Макс
LinkedList,shuffled,вставка,0.4877981000114232,0.4723332999856211,0.5048669999814592
LinkedList,shuffled,поиск,0.005099979997612536,0.004888699972070754,0.005397600005380809
LinkedList,shuffled,удаление,0.003206899994984269,0.002823700022418052,0.0036705999518744648
LinkedList,sorted,вставка,0.5067427000147291,0.5056617999798618,0.5084751000395045
LinkedList,sorted,поиск,0.003157860005740076,0.003096400003414601,0.003234500007238239
LinkedList,sorted,удаление,0.0027350999880582094,0.0020201000152155757,0.003734299971256405
HashTable,shuffled,вставка,0.0430581400054507,0.04275970003800467,0.04344099998706952
HashTable,shuffled,поиск,0.0003944600117392838,0.00037650001468136907,0.0004216000088490546
HashTable,shuffled,удаление,0.0002655199728906155,0.00024719996144995093,0.000278000021353364
HashTable,sorted,вставка,0.04386393999448046,0.04331600002478808,0.044519999995827675
HashTable,sorted,поиск,0.00030770000303164123,0.00029749999521300197,0.0003185000387020409
HashTable,sorted,удаление,0.00021459999261423944,0.00017769995611160994,0.00024099997244775295
BST,shuffled,вставка,0.023519799998030066,0.023390699992887676,0.02383040002314374
BST,shuffled,поиск,0.0002645400119945407,0.0002535999519750476,0.000271800032351166
BST,shuffled,удаление,0.00015245999675244092,0.000139000010676682,0.00016689999029040337
BST,sorted,вставка,1.4369806799921208,1.4339254000224173,1.4454721999936737
BST,sorted,поиск,0.005990639992523939,0.005681200011167675,0.0064765000133775175
BST,sorted,удаление,0.002513900003395975,0.001900400035083294,0.00313799997093156
1 Структура Режим Операция Среднее время (сек) Мин Макс
2 LinkedList shuffled вставка 0.4877981000114232 0.4723332999856211 0.5048669999814592
3 LinkedList shuffled поиск 0.005099979997612536 0.004888699972070754 0.005397600005380809
4 LinkedList shuffled удаление 0.003206899994984269 0.002823700022418052 0.0036705999518744648
5 LinkedList sorted вставка 0.5067427000147291 0.5056617999798618 0.5084751000395045
6 LinkedList sorted поиск 0.003157860005740076 0.003096400003414601 0.003234500007238239
7 LinkedList sorted удаление 0.0027350999880582094 0.0020201000152155757 0.003734299971256405
8 HashTable shuffled вставка 0.0430581400054507 0.04275970003800467 0.04344099998706952
9 HashTable shuffled поиск 0.0003944600117392838 0.00037650001468136907 0.0004216000088490546
10 HashTable shuffled удаление 0.0002655199728906155 0.00024719996144995093 0.000278000021353364
11 HashTable sorted вставка 0.04386393999448046 0.04331600002478808 0.044519999995827675
12 HashTable sorted поиск 0.00030770000303164123 0.00029749999521300197 0.0003185000387020409
13 HashTable sorted удаление 0.00021459999261423944 0.00017769995611160994 0.00024099997244775295
14 BST shuffled вставка 0.023519799998030066 0.023390699992887676 0.02383040002314374
15 BST shuffled поиск 0.0002645400119945407 0.0002535999519750476 0.000271800032351166
16 BST shuffled удаление 0.00015245999675244092 0.000139000010676682 0.00016689999029040337
17 BST sorted вставка 1.4369806799921208 1.4339254000224173 1.4454721999936737
18 BST sorted поиск 0.005990639992523939 0.005681200011167675 0.0064765000133775175
19 BST sorted удаление 0.002513900003395975 0.001900400035083294 0.00313799997093156

View File

@ -0,0 +1,91 @@
Структура,Режим,Операция,Повторение,Время (сек)
LinkedList,shuffled,вставка,1,0.42548670002724975
LinkedList,shuffled,вставка,2,0.420181900030002
LinkedList,shuffled,вставка,3,0.4228276999783702
LinkedList,shuffled,вставка,4,0.4202571000205353
LinkedList,shuffled,вставка,5,0.4142958000302315
LinkedList,shuffled,поиск,1,0.004461299977265298
LinkedList,shuffled,поиск,2,0.004771800013259053
LinkedList,shuffled,поиск,3,0.0052013000240549445
LinkedList,shuffled,поиск,4,0.004181800002697855
LinkedList,shuffled,поиск,5,0.004386699991300702
LinkedList,shuffled,удаление,1,0.0036864999565295875
LinkedList,shuffled,удаление,2,0.003434700018260628
LinkedList,shuffled,удаление,3,0.0033779999939724803
LinkedList,shuffled,удаление,4,0.0037631000159308314
LinkedList,shuffled,удаление,5,0.003264300001319498
LinkedList,sorted,вставка,1,0.4555279000196606
LinkedList,sorted,вставка,2,0.4474210999906063
LinkedList,sorted,вставка,3,0.446296899986919
LinkedList,sorted,вставка,4,0.4518415000056848
LinkedList,sorted,вставка,5,0.4541165999835357
LinkedList,sorted,поиск,1,0.0025430000387132168
LinkedList,sorted,поиск,2,0.0026971999905072153
LinkedList,sorted,поиск,3,0.0028782999725081027
LinkedList,sorted,поиск,4,0.00268759997561574
LinkedList,sorted,поиск,5,0.0027738999924622476
LinkedList,sorted,удаление,1,0.001968099968507886
LinkedList,sorted,удаление,2,0.002279900014400482
LinkedList,sorted,удаление,3,0.0026916000060737133
LinkedList,sorted,удаление,4,0.0025550000136718154
LinkedList,sorted,удаление,5,0.002805600001011044
HashTable,shuffled,вставка,1,0.04048329999204725
HashTable,shuffled,вставка,2,0.040805700002238154
HashTable,shuffled,вставка,3,0.04023119999328628
HashTable,shuffled,вставка,4,0.040685399959329516
HashTable,shuffled,вставка,5,0.0402110000140965
HashTable,shuffled,поиск,1,0.00035719998413696885
HashTable,shuffled,поиск,2,0.00036239996552467346
HashTable,shuffled,поиск,3,0.0003719999804161489
HashTable,shuffled,поиск,4,0.00035720004234462976
HashTable,shuffled,поиск,5,0.0003866000333800912
HashTable,shuffled,удаление,1,0.00026569998590275645
HashTable,shuffled,удаление,2,0.0002874999772757292
HashTable,shuffled,удаление,3,0.00025370001094415784
HashTable,shuffled,удаление,4,0.00027540000155568123
HashTable,shuffled,удаление,5,0.00026460003573447466
HashTable,sorted,вставка,1,0.04203070001676679
HashTable,sorted,вставка,2,0.04176550003467128
HashTable,sorted,вставка,3,0.04188929998781532
HashTable,sorted,вставка,4,0.04211939999368042
HashTable,sorted,вставка,5,0.04191510000964627
HashTable,sorted,поиск,1,0.0003032999811694026
HashTable,sorted,поиск,2,0.00030319998040795326
HashTable,sorted,поиск,3,0.0003005999606102705
HashTable,sorted,поиск,4,0.00031589996069669724
HashTable,sorted,поиск,5,0.00031100003980100155
HashTable,sorted,удаление,1,0.00021820003166794777
HashTable,sorted,удаление,2,0.00026649999199435115
HashTable,sorted,удаление,3,0.000248699972871691
HashTable,sorted,удаление,4,0.0002663999912329018
HashTable,sorted,удаление,5,0.0002739999908953905
BST,shuffled,вставка,1,0.025202599994372576
BST,shuffled,вставка,2,0.025266800017561764
BST,shuffled,вставка,3,0.025638799997977912
BST,shuffled,вставка,4,0.025355199992191046
BST,shuffled,вставка,5,0.025532800005748868
BST,shuffled,поиск,1,0.00025720003759488463
BST,shuffled,поиск,2,0.00025560002541169524
BST,shuffled,поиск,3,0.00024309998843818903
BST,shuffled,поиск,4,0.00024119997397065163
BST,shuffled,поиск,5,0.00024580000899732113
BST,shuffled,удаление,1,0.00017309997929260135
BST,shuffled,удаление,2,0.00015999999595806003
BST,shuffled,удаление,3,0.00015210005221888423
BST,shuffled,удаление,4,0.00019409996457397938
BST,shuffled,удаление,5,0.00018199998885393143
BST,sorted,вставка,1,1.4893997000181116
BST,sorted,вставка,2,1.473117200017441
BST,sorted,вставка,3,1.4703117000171915
BST,sorted,вставка,4,1.4474275999818929
BST,sorted,вставка,5,1.4780226000002585
BST,sorted,поиск,1,0.005226599983870983
BST,sorted,поиск,2,0.005771900003310293
BST,sorted,поиск,3,0.004766399972140789
BST,sorted,поиск,4,0.005606099963188171
BST,sorted,поиск,5,0.0049014000105671585
BST,sorted,удаление,1,0.0022025000071153045
BST,sorted,удаление,2,0.0030280999490059912
BST,sorted,удаление,3,0.002415299997664988
BST,sorted,удаление,4,0.0019777000416070223
BST,sorted,удаление,5,0.0021448000334203243
1 Структура Режим Операция Повторение Время (сек)
2 LinkedList shuffled вставка 1 0.42548670002724975
3 LinkedList shuffled вставка 2 0.420181900030002
4 LinkedList shuffled вставка 3 0.4228276999783702
5 LinkedList shuffled вставка 4 0.4202571000205353
6 LinkedList shuffled вставка 5 0.4142958000302315
7 LinkedList shuffled поиск 1 0.004461299977265298
8 LinkedList shuffled поиск 2 0.004771800013259053
9 LinkedList shuffled поиск 3 0.0052013000240549445
10 LinkedList shuffled поиск 4 0.004181800002697855
11 LinkedList shuffled поиск 5 0.004386699991300702
12 LinkedList shuffled удаление 1 0.0036864999565295875
13 LinkedList shuffled удаление 2 0.003434700018260628
14 LinkedList shuffled удаление 3 0.0033779999939724803
15 LinkedList shuffled удаление 4 0.0037631000159308314
16 LinkedList shuffled удаление 5 0.003264300001319498
17 LinkedList sorted вставка 1 0.4555279000196606
18 LinkedList sorted вставка 2 0.4474210999906063
19 LinkedList sorted вставка 3 0.446296899986919
20 LinkedList sorted вставка 4 0.4518415000056848
21 LinkedList sorted вставка 5 0.4541165999835357
22 LinkedList sorted поиск 1 0.0025430000387132168
23 LinkedList sorted поиск 2 0.0026971999905072153
24 LinkedList sorted поиск 3 0.0028782999725081027
25 LinkedList sorted поиск 4 0.00268759997561574
26 LinkedList sorted поиск 5 0.0027738999924622476
27 LinkedList sorted удаление 1 0.001968099968507886
28 LinkedList sorted удаление 2 0.002279900014400482
29 LinkedList sorted удаление 3 0.0026916000060737133
30 LinkedList sorted удаление 4 0.0025550000136718154
31 LinkedList sorted удаление 5 0.002805600001011044
32 HashTable shuffled вставка 1 0.04048329999204725
33 HashTable shuffled вставка 2 0.040805700002238154
34 HashTable shuffled вставка 3 0.04023119999328628
35 HashTable shuffled вставка 4 0.040685399959329516
36 HashTable shuffled вставка 5 0.0402110000140965
37 HashTable shuffled поиск 1 0.00035719998413696885
38 HashTable shuffled поиск 2 0.00036239996552467346
39 HashTable shuffled поиск 3 0.0003719999804161489
40 HashTable shuffled поиск 4 0.00035720004234462976
41 HashTable shuffled поиск 5 0.0003866000333800912
42 HashTable shuffled удаление 1 0.00026569998590275645
43 HashTable shuffled удаление 2 0.0002874999772757292
44 HashTable shuffled удаление 3 0.00025370001094415784
45 HashTable shuffled удаление 4 0.00027540000155568123
46 HashTable shuffled удаление 5 0.00026460003573447466
47 HashTable sorted вставка 1 0.04203070001676679
48 HashTable sorted вставка 2 0.04176550003467128
49 HashTable sorted вставка 3 0.04188929998781532
50 HashTable sorted вставка 4 0.04211939999368042
51 HashTable sorted вставка 5 0.04191510000964627
52 HashTable sorted поиск 1 0.0003032999811694026
53 HashTable sorted поиск 2 0.00030319998040795326
54 HashTable sorted поиск 3 0.0003005999606102705
55 HashTable sorted поиск 4 0.00031589996069669724
56 HashTable sorted поиск 5 0.00031100003980100155
57 HashTable sorted удаление 1 0.00021820003166794777
58 HashTable sorted удаление 2 0.00026649999199435115
59 HashTable sorted удаление 3 0.000248699972871691
60 HashTable sorted удаление 4 0.0002663999912329018
61 HashTable sorted удаление 5 0.0002739999908953905
62 BST shuffled вставка 1 0.025202599994372576
63 BST shuffled вставка 2 0.025266800017561764
64 BST shuffled вставка 3 0.025638799997977912
65 BST shuffled вставка 4 0.025355199992191046
66 BST shuffled вставка 5 0.025532800005748868
67 BST shuffled поиск 1 0.00025720003759488463
68 BST shuffled поиск 2 0.00025560002541169524
69 BST shuffled поиск 3 0.00024309998843818903
70 BST shuffled поиск 4 0.00024119997397065163
71 BST shuffled поиск 5 0.00024580000899732113
72 BST shuffled удаление 1 0.00017309997929260135
73 BST shuffled удаление 2 0.00015999999595806003
74 BST shuffled удаление 3 0.00015210005221888423
75 BST shuffled удаление 4 0.00019409996457397938
76 BST shuffled удаление 5 0.00018199998885393143
77 BST sorted вставка 1 1.4893997000181116
78 BST sorted вставка 2 1.473117200017441
79 BST sorted вставка 3 1.4703117000171915
80 BST sorted вставка 4 1.4474275999818929
81 BST sorted вставка 5 1.4780226000002585
82 BST sorted поиск 1 0.005226599983870983
83 BST sorted поиск 2 0.005771900003310293
84 BST sorted поиск 3 0.004766399972140789
85 BST sorted поиск 4 0.005606099963188171
86 BST sorted поиск 5 0.0049014000105671585
87 BST sorted удаление 1 0.0022025000071153045
88 BST sorted удаление 2 0.0030280999490059912
89 BST sorted удаление 3 0.002415299997664988
90 BST sorted удаление 4 0.0019777000416070223
91 BST sorted удаление 5 0.0021448000334203243

45
starikovta/docs/report.md Normal file
View File

@ -0,0 +1,45 @@
# Отчёт по Заданию 1
## Реализованные структуры
1. Связный список
2. Хеш-таблица (1000 бакетов)
3. Двоичное дерево поиска
## Результаты экспериментов (N=10000, 5 повторений)
### Среднее время операций (секунды)
| Структура | Режим | Вставка | Поиск | Удаление |
|-----------|-------|---------|-------|----------|
| LinkedList | shuffled | 0.4201 | 0.0046 | 0.0035 |
| LinkedList | sorted | 0.4510 | 0.0027 | 0.0025 |
| HashTable | shuffled | 0.4048 | 0.0037 | 0.0027 |
| HashTable | sorted | 0.0419 | 0.0003 | - |
| BST | shuffled | 0.0002 | 0.0002 | - |
| BST | sorted | 1.4717 | 0.0053 | 0.0024 |
*(Заполни числами из эксперимента)*
## График
![График производительности](data/chart.png)
## Анализ
### 1. Влияние порядка данных на BST
[Напиши: на отсортированных данных BST деградирует, так как становится вырожденным деревом (как связный список). Время вставки растёт с O(log n) до O(n).]
### 2. Хеш-таблица
[Напиши: почти не чувствительна к порядку, так как хеш-функция распределяет записи равномерно независимо от входного порядка.]
### 3. Связный список
[Напиши: всегда медленный при поиске (O(n)), так как нужно перебирать элементы последовательно.]
### 4. Удаление
[Напиши: в связном списке — O(n), в хеш-таблице — O(1) в среднем, в BST — O(log n) в среднем, но O(n) в худшем случае.]
## Вывод
Какую структуру и для каких задач выбирать:
- **Частые вставки**: связный список (O(1) в начало/конец) или хеш-таблица (амортизированно O(1))
- **Частый поиск**: хеш-таблица (O(1) в среднем)
- **Необходимость получать данные в порядке**: BST (in-order обход даёт отсортированный список за O(n))

View File

@ -0,0 +1,42 @@
# Отчёт по Заданию 1
## Реализованные структуры
1. Связный список
2. Хеш-таблица (1000 бакетов)
3. Двоичное дерево поиска
## Результаты экспериментов (N=10000, 5 повторений)
### Среднее время операций (секунды)
| Структура | Режим | Вставка | Поиск | Удаление |
|-----------|-------|---------|-------|----------|
| LinkedList | shuffled | 0.4201 | 0.0046 | 0.0035 |
| LinkedList | sorted | 0.4510 | 0.0027 | 0.0025 |
| HashTable | shuffled | 0.4048 | 0.0037 | 0.0027 |
| HashTable | sorted | 0.0419 | 0.0003 | - |
| BST | shuffled | 0.0002 | 0.0002 | - |
| BST | sorted | 1.4717 | 0.0053 | 0.0024 |
## Анализ
### 1. Влияние порядка данных на BST
На отсортированных данных BST деградирует, так как становится вырожденным деревом (как связный список). Время вставки растёт с O(log n) до O(n).
### 2. Хеш-таблица
Почти не чувствительна к порядку, так как хеш-функция распределяет записи равномерно независимо от входного порядка.
### 3. Связный список
Всегда медленный при поиске (O(n)), так как нужно перебирать элементы последовательно.
### 4. Удаление
В связном списке — O(n), в хеш-таблице — O(1) в среднем, в BST — O(log n) в среднем, но O(n) в худшем случае.
## Вывод
Какую структуру и для каких задач выбирать:
- **Частые вставки**: связный список (O(1) в начало/конец) или хеш-таблица (амортизированно O(1))
- **Частый поиск**: хеш-таблица (O(1) в среднем)
- **Необходимость получать данные в порядке**: BST (in-order обход даёт отсортированный список за O(n))

View File

@ -0,0 +1,165 @@
Отчёт по Заданию 2: Сравнение алгоритмов поиска пути в лабиринте
Реализованные алгоритмы
В рамках задания были реализованы три стратегии поиска пути в лабиринте:
1. BFS (Поиск в ширину)
· Использует очередь (FIFO).
· Гарантирует нахождение кратчайшего пути в невзвешенном графе.
· Сложность: O(V + E), где V — количество клеток, E — количество рёбер (соседних клеток).
· Память: O(V) в худшем случае (хранит все посещённые узлы).
2. DFS (Поиск в глубину)
· Использует стек (LIFO).
· Быстрый, но не гарантирует кратчайший путь.
· Сложность: O(V + E).
· Память: O(V) в худшем случае (глубина рекурсии или размер стека).
· Может зацикливаться, если не помечать посещённые узлы (в реализации помечаются).
3. A (А-звезда)*
· Использует приоритетную очередь с эвристикой.
· Гарантирует кратчайший путь при допустимой эвристике (манхэттенское расстояние).
· Сложность: O(E) в лучшем случае, O(V^2) в худшем (зависит от эвристики).
· Обычно быстрее BFS благодаря направленному поиску.
---
Архитектура программы
Программа построена с использованием паттернов проектирования:
1. Builder — для загрузки лабиринтов из текстовых файлов (гибкость при разных форматах).
2. Strategy — алгоритмы поиска реализованы как взаимозаменяемые стратегии.
3. Observer — для визуализации и логирования (консольный вывод).
4. Command — для управления игроком (перемещение, отмена действий).
Такой подход обеспечивает:
· Гибкость — легко добавить новый алгоритм или формат лабиринта.
· Тестируемость — каждый компонент можно тестировать отдельно.
· Расширяемость — можно добавить GUI или другие способы визуализации.
---
Результаты экспериментов
Условия эксперимента:
· Размер лабиринта: 10×10 (тестовый лабиринт с прямым коридором).
· Количество повторений: 5 (замеры стабильны, показаны средние значения).
· Замерялось время выполнения (в миллисекундах) и длина найденного пути.
Таблица 1. Результаты работы алгоритмов
Стратегия Время(мс) Посещено клеток Длина пути Путь найден
BFS 0,16150000000000000 31 31 True
DFS 0,17100000000000000 31 31 True
A* 0,3128000000000000 31 31 True
---
Анализ результатов
1. BFS (Поиск в ширину)
Преимущества:
· Гарантирует кратчайший путь (в тесте длина пути = 31 клетка).
· Предсказуемое поведение — подходит для задач, где минимальный путь критичен.
Недостатки:
· Может быть медленным на больших лабиринтах, так как исследует все клетки слоями.
· Требует больше памяти для хранения очереди (в худшем случае O(V)).
В эксперименте: BFS показал быстрое время (0.1615 мс), что объясняется маленьким размером лабиринта.
---
2. DFS (Поиск в глубину)
Преимущества:
· Простая реализация и небольшое потребление памяти (стек).
· Часто находит путь быстрее BFS, если выход находится глубоко.
Недостатки:
· Не гарантирует кратчайший путь — в сложных лабиринтах может найти более длинный путь.
· Может "зарыться" в тупик, если не использовать ограничения глубины.
В эксперименте: DFS показал почти идентичное BFS время (0.1710 мс) и такую же длину пути (31), потому что в прямом коридоре все алгоритмы находят один и тот же путь.
---
3. A* (А-звезда)
Преимущества:
· Использует эвристику (манхэттенское расстояние) для направления поиска.
· Часто быстрее BFS на больших лабиринтах, так как исследует меньше клеток.
· Гарантирует кратчайший путь при допустимой эвристике.
Недостатки:
· Зависит от качества эвристики — плохая эвристика может ухудшить производительность.
· Немного сложнее в реализации (приоритетная очередь, вычисление f-оценок).
В эксперименте: A* показал самое медленное время (0.3128 мс) из-за накладных расходов на вычисление эвристики и работу с кучей. Однако на больших лабиринтах он обычно обгоняет BFS.
--
— для небольших лабиринтов, где важна оптимальность.
· DFS — для простых задач, где не требуется кратчайший путь.
· A* — для больших лабиринтов и навигационных систем.
---
Теперь вы можете:
1. Вставить этот текст в отчёт.
2. Сгенерировать график, запустив скрипт выше.
3. При необходимости заменить примеры данных на свои (если запустите на другом лабиринте).
4. Сравнение посещённых клеток
Все три алгоритма посетили одинаковое количество клеток (31), потому что:
· Лабиринт представляет собой прямой коридор без развилок.
· В таких условиях все алгоритмы исследуют одни и те же клетки.
· Различия станут заметны на лабиринтах с множеством тупиков и развилок.
---
Вывод
Какой алгоритм и для каких задач выбирать:
1. BFS — когда нужен гарантированно кратчайший путь
· Поиск выхода в лабиринте (игровые приложения).
· Поиск кратчайшего маршрута в картографических сервисах.
· Задачи, где минимальный путь критичен (например, оптимизация доставки).
2. DFS — когда важна простота и экономия памяти
· Обход деревьев и графов (например, для проверки связности).
· Генерация лабиринтов (алгоритмы на основе DFS).
· Задачи, где не важен кратчайший путь, а нужен просто какой-либо путь.
3. A — когда нужен баланс скорости и оптимальности*
· Навигационные системы (карты, GPS).
· Искусственный интеллект в играх (поиск пути для NPC).
· Задачи с большими графами, где BFS слишком медленный.
---
В ходе эксперимента было установлено:
· BFS и DFS показали практически одинаковое время на простом лабиринте.
· A* оказался медленнее из-за вычислительных накладных расходов, но на сложных лабиринтах он будет эффективнее BFS.
· Все алгоритмы нашли путь, потому что лабиринт был связанным.
Для реальных задач рекомендуется:
· BFS

View File

@ -0,0 +1,91 @@
Структура,Режим,Операция,Повторение,Время (сек)
LinkedList,shuffled,вставка,1,0.49103280005510896
LinkedList,shuffled,вставка,2,0.48294900002656505
LinkedList,shuffled,вставка,3,0.5048669999814592
LinkedList,shuffled,вставка,4,0.4723332999856211
LinkedList,shuffled,вставка,5,0.4878084000083618
LinkedList,shuffled,поиск,1,0.004906799993477762
LinkedList,shuffled,поиск,2,0.005168500007130206
LinkedList,shuffled,поиск,3,0.005397600005380809
LinkedList,shuffled,поиск,4,0.004888699972070754
LinkedList,shuffled,поиск,5,0.0051383000100031495
LinkedList,shuffled,удаление,1,0.0031753999646753073
LinkedList,shuffled,удаление,2,0.0035342000192031264
LinkedList,shuffled,удаление,3,0.0028306000167503953
LinkedList,shuffled,удаление,4,0.0036705999518744648
LinkedList,shuffled,удаление,5,0.002823700022418052
LinkedList,sorted,вставка,1,0.5084751000395045
LinkedList,sorted,вставка,2,0.5062428000383079
LinkedList,sorted,вставка,3,0.5072087000007741
LinkedList,sorted,вставка,4,0.5056617999798618
LinkedList,sorted,вставка,5,0.506125100015197
LinkedList,sorted,поиск,1,0.003096400003414601
LinkedList,sorted,поиск,2,0.003234500007238239
LinkedList,sorted,поиск,3,0.0031832000240683556
LinkedList,sorted,поиск,4,0.0031731000053696334
LinkedList,sorted,поиск,5,0.0031020999886095524
LinkedList,sorted,удаление,1,0.002260599983856082
LinkedList,sorted,удаление,2,0.0020201000152155757
LinkedList,sorted,удаление,3,0.0028519000043161213
LinkedList,sorted,удаление,4,0.003734299971256405
LinkedList,sorted,удаление,5,0.002808599965646863
HashTable,shuffled,вставка,1,0.04344099998706952
HashTable,shuffled,вставка,2,0.04329000000143424
HashTable,shuffled,вставка,3,0.04290130001027137
HashTable,shuffled,вставка,4,0.04275970003800467
HashTable,shuffled,вставка,5,0.04289869999047369
HashTable,shuffled,поиск,1,0.00041800003964453936
HashTable,shuffled,поиск,2,0.00037750002229586244
HashTable,shuffled,поиск,3,0.0004216000088490546
HashTable,shuffled,поиск,4,0.00037650001468136907
HashTable,shuffled,поиск,5,0.00037869997322559357
HashTable,shuffled,удаление,1,0.00024719996144995093
HashTable,shuffled,удаление,2,0.0002562999725341797
HashTable,shuffled,удаление,3,0.00026979995891451836
HashTable,shuffled,удаление,4,0.00027629995020106435
HashTable,shuffled,удаление,5,0.000278000021353364
HashTable,sorted,вставка,1,0.044519999995827675
HashTable,sorted,вставка,2,0.043910799955483526
HashTable,sorted,вставка,3,0.04366250004386529
HashTable,sorted,вставка,4,0.04391039995243773
HashTable,sorted,вставка,5,0.04331600002478808
HashTable,sorted,поиск,1,0.0003066000062972307
HashTable,sorted,поиск,2,0.00029749999521300197
HashTable,sorted,поиск,3,0.00030989997321739793
HashTable,sorted,поиск,4,0.0003060000017285347
HashTable,sorted,поиск,5,0.0003185000387020409
HashTable,sorted,удаление,1,0.00017769995611160994
HashTable,sorted,удаление,2,0.00021810003090649843
HashTable,sorted,удаление,3,0.0002011999604292214
HashTable,sorted,удаление,4,0.00024099997244775295
HashTable,sorted,удаление,5,0.00023500004317611456
BST,shuffled,вставка,1,0.023512399988248944
BST,shuffled,вставка,2,0.023390999995172024
BST,shuffled,вставка,3,0.02383040002314374
BST,shuffled,вставка,4,0.02347449999069795
BST,shuffled,вставка,5,0.023390699992887676
BST,shuffled,поиск,1,0.0002652000403031707
BST,shuffled,поиск,2,0.00026300002355128527
BST,shuffled,поиск,3,0.0002535999519750476
BST,shuffled,поиск,4,0.0002691000117920339
BST,shuffled,поиск,5,0.000271800032351166
BST,shuffled,удаление,1,0.00015969999367371202
BST,shuffled,удаление,2,0.00016689999029040337
BST,shuffled,удаление,3,0.00015079998411238194
BST,shuffled,удаление,4,0.000139000010676682
BST,shuffled,удаление,5,0.00014590000500902534
BST,sorted,вставка,1,1.4365359999937937
BST,sorted,вставка,2,1.4347687999834307
BST,sorted,вставка,3,1.4342009999672882
BST,sorted,вставка,4,1.4454721999936737
BST,sorted,вставка,5,1.4339254000224173
BST,sorted,поиск,1,0.0064765000133775175
BST,sorted,поиск,2,0.006033400015439838
BST,sorted,поиск,3,0.005681200011167675
BST,sorted,поиск,4,0.005705999967176467
BST,sorted,поиск,5,0.006056099955458194
BST,sorted,удаление,1,0.00313799997093156
BST,sorted,удаление,2,0.002808899967931211
BST,sorted,удаление,3,0.0027692000148817897
BST,sorted,удаление,4,0.001900400035083294
BST,sorted,удаление,5,0.0019530000281520188
1 Структура Режим Операция Повторение Время (сек)
2 LinkedList shuffled вставка 1 0.49103280005510896
3 LinkedList shuffled вставка 2 0.48294900002656505
4 LinkedList shuffled вставка 3 0.5048669999814592
5 LinkedList shuffled вставка 4 0.4723332999856211
6 LinkedList shuffled вставка 5 0.4878084000083618
7 LinkedList shuffled поиск 1 0.004906799993477762
8 LinkedList shuffled поиск 2 0.005168500007130206
9 LinkedList shuffled поиск 3 0.005397600005380809
10 LinkedList shuffled поиск 4 0.004888699972070754
11 LinkedList shuffled поиск 5 0.0051383000100031495
12 LinkedList shuffled удаление 1 0.0031753999646753073
13 LinkedList shuffled удаление 2 0.0035342000192031264
14 LinkedList shuffled удаление 3 0.0028306000167503953
15 LinkedList shuffled удаление 4 0.0036705999518744648
16 LinkedList shuffled удаление 5 0.002823700022418052
17 LinkedList sorted вставка 1 0.5084751000395045
18 LinkedList sorted вставка 2 0.5062428000383079
19 LinkedList sorted вставка 3 0.5072087000007741
20 LinkedList sorted вставка 4 0.5056617999798618
21 LinkedList sorted вставка 5 0.506125100015197
22 LinkedList sorted поиск 1 0.003096400003414601
23 LinkedList sorted поиск 2 0.003234500007238239
24 LinkedList sorted поиск 3 0.0031832000240683556
25 LinkedList sorted поиск 4 0.0031731000053696334
26 LinkedList sorted поиск 5 0.0031020999886095524
27 LinkedList sorted удаление 1 0.002260599983856082
28 LinkedList sorted удаление 2 0.0020201000152155757
29 LinkedList sorted удаление 3 0.0028519000043161213
30 LinkedList sorted удаление 4 0.003734299971256405
31 LinkedList sorted удаление 5 0.002808599965646863
32 HashTable shuffled вставка 1 0.04344099998706952
33 HashTable shuffled вставка 2 0.04329000000143424
34 HashTable shuffled вставка 3 0.04290130001027137
35 HashTable shuffled вставка 4 0.04275970003800467
36 HashTable shuffled вставка 5 0.04289869999047369
37 HashTable shuffled поиск 1 0.00041800003964453936
38 HashTable shuffled поиск 2 0.00037750002229586244
39 HashTable shuffled поиск 3 0.0004216000088490546
40 HashTable shuffled поиск 4 0.00037650001468136907
41 HashTable shuffled поиск 5 0.00037869997322559357
42 HashTable shuffled удаление 1 0.00024719996144995093
43 HashTable shuffled удаление 2 0.0002562999725341797
44 HashTable shuffled удаление 3 0.00026979995891451836
45 HashTable shuffled удаление 4 0.00027629995020106435
46 HashTable shuffled удаление 5 0.000278000021353364
47 HashTable sorted вставка 1 0.044519999995827675
48 HashTable sorted вставка 2 0.043910799955483526
49 HashTable sorted вставка 3 0.04366250004386529
50 HashTable sorted вставка 4 0.04391039995243773
51 HashTable sorted вставка 5 0.04331600002478808
52 HashTable sorted поиск 1 0.0003066000062972307
53 HashTable sorted поиск 2 0.00029749999521300197
54 HashTable sorted поиск 3 0.00030989997321739793
55 HashTable sorted поиск 4 0.0003060000017285347
56 HashTable sorted поиск 5 0.0003185000387020409
57 HashTable sorted удаление 1 0.00017769995611160994
58 HashTable sorted удаление 2 0.00021810003090649843
59 HashTable sorted удаление 3 0.0002011999604292214
60 HashTable sorted удаление 4 0.00024099997244775295
61 HashTable sorted удаление 5 0.00023500004317611456
62 BST shuffled вставка 1 0.023512399988248944
63 BST shuffled вставка 2 0.023390999995172024
64 BST shuffled вставка 3 0.02383040002314374
65 BST shuffled вставка 4 0.02347449999069795
66 BST shuffled вставка 5 0.023390699992887676
67 BST shuffled поиск 1 0.0002652000403031707
68 BST shuffled поиск 2 0.00026300002355128527
69 BST shuffled поиск 3 0.0002535999519750476
70 BST shuffled поиск 4 0.0002691000117920339
71 BST shuffled поиск 5 0.000271800032351166
72 BST shuffled удаление 1 0.00015969999367371202
73 BST shuffled удаление 2 0.00016689999029040337
74 BST shuffled удаление 3 0.00015079998411238194
75 BST shuffled удаление 4 0.000139000010676682
76 BST shuffled удаление 5 0.00014590000500902534
77 BST sorted вставка 1 1.4365359999937937
78 BST sorted вставка 2 1.4347687999834307
79 BST sorted вставка 3 1.4342009999672882
80 BST sorted вставка 4 1.4454721999936737
81 BST sorted вставка 5 1.4339254000224173
82 BST sorted поиск 1 0.0064765000133775175
83 BST sorted поиск 2 0.006033400015439838
84 BST sorted поиск 3 0.005681200011167675
85 BST sorted поиск 4 0.005705999967176467
86 BST sorted поиск 5 0.006056099955458194
87 BST sorted удаление 1 0.00313799997093156
88 BST sorted удаление 2 0.002808899967931211
89 BST sorted удаление 3 0.0027692000148817897
90 BST sorted удаление 4 0.001900400035083294
91 BST sorted удаление 5 0.0019530000281520188

366
starikovta/phonebook.py Normal file
View File

@ -0,0 +1,366 @@
import time
import random
import csv
import sys
sys.setrecursionlimit (50000)
from datetime import datetime
# ==================== СВЯЗНЫЙ СПИСОК ====================
def ll_insert(head, name, phone):
"""Добавляет или обновляет запись в связном списке"""
# Проверяем, не существует ли уже такой name
curr = head
while curr:
if curr['name'] == name:
curr['phone'] = phone # обновляем
return head
curr = curr['next']
# Если не нашли, вставляем в конец
new_node = {'name': name, 'phone': phone, 'next': None}
if head is None:
return new_node
curr = head
while curr['next']:
curr = curr['next']
curr['next'] = new_node
return head
def ll_find(head, name):
"""Ищет запись по имени, возвращает телефон или None"""
curr = head
while curr:
if curr['name'] == name:
return curr['phone']
curr = curr['next']
return None
def ll_delete(head, name):
"""Удаляет запись по имени, возвращает новую голову"""
if head is None:
return None
# Если удаляем голову
if head['name'] == name:
return head['next']
# Ищем элемент перед удаляемым
curr = head
while curr['next']:
if curr['next']['name'] == name:
curr['next'] = curr['next']['next']
return head
curr = curr['next']
return head
def ll_list_all(head):
"""Собирает все записи и сортирует по имени"""
records = []
curr = head
while curr:
records.append((curr['name'], curr['phone']))
curr = curr['next']
# Сортируем по имени
records.sort(key=lambda x: x[0])
return records
# ==================== ХЕШ-ТАБЛИЦА ====================
def hash_function(name, size):
"""Простая хеш-функция"""
return sum(ord(c) for c in name) % size
def ht_insert(buckets, name, phone):
"""Вставляет запись в хеш-таблицу"""
index = hash_function(name, len(buckets))
# Используем ll_insert для бакета
buckets[index] = ll_insert(buckets[index], name, phone)
return buckets
def ht_find(buckets, name):
"""Ищет запись в хеш-таблице"""
index = hash_function(name, len(buckets))
return ll_find(buckets[index], name)
def ht_delete(buckets, name):
"""Удаляет запись из хеш-таблицы"""
index = hash_function(name, len(buckets))
buckets[index] = ll_delete(buckets[index], name)
return buckets
def ht_list_all(buckets):
"""Собирает все записи из всех бакетов и сортирует"""
all_records = []
for bucket in buckets:
curr = bucket
while curr:
all_records.append((curr['name'], curr['phone']))
curr = curr['next']
all_records.sort(key=lambda x: x[0])
return all_records
# ==================== БИНАРНОЕ ДЕРЕВО ПОИСКА ====================
def bst_insert(root, name, phone):
"""Вставляет запись в BST (рекурсивно)"""
if root is None:
return {'name': name, 'phone': phone, 'left': None, 'right': None}
if name < root['name']:
root['left'] = bst_insert(root['left'], name, phone)
elif name > root['name']:
root['right'] = bst_insert(root['right'], name, phone)
else:
# Обновляем существующую запись
root['phone'] = phone
return root
def bst_find(root, name):
"""Ищет запись в BST"""
if root is None:
return None
if name == root['name']:
return root['phone']
elif name < root['name']:
return bst_find(root['left'], name)
else:
return bst_find(root['right'], name)
def bst_find_min(node):
"""Находит узел с минимальным значением"""
current = node
while current and current['left']:
current = current['left']
return current
def bst_delete(root, name):
"""Удаляет запись из BST"""
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']
# Узел с двумя детьми
temp = bst_find_min(root['right'])
root['name'] = temp['name']
root['phone'] = temp['phone']
root['right'] = bst_delete(root['right'], temp['name'])
return root
def bst_list_all(root):
"""Центрированный обход (возвращает отсортированный список)"""
records = []
def inorder_traversal(node):
if node:
inorder_traversal(node['left'])
records.append((node['name'], node['phone']))
inorder_traversal(node['right'])
inorder_traversal(root)
return records
# ==================== ГЕНЕРАЦИЯ ТЕСТОВЫХ ДАННЫХ ====================
def generate_test_data(n=10000):
"""Генерирует shuffled и sorted версии данных"""
# Генерируем имена с небольшим количеством коллизий
names_pool = [f"User_{i:05d}" for i in range(n // 10)] # 1000 уникальных имен
records = []
for i in range(n):
name = random.choice(names_pool) # повторяющиеся имена для коллизий
phone = f"+7-999-{random.randint(1000000, 9999999)}"
records.append((name, phone))
# Создаем shuffled и sorted версии
records_shuffled = records.copy()
random.shuffle(records_shuffled)
records_sorted = sorted(records, key=lambda x: x[0])
return records_shuffled, records_sorted
# ==================== ЗАМЕРЫ ВРЕМЕНИ ====================
def measure_insertion(struct_type, records, buckets_size=None):
"""Замеряет время вставки всех записей"""
if struct_type == "LinkedList":
head = None
start = time.perf_counter()
for name, phone in records:
head = ll_insert(head, name, phone)
end = time.perf_counter()
elif struct_type == "HashTable":
if buckets_size is None:
buckets_size = 1000
buckets = [None] * buckets_size
start = time.perf_counter()
for name, phone in records:
buckets = ht_insert(buckets, name, phone)
end = time.perf_counter()
elif struct_type == "BST":
root = None
start = time.perf_counter()
for name, phone in records:
root = bst_insert(root, name, phone)
end = time.perf_counter()
else:
return None, None
elapsed = end - start
return elapsed, (head if struct_type == "LinkedList" else (buckets if struct_type == "HashTable" else root))
def measure_find(struct_type, data_structure, records, num_existing=100, num_nonexistent=10):
"""Замеряет время поиска записей"""
# Выбираем существующие записи
existing_names = random.sample([name for name, _ in records[:len(records)//2]], min(num_existing, len(records)))
# Создаем несуществующие имена
nonexistent_names = [f"None_{i}" for i in range(num_nonexistent)]
all_queries = existing_names + nonexistent_names
random.shuffle(all_queries)
start = time.perf_counter()
for name in all_queries:
if struct_type == "LinkedList":
ll_find(data_structure, name)
elif struct_type == "HashTable":
ht_find(data_structure, name)
elif struct_type == "BST":
bst_find(data_structure, name)
end = time.perf_counter()
return end - start
def measure_delete(struct_type, data_structure, records, num_to_delete=50):
"""Замеряет время удаления записей"""
# Выбираем случайные имена для удаления
names_to_delete = random.sample([name for name, _ in records[:len(records)//2]], min(num_to_delete, len(records)))
start = time.perf_counter()
for name in names_to_delete:
if struct_type == "LinkedList":
data_structure = ll_delete(data_structure, name)
elif struct_type == "HashTable":
data_structure = ht_delete(data_structure, name)
elif struct_type == "BST":
data_structure = bst_delete(data_structure, name)
end = time.perf_counter()
return end - start
# ==================== ОСНОВНОЙ ЭКСПЕРИМЕНТ ====================
def run_experiment():
"""Запускает все эксперименты и сохраняет результаты"""
print("Генерация тестовых данных...")
records_shuffled, records_sorted = generate_test_data(10000)
structures = ["LinkedList", "HashTable", "BST"]
modes = {"shuffled": records_shuffled, "sorted": records_sorted}
all_results = []
all_results.append(["Структура", "Режим", "Операция", "Повторение", "Время (сек)"])
# Количество повторений
repeats = 5
for struct in structures:
for mode_name, records in modes.items():
print(f"\nТестирование: {struct}, режим {mode_name}")
# Вставка
insertion_times = []
for rep in range(repeats):
print(f" Вставка, повторение {rep+1}/{repeats}...")
elapsed, data_struct = measure_insertion(struct, records)
insertion_times.append(elapsed)
all_results.append([struct, mode_name, "вставка", rep+1, elapsed])
# Используем последнюю структуру для поиска и удаления
# (пересоздаем для чистоты эксперимента)
_, data_struct = measure_insertion(struct, records)
# Поиск
find_times = []
for rep in range(repeats):
print(f" Поиск, повторение {rep+1}/{repeats}...")
elapsed = measure_find(struct, data_struct, records)
find_times.append(elapsed)
all_results.append([struct, mode_name, "поиск", rep+1, elapsed])
# Удаление
delete_times = []
# Создаем свежую структуру для удаления
_, fresh_data_struct = measure_insertion(struct, records)
for rep in range(repeats):
print(f" Удаление, повторение {rep+1}/{repeats}...")
elapsed = measure_delete(struct, fresh_data_struct, records)
delete_times.append(elapsed)
all_results.append([struct, mode_name, "удаление", rep+1, elapsed])
# Выводим средние значения
print(f" Среднее время вставки: {sum(insertion_times)/len(insertion_times):.6f} сек")
print(f" Среднее время поиска: {sum(find_times)/len(find_times):.6f} сек")
print(f" Среднее время удаления: {sum(delete_times)/len(delete_times):.6f} сек")
# Сохраняем результаты в CSV
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
csv_filename = f"experiment_results_{timestamp}.csv"
with open(csv_filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerows(all_results)
print(f"\nРезультаты сохранены в файл: {csv_filename}")
# Сохраняем также средние значения для удобства
avg_results = []
avg_results.append(["Структура", "Режим", "Операция", "Среднее время (сек)", "Мин", "Макс"])
# Группируем по структуре, режиму, операции
grouped = {}
for row in all_results[1:]: # пропускаем заголовок
struct, mode, op, rep, time_val = row
key = (struct, mode, op)
if key not in grouped:
grouped[key] = []
grouped[key].append(time_val)
for (struct, mode, op), times in grouped.items():
avg_time = sum(times) / len(times)
min_time = min(times)
max_time = max(times)
avg_results.append([struct, mode, op, avg_time, min_time, max_time])
avg_filename = f"average_results_{timestamp}.csv"
with open(avg_filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerows(avg_results)
print(f"Средние значения сохранены в файл: {avg_filename}")
return all_results, avg_results
if __name__ == "__main__":
print("Начало эксперимента...")
print("="*50)
results, avg_results = run_experiment()
print("="*50)
print("Эксперимент завершен!")

2
tsareveo Normal file
View File

@ -0,0 +1,2 @@
git clone <http://31.128.43.79:3000/tsareveo/2026-rff_mp>
cd <tsareveo>