1
0
forked from UNN/2026-rff_mp

Добавлены лабораторные работы 1 и 2

This commit is contained in:
PaulVA 2026-09-05 19:09:46 +03:00
parent 58d0447aae
commit c12f9ef1bf
24 changed files with 1344 additions and 0 deletions

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

34
lab1/docs/report.md Normal file
View File

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

185
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
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
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
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* хорошо показывает себя на больших и открытых лабиринтах

93
lab2/experiments.py Normal file
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
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
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
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)}")

20
lab2/mazes/dead.txt Normal file
View File

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

50
lab2/mazes/empty.txt Normal file
View File

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

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

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

10
lab2/mazes/noexit.txt Normal file
View File

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

5
lab2/mazes/simple.txt Normal file
View File

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