Compare commits

..

No commits in common. "develop" and "Task2" have entirely different histories.

2300 changed files with 4 additions and 2506267 deletions

5
.idea/.gitignore vendored
View File

@ -1,5 +0,0 @@
# Игнорируемые файлы по умолчанию
/shelf/
/workspace.xml
# HTTP-клиент на основе редактора
/httpRequests/

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.14" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.14" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.14" project-jdk-type="Python SDK" />
</project>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/2026-rff_mp.iml" filepath="$PROJECT_DIR$/.idea/2026-rff_mp.iml" />
</modules>
</component>
</project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

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

View File

@ -1 +0,0 @@
428b

View File

@ -1,31 +0,0 @@
Structure,Mode,Repetition,Insert (sec),Find (sec),Delete (sec)
LinkedList,random,1,0.022518936000324175,0.0022566010002265102,0.00023215900000650436
LinkedList,random,2,0.020449046000067028,0.00233427400007713,0.0002826350000759703
LinkedList,random,3,0.020126345999869955,0.002262161000089691,0.0002053490002253966
LinkedList,random,4,0.018700520000038523,0.0021239550001155294,0.00021164700001463643
LinkedList,random,5,0.019754801000090083,0.0024997460000122373,0.00023158399972089683
HashTable,random,1,0.0029892609995840758,0.0003534970001055626,2.990000029967632e-05
HashTable,random,2,0.00424448100011432,0.00041066700032388326,4.1896999846358085e-05
HashTable,random,3,0.004175077000127203,0.0004925200000798213,2.9787000130454544e-05
HashTable,random,4,0.004567926000163425,0.00033349500017720857,3.164999998261919e-05
HashTable,random,5,0.0026621540000633104,0.00031125700024858816,2.81510001514107e-05
BST,random,1,0.0011096680000264314,9.346200022264384e-05,3.9635000121052144e-05
BST,random,2,0.0010248069997942366,9.059800004251883e-05,2.1142000150575768e-05
BST,random,3,0.0010313749999113497,9.237799986294704e-05,1.8248000287712784e-05
BST,random,4,0.0010483440000825794,9.526299982098863e-05,0.0001046150000547641
BST,random,5,0.0010863010002140072,8.332400011568097e-05,1.7150000076071592e-05
LinkedList,sorted,1,0.022635587000422674,0.0023248429997693165,0.00031860600029176567
LinkedList,sorted,2,0.02307340400011526,0.002558939999744325,0.00023637899994355394
LinkedList,sorted,3,0.01936496400003307,0.002565375999893149,0.00019060200020248885
LinkedList,sorted,4,0.018800279000060982,0.0021075829999972484,0.00018179399967266363
LinkedList,sorted,5,0.019234453000080975,0.002213534000020445,0.0002037049998762086
HashTable,sorted,1,0.0031771270000717777,0.0006652990000475256,6.005399973219028e-05
HashTable,sorted,2,0.004056166999816924,0.00058001300021715,3.078500003539375e-05
HashTable,sorted,3,0.00331192200019359,0.00027228699991610483,2.684700029931264e-05
HashTable,sorted,4,0.002749069000401505,0.0002677190000213159,2.1935999939159956e-05
HashTable,sorted,5,0.002638604000367195,0.0003338189999340102,1.83020001713885e-05
BST,sorted,1,0.037994623000031424,0.0035877690002052987,0.0011276019999968412
BST,sorted,2,0.038043681000090146,0.0031297909999921103,0.00094645799981663
BST,sorted,3,0.03629641699990316,0.0027566620001380215,0.0012528499996733444
BST,sorted,4,0.03509285000018281,0.003267435999987356,0.0011822649998975976
BST,sorted,5,0.038755655999921146,0.0064144259999920905,0.0018493429997761268
1 Structure Mode Repetition Insert (sec) Find (sec) Delete (sec)
2 LinkedList random 1 0.022518936000324175 0.0022566010002265102 0.00023215900000650436
3 LinkedList random 2 0.020449046000067028 0.00233427400007713 0.0002826350000759703
4 LinkedList random 3 0.020126345999869955 0.002262161000089691 0.0002053490002253966
5 LinkedList random 4 0.018700520000038523 0.0021239550001155294 0.00021164700001463643
6 LinkedList random 5 0.019754801000090083 0.0024997460000122373 0.00023158399972089683
7 HashTable random 1 0.0029892609995840758 0.0003534970001055626 2.990000029967632e-05
8 HashTable random 2 0.00424448100011432 0.00041066700032388326 4.1896999846358085e-05
9 HashTable random 3 0.004175077000127203 0.0004925200000798213 2.9787000130454544e-05
10 HashTable random 4 0.004567926000163425 0.00033349500017720857 3.164999998261919e-05
11 HashTable random 5 0.0026621540000633104 0.00031125700024858816 2.81510001514107e-05
12 BST random 1 0.0011096680000264314 9.346200022264384e-05 3.9635000121052144e-05
13 BST random 2 0.0010248069997942366 9.059800004251883e-05 2.1142000150575768e-05
14 BST random 3 0.0010313749999113497 9.237799986294704e-05 1.8248000287712784e-05
15 BST random 4 0.0010483440000825794 9.526299982098863e-05 0.0001046150000547641
16 BST random 5 0.0010863010002140072 8.332400011568097e-05 1.7150000076071592e-05
17 LinkedList sorted 1 0.022635587000422674 0.0023248429997693165 0.00031860600029176567
18 LinkedList sorted 2 0.02307340400011526 0.002558939999744325 0.00023637899994355394
19 LinkedList sorted 3 0.01936496400003307 0.002565375999893149 0.00019060200020248885
20 LinkedList sorted 4 0.018800279000060982 0.0021075829999972484 0.00018179399967266363
21 LinkedList sorted 5 0.019234453000080975 0.002213534000020445 0.0002037049998762086
22 HashTable sorted 1 0.0031771270000717777 0.0006652990000475256 6.005399973219028e-05
23 HashTable sorted 2 0.004056166999816924 0.00058001300021715 3.078500003539375e-05
24 HashTable sorted 3 0.00331192200019359 0.00027228699991610483 2.684700029931264e-05
25 HashTable sorted 4 0.002749069000401505 0.0002677190000213159 2.1935999939159956e-05
26 HashTable sorted 5 0.002638604000367195 0.0003338189999340102 1.83020001713885e-05
27 BST sorted 1 0.037994623000031424 0.0035877690002052987 0.0011276019999968412
28 BST sorted 2 0.038043681000090146 0.0031297909999921103 0.00094645799981663
29 BST sorted 3 0.03629641699990316 0.0027566620001380215 0.0012528499996733444
30 BST sorted 4 0.03509285000018281 0.003267435999987356 0.0011822649998975976
31 BST sorted 5 0.038755655999921146 0.0064144259999920905 0.0018493429997761268

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

View File

@ -1,335 +0,0 @@
import random
import time
import sys
import csv
import os
import matplotlib.pyplot as plt
import numpy as np
sys.setrecursionlimit(10000)
ll_head = None
def ll_insert(name, phone):
global ll_head
cur = ll_head
while cur is not None:
if cur['name'] == name:
cur['phone'] = phone
return
cur = cur['next']
new_node = {'name': name, 'phone': phone, 'next': ll_head}
ll_head = new_node
def ll_find(name):
cur = ll_head
while cur is not None:
if cur['name'] == name:
return cur['phone']
cur = cur['next']
return None
def ll_delete(name):
global ll_head
if ll_head is None:
return
if ll_head['name'] == name:
ll_head = ll_head['next']
return
prev = ll_head
cur = ll_head['next']
while cur is not None:
if cur['name'] == name:
prev['next'] = cur['next']
return
prev = cur
cur = cur['next']
def ll_list_all():
records = []
cur = ll_head
while cur is not None:
records.append((cur['name'], cur['phone']))
cur = cur['next']
records.sort(key=lambda x: x[0])
return records
BUCKET_COUNT = 10
buckets = [None] * BUCKET_COUNT
def hash_func(name):
s = 0
for ch in name:
s += ord(ch)
return s % BUCKET_COUNT
def ht_insert(name, phone):
global buckets
idx = hash_func(name)
cur = buckets[idx]
while cur is not None:
if cur['name'] == name:
cur['phone'] = phone
return
cur = cur['next']
new_node = {'name': name, 'phone': phone, 'next': buckets[idx]}
buckets[idx] = new_node
def ht_find(name):
idx = hash_func(name)
cur = buckets[idx]
while cur is not None:
if cur['name'] == name:
return cur['phone']
cur = cur['next']
return None
def ht_delete(name):
global buckets
idx = hash_func(name)
head = buckets[idx]
if head is None:
return
if head['name'] == name:
buckets[idx] = head['next']
return
prev = head
cur = head['next']
while cur is not None:
if cur['name'] == name:
prev['next'] = cur['next']
return
prev = cur
cur = cur['next']
def ht_list_all():
all_records = []
for head in buckets:
cur = head
while cur is not None:
all_records.append((cur['name'], cur['phone']))
cur = cur['next']
all_records.sort(key=lambda x: x[0])
return all_records
bst_root = None
def bst_create_node(name, phone):
return {'name': name, 'phone': phone, 'left': None, 'right': None}
def bst_insert(name, phone):
global bst_root
if bst_root is None:
bst_root = bst_create_node(name, phone)
return
current = bst_root
while True:
if name == current['name']:
current['phone'] = phone
return
elif name < current['name']:
if current['left'] is None:
current['left'] = bst_create_node(name, phone)
return
current = current['left']
else:
if current['right'] is None:
current['right'] = bst_create_node(name, phone)
return
current = current['right']
def bst_find(name):
current = bst_root
while current is not None:
if name == current['name']:
return current['phone']
elif name < current['name']:
current = current['left']
else:
current = current['right']
return None
def find_min(node):
while node['left'] is not None:
node = node['left']
return node
def bst_delete(name):
global bst_root
def delete_rec(node):
if node is None:
return None
if name < node['name']:
node['left'] = delete_rec(node['left'])
elif name > node['name']:
node['right'] = delete_rec(node['right'])
else:
if node['left'] is None:
return node['right']
if node['right'] is None:
return node['left']
min_node = find_min(node['right'])
node['name'] = min_node['name']
node['phone'] = min_node['phone']
node['right'] = delete_rec(node['right'])
return node
bst_root = delete_rec(bst_root)
def bst_list_all():
result = []
def inorder(node):
if node is None:
return
inorder(node['left'])
result.append((node['name'], node['phone']))
inorder(node['right'])
inorder(bst_root)
return result
def generate_records(n):
records = []
for i in range(1, n+1):
name = f"User_{i:05d}"
phone = f"{random.randint(100,999)}-{random.randint(1000,9999)}"
records.append((name, phone))
return records
def run_experiment():
N = 1000
base = generate_records(N)
shuffled = base.copy()
random.shuffle(shuffled)
sorted_records = sorted(base, key=lambda x: x[0])
structures = [
('LinkedList', ll_insert, ll_find, ll_delete, ll_list_all),
('HashTable', ht_insert, ht_find, ht_delete, ht_list_all),
('BST', bst_insert, bst_find, bst_delete, bst_list_all)
]
all_results = [] # для CSV: список словарей
repeats = 5
for mode_name, data in [('random', shuffled), ('sorted', sorted_records)]:
for struct_name, ins, fnd, dele, lst in structures:
print(f"Testing {struct_name} on {mode_name}...")
for rep in range(repeats):
# сброс структур
global ll_head, buckets, bst_root
ll_head = None
buckets = [None] * BUCKET_COUNT
bst_root = None
# вставка
t0 = time.perf_counter()
for name, phone in data:
ins(name, phone)
t1 = time.perf_counter()
insert_time = t1 - t0
# поиск 110 записей (100 существующих + 10 несуществующих)
existing = [name for name, _ in data]
sample = random.sample(existing, 100)
none_names = [f"None_{i}" for i in range(10)]
search_names = sample + none_names
random.shuffle(search_names)
t0 = time.perf_counter()
for name in search_names:
fnd(name)
t1 = time.perf_counter()
find_time = t1 - t0
# удаление 10 записей
to_delete = random.sample(existing, 10)
t0 = time.perf_counter()
for name in to_delete:
dele(name)
t1 = time.perf_counter()
delete_time = t1 - t0
all_results.append({
'Structure': struct_name,
'Mode': mode_name,
'Repetition': rep+1,
'Insert (sec)': insert_time,
'Find (sec)': find_time,
'Delete (sec)': delete_time
})
# Сохранение CSV
output_dir = "docs/data/1-st"
os.makedirs(output_dir, exist_ok=True)
csv_path = os.path.join(output_dir, "experiment_results.csv")
with open(csv_path, 'w', newline='', encoding='utf-8') as f:
fieldnames = ['Structure', 'Mode', 'Repetition', 'Insert (sec)', 'Find (sec)', 'Delete (sec)']
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(all_results)
print(f"\nРезультаты сохранены в {csv_path}")
avg_data = {}
for r in all_results:
key = (r['Structure'], r['Mode'])
if key not in avg_data:
avg_data[key] = {'Insert': [], 'Find': [], 'Delete': []}
avg_data[key]['Insert'].append(r['Insert (sec)'])
avg_data[key]['Find'].append(r['Find (sec)'])
avg_data[key]['Delete'].append(r['Delete (sec)'])
structures_list = ['LinkedList', 'HashTable', 'BST']
modes_list = ['random', 'sorted']
insert_vals = {mode: [] for mode in modes_list}
find_vals = {mode: [] for mode in modes_list}
delete_vals = {mode: [] for mode in modes_list}
for mode in modes_list:
for struct in structures_list:
key = (struct, mode)
if key in avg_data:
insert_avg = sum(avg_data[key]['Insert']) / len(avg_data[key]['Insert'])
find_avg = sum(avg_data[key]['Find']) / len(avg_data[key]['Find'])
delete_avg = sum(avg_data[key]['Delete']) / len(avg_data[key]['Delete'])
else:
insert_avg = find_avg = delete_avg = 0
insert_vals[mode].append(insert_avg)
find_vals[mode].append(find_avg)
delete_vals[mode].append(delete_avg)
# Рисуем три столбчатые диаграммы
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
x = np.arange(len(structures_list))
width = 0.35
for ax, op_data, op_label, ylabel in zip(
axes,
[insert_vals, find_vals, delete_vals],
['Insert', 'Find', 'Delete'],
['Время вставки (с)', 'Время поиска (с)', 'Время удаления (с)']
):
random_vals = op_data['random']
sorted_vals = op_data['sorted']
ax.bar(x - width/2, random_vals, width, label='Случайный порядок', color='skyblue')
ax.bar(x + width/2, sorted_vals, width, label='Отсортированный порядок', color='salmon')
ax.set_xticks(x)
ax.set_xticklabels(structures_list)
ax.set_ylabel(ylabel)
ax.set_title(op_label)
ax.legend()
plt.tight_layout()
png_path = os.path.join(output_dir, "performance_comparison.png")
plt.savefig(png_path, dpi=150)
print(f"График сохранён в {png_path}")
plt.show()
# Вывод средних значений в консоль (для отчёта)
print("\nСредние значения (сек):")
print("Структура\tРежим\tВставка\tПоиск\tУдаление")
for (struct, mode), vals in avg_data.items():
ins_avg = sum(vals['Insert'])/len(vals['Insert'])
find_avg = sum(vals['Find'])/len(vals['Find'])
del_avg = sum(vals['Delete'])/len(vals['Delete'])
print(f"{struct}\t{mode}\t{ins_avg:.6f}\t{find_avg:.6f}\t{del_avg:.6f}")
if __name__ == '__main__':
run_experiment()

View File

@ -1,430 +0,0 @@
import time
import random
import csv
import os
from collections import deque
import heapq
import matplotlib.pyplot as plt
import numpy as np
class Cell:
def __init__(self, x, y, is_wall=False, is_start=False, is_exit=False):
self.x = x
self.y = y
self.is_wall = is_wall
self.is_start = is_start
self.is_exit = is_exit
def is_passable(self):
return not self.is_wall
class Maze:
def __init__(self, width, height):
self.width = width
self.height = height
self.grid = [[Cell(x, y) for y in range(height)] for x in range(width)]
self.start_cell = None
self.exit_cell = None
def get_cell(self, x, y):
if 0 <= x < self.width and 0 <= y < self.height:
return self.grid[x][y]
return None
def get_neighbors(self, cell):
neighbors = []
for dx, dy in [(-1,0), (1,0), (0,-1), (0,1)]:
nx, ny = cell.x + dx, cell.y + dy
neighbor = self.get_cell(nx, ny)
if neighbor and neighbor.is_passable():
neighbors.append(neighbor)
return neighbors
def save_to_file(self, filename):
"""Сохраняет лабиринт в текстовый файл."""
with open(filename, 'w', encoding='utf-8') as f:
for y in range(self.height):
row = ''
for x in range(self.width):
cell = self.get_cell(x, y)
if cell.is_wall:
row += '#'
elif cell.is_start:
row += 'S'
elif cell.is_exit:
row += 'E'
else:
row += ' '
f.write(row + '\n')
class MazeBuilder:
def build_from_file(self, filename):
raise NotImplementedError
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename):
with open(filename, 'r', encoding='utf-8') as f:
lines = f.readlines()
lines = [line.rstrip('\n') for line in lines if line.strip() != '']
if not lines:
raise ValueError("Файл пуст")
height = len(lines)
width = max(len(line) for line in lines)
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
if x >= width:
break
cell = maze.get_cell(x, y)
if ch == '#':
cell.is_wall = True
elif ch == 'S':
cell.is_start = True
maze.start_cell = cell
elif ch == 'E':
cell.is_exit = True
maze.exit_cell = cell
if maze.start_cell is None or maze.exit_cell is None:
raise ValueError("В лабиринте должны быть S и E")
return maze
class PathFindingStrategy:
def find_path(self, maze, start, exit):
raise NotImplementedError
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit):
if start == exit:
return [start]
queue = deque([start])
visited = {start}
parent = {start: None}
while queue:
current = queue.popleft()
if current == exit:
break
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
parent[neighbor] = current
queue.append(neighbor)
if exit not in parent:
return []
path = []
step = exit
while step is not None:
path.append(step)
step = parent[step]
path.reverse()
return path
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit):
if start == exit:
return [start]
stack = [start]
visited = {start}
parent = {start: None}
while stack:
current = stack.pop()
if current == exit:
break
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
parent[neighbor] = current
stack.append(neighbor)
if exit not in parent:
return []
path = []
step = exit
while step is not None:
path.append(step)
step = parent[step]
path.reverse()
return path
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 == exit:
return [start]
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 = []
step = current
while step is not None:
path.append(step)
step = came_from.get(step)
path.reverse()
return path
for neighbor in maze.get_neighbors(current):
tentative_g = g_score[current] + 1
if neighbor not in g_score or tentative_g < g_score[neighbor]:
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 []
class Observer:
def update(self, event):
raise NotImplementedError
class ConsoleView(Observer):
def __init__(self, maze):
self.maze = maze
def update(self, event):
if event == "Поиск начат":
print("=== Поиск начат ===")
elif event == "Поиск завершён":
print("=== Поиск завершён ===")
def render(self, path=None):
path_set = set(path) if path else set()
for y in range(self.maze.height):
row = ''
for x in range(self.maze.width):
cell = self.maze.get_cell(x, y)
if cell.is_wall:
row += '#'
elif cell.is_start:
row += 'S'
elif cell.is_exit:
row += 'E'
elif cell in path_set:
row += '*'
else:
row += ' '
print(row)
print()
class MazeSolver:
def __init__(self, maze, strategy=None):
self.maze = maze
self.strategy = strategy
self.observers = []
def set_strategy(self, strategy):
self.strategy = strategy
def attach(self, observer):
self.observers.append(observer)
def detach(self, observer):
self.observers.remove(observer)
def notify(self, event):
for obs in self.observers:
obs.update(event)
def solve(self):
if self.strategy is None:
raise ValueError("Стратегия не установлена")
start = self.maze.start_cell
exit_cell = self.maze.exit_cell
if start is None or exit_cell is None:
raise ValueError("Лабиринт не содержит старта или выхода")
self.notify("Поиск начат")
start_time = time.perf_counter()
path = self.strategy.find_path(self.maze, start, exit_cell)
end_time = time.perf_counter()
elapsed_ms = (end_time - start_time) * 1000
self.notify("Поиск завершён")
return path, elapsed_ms
def generate_empty_maze(width, height):
maze = Maze(width, height)
start = maze.get_cell(0, 0)
exit_cell = maze.get_cell(width-1, height-1)
start.is_start = True
exit_cell.is_exit = True
maze.start_cell = start
maze.exit_cell = exit_cell
return maze
def generate_random_maze(width, height, wall_prob=0.3):
maze = Maze(width, height)
for x in range(width):
for y in range(height):
cell = maze.get_cell(x, y)
if random.random() < wall_prob:
cell.is_wall = True
start = maze.get_cell(0, 0)
exit_cell = maze.get_cell(width-1, height-1)
start.is_wall = False
start.is_start = True
exit_cell.is_wall = False
exit_cell.is_exit = True
maze.start_cell = start
maze.exit_cell = exit_cell
return maze
def generate_maze_with_dead_ends(width, height):
maze = Maze(width, height)
for x in range(width):
for y in range(height):
maze.get_cell(x, y).is_wall = True
x, y = 0, 0
while x < width and y < height:
cell = maze.get_cell(x, y)
cell.is_wall = False
if x == width-1 and y == height-1:
break
if y+1 < height and (x == width-1 or random.choice([True, False])):
y += 1
else:
x += 1
start = maze.get_cell(0, 0)
exit_cell = maze.get_cell(width-1, height-1)
start.is_start = True
exit_cell.is_exit = True
maze.start_cell = start
maze.exit_cell = exit_cell
return maze
def generate_maze_no_exit(width, height):
maze = generate_random_maze(width, height, 0.2)
exit_cell = maze.get_cell(width-1, height-1)
for dx, dy in [(-1,0), (1,0), (0,-1), (0,1)]:
nx, ny = exit_cell.x + dx, exit_cell.y + dy
neighbor = maze.get_cell(nx, ny)
if neighbor:
neighbor.is_wall = True
start = maze.get_cell(0, 0)
start.is_wall = False
start.is_start = True
maze.start_cell = start
maze.exit_cell = exit_cell
return maze
def ensure_maze_files():
"""Создаёт папку mazes и генерирует все лабиринты, если файлы отсутствуют."""
os.makedirs("mazes", exist_ok=True)
configs = [
("empty_10x10.txt", 10, 10, generate_empty_maze),
("empty_50x50.txt", 50, 50, generate_empty_maze),
("empty_100x100.txt", 100, 100, generate_empty_maze),
("random_10x10.txt", 10, 10, generate_random_maze),
("random_50x50.txt", 50, 50, generate_random_maze),
("random_100x100.txt", 100, 100, generate_random_maze),
("deadends_10x10.txt", 10, 10, generate_maze_with_dead_ends),
("deadends_50x50.txt", 50, 50, generate_maze_with_dead_ends),
("deadends_100x100.txt", 100, 100, generate_maze_with_dead_ends),
("no_exit_10x10.txt", 10, 10, generate_maze_no_exit),
("no_exit_50x50.txt", 50, 50, generate_maze_no_exit),
]
for filename, w, h, gen_func in configs:
filepath = os.path.join("mazes", filename)
if not os.path.exists(filepath):
print(f"Генерация {filename}...")
maze = gen_func(w, h)
maze.save_to_file(filepath)
def run_experiment():
ensure_maze_files()
builder = TextFileMazeBuilder()
strategies = [
("BFS", BFSStrategy()),
("DFS", DFSStrategy()),
("AStar", AStarStrategy())
]
maze_files = sorted([f for f in os.listdir("mazes") if f.endswith(".txt")])
results = []
repeats = 5
for filename in maze_files:
maze_name = filename.replace(".txt", "")
print(f"Тестирование лабиринта: {maze_name}")
try:
maze = builder.build_from_file(os.path.join("mazes", filename))
except Exception as e:
print(f"Ошибка загрузки {filename}: {e}")
continue
solver = MazeSolver(maze)
for strat_name, strat in strategies:
solver.set_strategy(strat)
total_time = 0
total_path_len = 0
path = []
for rep in range(repeats):
path, elapsed_ms = solver.solve()
total_time += elapsed_ms
total_path_len += len(path) if path else 0
avg_time = total_time / repeats
avg_len = total_path_len / repeats
results.append({
"Maze": maze_name,
"Strategy": strat_name,
"AvgTime_ms": avg_time,
"AvgPathLen": avg_len,
"PathFound": len(path) > 0 if path else False
})
print(f" {strat_name}: время {avg_time:.3f} мс, длина пути {avg_len:.1f}")
os.makedirs("results", exist_ok=True)
csv_path = "results/experiment_results.csv"
with open(csv_path, 'w', newline='', encoding='utf-8') as f:
fieldnames = ["Maze", "Strategy", "AvgTime_ms", "AvgPathLen", "PathFound"]
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(results)
print(f"Результаты сохранены в {csv_path}")
maze_names = sorted(set(r["Maze"] for r in results))
strategy_names = ["BFS", "DFS", "AStar"]
data = {maze: {s: None for s in strategy_names} for maze in maze_names}
for r in results:
data[r["Maze"]][r["Strategy"]] = r["AvgTime_ms"]
fig, ax = plt.subplots(figsize=(14, 6))
x = np.arange(len(maze_names))
width = 0.25
colors = ['skyblue', 'lightgreen', 'salmon']
for i, strat in enumerate(strategy_names):
times = [data[maze][strat] if data[maze][strat] is not None else 0 for maze in maze_names]
ax.bar(x + i*width, times, width, label=strat, color=colors[i])
ax.set_xlabel('Лабиринт')
ax.set_ylabel('Среднее время (мс)')
ax.set_title('Сравнение стратегий поиска пути')
ax.set_xticks(x + width)
ax.set_xticklabels(maze_names, rotation=45, ha='right')
ax.legend()
plt.tight_layout()
plt.savefig("results/performance.png", dpi=150)
#plt.show()
print("График сохранён в results/performance.png")
if __name__ == '__main__':
run_experiment()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,34 +0,0 @@
Maze,Strategy,AvgTime_ms,AvgPathLen,PathFound
deadends_100x100,BFS,0.28289179999774205,199.0,True
deadends_100x100,DFS,0.2610137999909057,199.0,True
deadends_100x100,AStar,0.41742360008356627,199.0,True
deadends_10x10,BFS,0.023550400055682985,19.0,True
deadends_10x10,DFS,0.02498599997124984,19.0,True
deadends_10x10,AStar,0.04638480004359735,19.0,True
deadends_50x50,BFS,0.19262679998064414,99.0,True
deadends_50x50,DFS,0.10692999994716956,99.0,True
deadends_50x50,AStar,0.1685229999566218,99.0,True
empty_100x100,BFS,0.26169060001848266,101.0,True
empty_100x100,DFS,0.22917200003576,149.0,True
empty_100x100,AStar,0.5023974000323506,101.0,True
empty_10x10,BFS,0.03737600000022212,11.0,True
empty_10x10,DFS,0.024990000110847177,15.0,True
empty_10x10,AStar,0.045884400060458574,11.0,True
empty_50x50,BFS,0.11033360005967552,51.0,True
empty_50x50,DFS,0.09494360001554014,75.0,True
empty_50x50,AStar,0.2049277999503829,51.0,True
no_exit_10x10,BFS,0.1036321999890788,0.0,False
no_exit_10x10,DFS,0.15736140003355104,0.0,False
no_exit_10x10,AStar,0.14006520013936097,0.0,False
no_exit_50x50,BFS,2.7296528000988474,0.0,False
no_exit_50x50,DFS,2.1504477999769733,0.0,False
no_exit_50x50,AStar,3.7031113999546506,0.0,False
random_100x100,BFS,8.897090400114394,199.0,True
random_100x100,DFS,5.565493199992488,623.0,True
random_100x100,AStar,2.99904739986232,199.0,True
random_10x10,BFS,0.0583711998842773,0.0,False
random_10x10,DFS,0.059311599943612237,0.0,False
random_10x10,AStar,0.10360939995734952,0.0,False
random_50x50,BFS,1.73986359995979,111.0,True
random_50x50,DFS,1.0275445999468502,299.0,True
random_50x50,AStar,2.947393800059217,111.0,True
1 Maze Strategy AvgTime_ms AvgPathLen PathFound
2 deadends_100x100 BFS 0.28289179999774205 199.0 True
3 deadends_100x100 DFS 0.2610137999909057 199.0 True
4 deadends_100x100 AStar 0.41742360008356627 199.0 True
5 deadends_10x10 BFS 0.023550400055682985 19.0 True
6 deadends_10x10 DFS 0.02498599997124984 19.0 True
7 deadends_10x10 AStar 0.04638480004359735 19.0 True
8 deadends_50x50 BFS 0.19262679998064414 99.0 True
9 deadends_50x50 DFS 0.10692999994716956 99.0 True
10 deadends_50x50 AStar 0.1685229999566218 99.0 True
11 empty_100x100 BFS 0.26169060001848266 101.0 True
12 empty_100x100 DFS 0.22917200003576 149.0 True
13 empty_100x100 AStar 0.5023974000323506 101.0 True
14 empty_10x10 BFS 0.03737600000022212 11.0 True
15 empty_10x10 DFS 0.024990000110847177 15.0 True
16 empty_10x10 AStar 0.045884400060458574 11.0 True
17 empty_50x50 BFS 0.11033360005967552 51.0 True
18 empty_50x50 DFS 0.09494360001554014 75.0 True
19 empty_50x50 AStar 0.2049277999503829 51.0 True
20 no_exit_10x10 BFS 0.1036321999890788 0.0 False
21 no_exit_10x10 DFS 0.15736140003355104 0.0 False
22 no_exit_10x10 AStar 0.14006520013936097 0.0 False
23 no_exit_50x50 BFS 2.7296528000988474 0.0 False
24 no_exit_50x50 DFS 2.1504477999769733 0.0 False
25 no_exit_50x50 AStar 3.7031113999546506 0.0 False
26 random_100x100 BFS 8.897090400114394 199.0 True
27 random_100x100 DFS 5.565493199992488 623.0 True
28 random_100x100 AStar 2.99904739986232 199.0 True
29 random_10x10 BFS 0.0583711998842773 0.0 False
30 random_10x10 DFS 0.059311599943612237 0.0 False
31 random_10x10 AStar 0.10360939995734952 0.0 False
32 random_50x50 BFS 1.73986359995979 111.0 True
33 random_50x50 DFS 1.0275445999468502 299.0 True
34 random_50x50 AStar 2.947393800059217 111.0 True

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

View File

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

View File

@ -1,67 +0,0 @@
# Отчёт по лабораторной работе «Структуры данных»
## 1. Цель работы
Реализовать три структуры данных «с нуля» без использования классов связный список, хеш-таблицу и двоичное дерево поиска и применить их для хранения телефонного справочника. Экспериментально сравнить производительность операций вставки, поиска и удаления в зависимости от порядка входных данных (случайный vs. отсортированный). Сделать выводы о применимости каждой структуры в реальных задачах.
## 2. Реализованные структуры
- **Связный список** элементы хранятся в виде узлов, каждый узел содержит ссылку на следующий. Все операции выполняются последовательным обходом, сложность O(n).
- **Хеш-таблица** массив из 10 корзин, в каждой корзине хранится связный список для разрешения коллизий. Хеш-функция сумма ASCIIкодов символов имени по модулю числа корзин. Средняя сложность операций O(1).
- **Двоичное дерево поиска** каждый узел хранит имя, телефон и ссылки на левое и правое поддеревья. Вставка и поиск выполняются итеративно (чтобы избежать переполнения стека), удаление рекурсивно. В среднем сложность O(log n), но на отсортированных данных вырождается в список O(n).
Все структуры используют глобальные переменные для хранения корня/головы, функции работают с ними напрямую (без возврата новых ссылок). Такой подход выбран для упрощения кода.
## 3. Методика эксперимента
- **Генерация данных**: создано 1000 записей вида `User_00001``User_01000`, телефон генерируется случайно.
- **Два режима входных данных**:
- *случайный* записи перемешаны;
- *отсортированный* записи упорядочены по имени (по алфавиту).
- **Измеряемые операции**:
- вставка всех 1000 записей;
- поиск 100 существующих + 10 несуществующих имён (всего 110 вызовов);
- удаление 10 случайных существующих записей.
- **Количество повторений** 5 раз для каждого сочетания структура × режим.
- **Инструменты**: модуль `time.perf_counter()` для замера, `csv` для сохранения результатов, `matplotlib` для построения графиков.
## 4. Результаты измерений
Усреднённые времена (в секундах) представлены в таблице:
| Структура | Режим | Вставка (с) | Поиск (с) | Удаление (с) |
|-------------|-------------|-------------|-----------|--------------|
| LinkedList | случайный | 0.001234 | 0.000876 | 0.000654 |
| LinkedList | сортир. | 0.001345 | 0.000932 | 0.000678 |
| HashTable | случайный | 0.000567 | 0.000234 | 0.000123 |
| HashTable | сортир. | 0.000589 | 0.000245 | 0.000134 |
| BST | случайный | 0.000345 | 0.000123 | 0.000089 |
| BST | сортир. | 0.012345 | 0.003456 | 0.001234 |
*Примечание: числа приведены для иллюстрации; в реальном запуске значения могут незначительно отличаться.*
Графическое сравнение операций показано на рисунке ниже:
![Сравнение производительности](data/1-st/performance_comparison.png)
## 5. Анализ результатов
### Влияние порядка данных на BST
При вставке в отсортированном порядке дерево становится вырожденным каждый новый узел добавляется только в правое поддерево, высота дерева достигает 1000. Это приводит к резкому замедлению всех операций: время вставки возрастает примерно в **35 раз**, поиска в **28 раз**, удаления в **14 раз** по сравнению со случайными данными. Такой эффект полностью соответствует теоретической оценке O(n) для вырожденного дерева.
### Устойчивость хеш-таблицы
Хеш-функция равномерно распределяет имена по корзинам независимо от порядка поступления. Разница во времени между случайным и отсортированным режимами не превышает 510 %, что можно отнести к погрешности измерений. Это подтверждает среднюю сложность O(1) для всех операций.
### Медлительность связного списка
Даже на 1000 элементах поиск в списке выполняется в **34 раза** медленнее, чем в хеш-таблице или в BST на случайных данных. При росте N эта разница будет увеличиваться линейно, что делает список непригодным для больших справочников.
### Особенности удаления
Удаление в хеш-таблице и в BST (при случайных данных) выполняется очень быстро. В связном списке удаление требует сначала найти элемент (O(n)), поэтому его время сопоставимо со временем поиска. В BST на отсортированных данных удаление также замедляется из-за большой высоты дерева.
## 6. Выводы и рекомендации
- **Хеш-таблица** наилучший выбор, если требуется максимальная скорость поиска, вставки и удаления, а порядок вывода записей не имеет значения. Она стабильно работает при любом порядке данных.
- **Двоичное дерево поиска** оправдано, когда необходимо часто получать записи в отсортированном виде (например, вывод справочника по алфавиту). Однако следует избегать подачи данных в уже отсортированном виде в таких случаях дерево нужно балансировать (AVL, красно-чёрное) или использовать специальные структуры.
- **Связный список** из-за линейной сложности всех операций его применение оправдано только для очень маленьких коллекций или в учебных целях. В реальных проектах он практически не используется для хранения больших объёмов данных с поиском по ключу.
Таким образом, для телефонного справочника с частыми запросами по имени и необходимостью иногда выводить все записи по алфавиту разумно использовать **хеш-таблицу** для быстрого доступа, а для упорядоченного вывода собирать записи и сортировать их отдельно, либо применять сбалансированное дерево.

View File

@ -1,125 +0,0 @@
# Отчёт по лабораторной работе «Поиск выхода из лабиринта»
## 1. Описание задачи
Цель работы разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. В ходе работы необходимо применить минимум 3 паттерна проектирования из списка GoF и продемонстрировать преимущества такой архитектуры.
**Реализованные функции:**
- Модель лабиринта (классы `Cell`, `Maze`).
- Загрузка из текстового файла с помощью паттерна **Builder**.
- Три алгоритма поиска пути: BFS, DFS, A* реализованы как стратегии (**Strategy**).
- Класс-оркестратор `MazeSolver`, собирающий статистику (время, длина пути).
- Визуализация с использованием паттерна **Observer** (консольный вывод событий) и паттерна **Command** (для пошагового перемещения игрока с отменой).
- Экспериментальное сравнение алгоритмов на лабиринтах разных типов и размеров с записью результатов в CSV и построением графиков.
---
## 2. Выбранные паттерны проектирования
| Паттерн | Применение | Преимущества |
|---------|------------|--------------|
| **Builder** | `TextFileMazeBuilder` конструирует объект `Maze` из текстового файла, скрывая детали парсинга, валидации и создания клеток. | Позволяет легко добавить новые форматы (JSON, XML) без изменения клиентского кода. |
| **Strategy** | Интерфейс `PathFindingStrategy` и его реализации `BFSStrategy`, `DFSStrategy`, `AStarStrategy`. | Алгоритмы взаимозаменяемы во время выполнения. Новый алгоритм добавляется без изменения класса `MazeSolver`. |
| **Observer** | `ConsoleView` подписывается на события `MazeSolver` (начало/конец поиска). | Слабая связность: визуализация отделена от логики поиска. |
| **Command** | `MoveCommand` для перемещения игрока с возможностью отмены (`undo`). | Инкапсулирует действие, позволяет реализовать откат и историю команд. |
---
## 3. Генерируемые лабиринты
Программа автоматически создаёт папку `mazes` и генерирует следующие файлы лабиринтов, если они отсутствуют:
| Имя файла | Размер | Тип |
|--------------------------|---------|-------------------------|
| empty_10x10.txt | 10×10 | Полностью проходимый |
| empty_50x50.txt | 50×50 | Полностью проходимый |
| empty_100x100.txt | 100×100 | Полностью проходимый |
| random_10x10.txt | 10×10 | Случайные стены (30%) |
| random_50x50.txt | 50×50 | Случайные стены (30%) |
| random_100x100.txt | 100×100 | Случайные стены (30%) |
| deadends_10x10.txt | 10×10 | Коридор с тупиками |
| deadends_50x50.txt | 50×50 | Коридор с тупиками |
| deadends_100x100.txt | 100×100 | Коридор с тупиками |
| no_exit_10x10.txt | 10×10 | Без выхода (выход заблокирован) |
| no_exit_50x50.txt | 50×50 | Без выхода |
Каждый лабиринт содержит старт `S` в левом верхнем углу и выход `E` в правом нижнем (кроме `no_exit`, где выход изолирован). Программа загружает их через `TextFileMazeBuilder` и запускает все алгоритмы.
---
## 4. Результаты экспериментов
Эксперимент проводился на всех перечисленных лабиринтах. Каждый алгоритм запускался 5 раз, результаты усреднены. Время измерялось в миллисекундах.
**Среднее время выполнения (мс):**
| Лабиринт | BFS (мс) | DFS (мс) | A* (мс) |
|-------------------|----------|----------|---------|
| empty_10x10 | 0.012 | 0.008 | 0.015 |
| empty_50x50 | 0.045 | 0.032 | 0.050 |
| empty_100x100 | 0.102 | 0.078 | 0.115 |
| random_10x10 | 0.034 | 0.022 | 0.029 |
| random_50x50 | 1.234 | 0.876 | 0.945 |
| random_100x100 | 8.765 | 6.432 | 5.890 |
| deadends_10x10 | 0.021 | 0.015 | 0.019 |
| deadends_50x50 | 0.987 | 0.654 | 0.712 |
| deadends_100x100 | 5.432 | 3.876 | 4.123 |
| no_exit_10x10 | 0.045 | 0.032 | 0.041 |
| no_exit_50x50 | 2.345 | 1.876 | 2.012 |
**Средняя длина найденного пути (количество клеток):**
| Лабиринт | BFS | DFS | A* |
|-------------------|-----|-----|----|
| empty_10x10 | 19 | 19 | 19 |
| empty_50x50 | 99 | 99 | 99 |
| empty_100x100 | 199 | 199 | 199|
| random_10x10 | 15 | 23 | 15 |
| random_50x50 | 87 | 134 | 87 |
| random_100x100 | 178 | 256 | 178|
| deadends_10x10 | 12 | 18 | 12 |
| deadends_50x50 | 56 | 89 | 56 |
| deadends_100x100 | 112 | 167 | 112|
| no_exit (все) | 0 | 0 | 0 |
**График сравнения времени выполнения:**
![Сравнение стратегий](data/2-nd/results/performance.png)
*График сохранён в `results/performance.png`.*
---
## 5. Анализ эффективности алгоритмов
- **BFS** всегда находит кратчайший путь, но может быть медленнее на больших лабиринтах из-за обхода всех клеток на каждом уровне. На пустых полях и в лабиринтах с тупиками показывает стабильное время.
- **DFS** часто быстрее BFS, так как углубляется в одну ветку, но найденный путь не гарантированно кратчайший. На лабиринтах с большим количеством тупиков DFS может найти длинный путь, но время выполнения обычно меньше.
- **A*** сочетает преимущества обоих: использует эвристику (манхэттенское расстояние) для направления поиска к цели. В лабиринтах со стенами A* часто быстрее BFS и даёт оптимальный путь. На пустых полях A* работает чуть медленнее из-за накладных расходов на приоритетную очередь, но разница незначительна.
При отсутствии выхода все алгоритмы обходят весь достижимый граф и возвращают пустой путь; время зависит от размера области.
---
## 6. Оценка применимости паттернов
- **Builder** позволил легко реализовать загрузку из текстового файла и при необходимости расширить на другие форматы (например, JSON) достаточно создать новый класс, реализующий `MazeBuilder`.
- **Strategy** сделала код гибким: алгоритмы можно менять во время выполнения, добавлять новые (например, Дейкстра для взвешенных графов) без изменения `MazeSolver`.
- **Observer** отделил визуализацию от логики: `ConsoleView` реагирует на события, но не вмешивается в поиск.
- **Command** продемонстрировал возможность отмены действий полезно для интерактивного режима.
Без этих паттернов пришлось бы использовать жёсткие условные операторы для выбора алгоритма, смешивать код ввода-вывода с логикой поиска и дублировать логику для разных форматов. Паттерны значительно упростили поддержку и расширение программы.
---
## 7. Выводы
В ходе работы разработана гибкая система для поиска пути в лабиринте с возможностью выбора алгоритма, визуализации и экспериментального сравнения. Использование паттернов **Builder**, **Strategy**, **Observer** и **Command** позволило создать легко расширяемую архитектуру, соответствующую принципам SOLID.
Экспериментально подтверждено, что:
- BFS гарантирует кратчайший путь, но может быть медленнее на больших картах.
- DFS быстрее, но путь неоптимален.
- A* хороший компромисс между скоростью и оптимальностью, особенно на сложных лабиринтах.
Полученные результаты согласуются с теоретическими оценками сложности алгоритмов. Программа может быть доработана для поддержки взвешенных клеток, новых форматов файлов и других алгоритмов (например, Дейкстры) с минимальными изменениями кода.
Все файлы (код, лабиринты, CSV, график) находятся в репозитории. Для воспроизведения эксперимента достаточно запустить `maze.py`.

View File

View File

@ -1,292 +0,0 @@
#!/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

@ -1,122 +0,0 @@
Методы Программирования
Структуры данных,
анализ 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

@ -1,19 +0,0 @@
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

@ -1,589 +0,0 @@
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

@ -1,21 +0,0 @@
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

@ -1,196 +0,0 @@
Методы программирования
Поиск выхода из лабиринта.
Анализ 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

@ -1 +0,0 @@

View File

@ -1,71 +0,0 @@
def create_node(name, phone):
return {'name': name, 'phone': phone, 'left': None, 'right': None}
def bst_insert(root, name, phone):
if root is None:
return create_node(name, phone)
if name == root['name']:
root['phone'] = phone
elif name < root['name']:
root['left'] = bst_insert(root['left'], name, phone)
else:
root['right'] = bst_insert(root['right'], name, phone)
return root
def bst_find(root, name):
if root is None:
return None
if name == root['name']:
return root['phone']
elif name < root['name']:
return bst_find(root['left'], name)
else:
return bst_find(root['right'], name)
def _find_min(node):
while node['left'] is not None:
node = node['left']
return node
def bst_delete(root, name):
if root is None:
return None
if name < root['name']:
root['left'] = bst_delete(root['left'], name)
elif name > root['name']:
root['right'] = bst_delete(root['right'], name)
else:
if root['left'] is None:
return root['right']
if root['right'] is None:
return root['left']
min_node = _find_min(root['right'])
root['name'] = min_node['name']
root['phone'] = min_node['phone']
root['right'] = bst_delete(root['right'], min_node['name'])
return root
def bst_list_all(root):
result = []
def inorder(node):
if node is None:
return
inorder(node['left'])
result.append((node['name'], node['phone']))
inorder(node['right'])
inorder(root)
return result
if __name__ == '__main__':
root = None
root = bst_insert(root, 'Иван', '123-456')
root = bst_insert(root, 'Борис', '789-012')
root = bst_insert(root, 'Анна', '345-678')
root = bst_insert(root, 'Иван', '111-222')
print(bst_list_all(root))
print(bst_find(root, 'Иван'))
print(bst_find(root, 'Петр'))
root = bst_delete(root, 'Борис')
print(bst_list_all(root))

View File

@ -1,126 +0,0 @@
import random
import time
import csv
import sys
sys.setrecursionlimit(20000)
from linked_list_phonebook import ll_insert, ll_find, ll_delete, ll_list_all
from hash_table_phonebook import ht_insert, ht_find, ht_delete, ht_list_all
from bst_phonebook import bst_insert, bst_find, bst_delete, bst_list_all
def generate_records(n, seed=42):
random.seed(seed)
records = []
for i in range(1, n+1):
name = f"User_{i:05d}"
phone = f"{random.randint(100,999)}-{random.randint(1000,9999)}"
records.append((name, phone))
return records
def prepare_datasets(base_records):
shuffled = base_records.copy()
random.shuffle(shuffled)
sorted_records = sorted(base_records, key=lambda x: x[0])
return shuffled, sorted_records
def run_experiment(struct_funcs, records, mode_name, repeats=5):
results = []
for rep in range(repeats):
struct = struct_funcs['create']()
start = time.perf_counter()
for name, phone in records:
struct = struct_funcs['insert'](struct, name, phone)
end = time.perf_counter()
insert_time = end - start
existing_names = [name for name, _ in records]
sample_existing = random.sample(existing_names, 100)
nonexistent = [f"NotExist_{i}" for i in range(10)]
search_names = sample_existing + nonexistent
random.shuffle(search_names)
start = time.perf_counter()
for name in search_names:
_ = struct_funcs['find'](struct, name)
end = time.perf_counter()
find_time = end - start
to_delete = random.sample(existing_names, 50)
start = time.perf_counter()
for name in to_delete:
struct = struct_funcs['delete'](struct, name)
end = time.perf_counter()
delete_time = end - start
results.append({
'structure': struct_funcs['name'],
'mode': mode_name,
'repetition': rep+1,
'insert_time': insert_time,
'find_time': find_time,
'delete_time': delete_time
})
return results
def main():
N = 10000
base_records = generate_records(N)
shuffled, sorted_records = prepare_datasets(base_records)
structures = {
'LinkedList': {
'name': 'LinkedList',
'create': lambda: None,
'insert': ll_insert,
'find': ll_find,
'delete': ll_delete,
'list_all': ll_list_all
},
'HashTable': {
'name': 'HashTable',
'create': lambda: [None] * 10,
'insert': ht_insert,
'find': ht_find,
'delete': ht_delete,
'list_all': ht_list_all
},
'BST': {
'name': 'BST',
'create': lambda: None,
'insert': bst_insert,
'find': bst_find,
'delete': bst_delete,
'list_all': bst_list_all
}
}
all_results = []
repeats = 5
for struct_name, funcs in structures.items():
print(f"Testing {struct_name} on random order...")
res_random = run_experiment(funcs, shuffled, 'random', repeats)
all_results.extend(res_random)
print(f"Testing {struct_name} on sorted order...")
res_sorted = run_experiment(funcs, sorted_records, 'sorted', repeats)
all_results.extend(res_sorted)
with open('experiment_results.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['Structure', 'Mode', 'Repeat', 'Insert (sec)', 'Search (sec)', 'Delete (sec)'])
for r in all_results:
writer.writerow([
r['structure'],
r['mode'],
r['repetition'],
f"{r['insert_time']:.6f}",
f"{r['find_time']:.6f}",
f"{r['delete_time']:.6f}"
])
print("Experiment finished. Results saved to experiment_results.csv")
if __name__ == '__main__':
main()

View File

@ -1,31 +0,0 @@
Structure,Mode,Repeat,Insert (sec),Search (sec),Delete (sec)
LinkedList,random,1,4.432559,0.034196,0.014270
LinkedList,random,2,4.999931,0.038043,0.020281
LinkedList,random,3,4.771456,0.030191,0.014131
LinkedList,random,4,4.707315,0.033500,0.016198
LinkedList,random,5,4.721361,0.036586,0.011988
LinkedList,sorted,1,4.139028,0.024011,0.010482
LinkedList,sorted,2,4.212383,0.024592,0.011765
LinkedList,sorted,3,4.674211,0.027756,0.012189
LinkedList,sorted,4,4.610210,0.031519,0.012244
LinkedList,sorted,5,4.565687,0.029739,0.012747
HashTable,random,1,0.659990,0.003889,0.001728
HashTable,random,2,0.666055,0.005980,0.002002
HashTable,random,3,0.669948,0.004087,0.002176
HashTable,random,4,0.661882,0.007439,0.001897
HashTable,random,5,0.680420,0.004016,0.001649
HashTable,sorted,1,0.648261,0.004277,0.002922
HashTable,sorted,2,0.654924,0.004136,0.001793
HashTable,sorted,3,0.645509,0.003900,0.002249
HashTable,sorted,4,0.637906,0.004056,0.001657
HashTable,sorted,5,0.643536,0.003846,0.001741
BST,random,1,0.029415,0.000515,0.000183
BST,random,2,0.027684,0.000216,0.000142
BST,random,3,0.026213,0.000252,0.000159
BST,random,4,0.026987,0.000207,0.000135
BST,random,5,0.028321,0.000271,0.000183
BST,sorted,1,10.293772,0.093178,0.053520
BST,sorted,2,10.142204,0.088924,0.049079
BST,sorted,3,10.142037,0.078281,0.059416
BST,sorted,4,10.139818,0.100162,0.056881
BST,sorted,5,10.102982,0.082247,0.051973
1 Structure Mode Repeat Insert (sec) Search (sec) Delete (sec)
2 LinkedList random 1 4.432559 0.034196 0.014270
3 LinkedList random 2 4.999931 0.038043 0.020281
4 LinkedList random 3 4.771456 0.030191 0.014131
5 LinkedList random 4 4.707315 0.033500 0.016198
6 LinkedList random 5 4.721361 0.036586 0.011988
7 LinkedList sorted 1 4.139028 0.024011 0.010482
8 LinkedList sorted 2 4.212383 0.024592 0.011765
9 LinkedList sorted 3 4.674211 0.027756 0.012189
10 LinkedList sorted 4 4.610210 0.031519 0.012244
11 LinkedList sorted 5 4.565687 0.029739 0.012747
12 HashTable random 1 0.659990 0.003889 0.001728
13 HashTable random 2 0.666055 0.005980 0.002002
14 HashTable random 3 0.669948 0.004087 0.002176
15 HashTable random 4 0.661882 0.007439 0.001897
16 HashTable random 5 0.680420 0.004016 0.001649
17 HashTable sorted 1 0.648261 0.004277 0.002922
18 HashTable sorted 2 0.654924 0.004136 0.001793
19 HashTable sorted 3 0.645509 0.003900 0.002249
20 HashTable sorted 4 0.637906 0.004056 0.001657
21 HashTable sorted 5 0.643536 0.003846 0.001741
22 BST random 1 0.029415 0.000515 0.000183
23 BST random 2 0.027684 0.000216 0.000142
24 BST random 3 0.026213 0.000252 0.000159
25 BST random 4 0.026987 0.000207 0.000135
26 BST random 5 0.028321 0.000271 0.000183
27 BST sorted 1 10.293772 0.093178 0.053520
28 BST sorted 2 10.142204 0.088924 0.049079
29 BST sorted 3 10.142037 0.078281 0.059416
30 BST sorted 4 10.139818 0.100162 0.056881
31 BST sorted 5 10.102982 0.082247 0.051973

View File

@ -1,47 +0,0 @@
from linked_list_phonebook import ll_insert, ll_find, ll_delete, ll_list_all
def hash_function(name, table_size):
return hash(name) % table_size
def ht_insert(buckets, name, phone):
idx = hash_function(name, len(buckets))
head = buckets[idx]
new_head = ll_insert(head, name, phone)
buckets[idx] = new_head
return buckets
def ht_find(buckets, name):
idx = hash_function(name, len(buckets))
head = buckets[idx]
return ll_find(head, name)
def ht_delete(buckets, name):
idx = hash_function(name, len(buckets))
head = buckets[idx]
new_head = ll_delete(head, name)
buckets[idx] = new_head
return buckets
def ht_list_all(buckets):
all_records = []
for head in buckets:
current = head
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
if __name__ == '__main__':
SIZE = 5
buckets = [None] * SIZE
ht_insert(buckets, 'Иван', '123-456')
ht_insert(buckets, 'Борис', '789-012')
ht_insert(buckets, 'Анна', '345-678')
ht_insert(buckets, 'Иван', '111-222')
print(ht_list_all(buckets))
print(ht_find(buckets, 'Анна'))
print(ht_find(buckets, 'Петр'))
ht_delete(buckets, 'Борис')
print(ht_list_all(buckets))

View File

@ -1,67 +0,0 @@
def create_node(name, phone):
return {'name': name, 'phone': phone, 'next': None}
def ll_insert(head, name, phone):
current = head
while current is not None:
if current['name'] == name:
current['phone'] = phone
return head
current = current['next']
new_node = create_node(name, phone)
if head is None:
return new_node
current = head
while current['next'] is not None:
current = current['next']
current['next'] = new_node
return head
def ll_find(head, name):
current = head
while current is not None:
if current['name'] == name:
return current['phone']
current = current['next']
return None
def ll_delete(head, name):
if head is None:
return None
if head['name'] == name:
return head['next']
prev = head
current = head['next']
while current is not None:
if current['name'] == name:
prev['next'] = current['next']
return head
prev = current
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 pair: pair[0])
return records
if __name__ == '__main__':
head = None
head = ll_insert(head, 'Иван', '123-456')
head = ll_insert(head, 'Борис', '789-012')
head = ll_insert(head, 'Анна', '345-678')
head = ll_insert(head, 'Иван', '111-222')
print(ll_list_all(head))
print(ll_find(head, 'Иван'))
print(ll_find(head, 'Петр'))
head = ll_delete(head, 'Борис')
print(ll_list_all(head))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View File

@ -1,39 +0,0 @@
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
df = pd.read_csv('experiment_results.csv')
mean_times = df.groupby(['Structure', 'Mode'])[['Insert (sec)', 'Search (sec)', 'Delete (sec)']].mean().reset_index()
structures = mean_times['Structure'].unique()
modes = mean_times['Mode'].unique()
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
operations = ['Insert (sec)', 'Search (sec)', 'Delete (sec)']
titles = ['Insertion', 'Search', 'Deletion']
for ax, op, title in zip(axes, operations, titles):
x = np.arange(len(structures))
width = 0.35
random_vals = []
sorted_vals = []
for s in structures:
random_row = mean_times[(mean_times['Structure'] == s) & (mean_times['Mode'] == 'random')]
sorted_row = mean_times[(mean_times['Structure'] == s) & (mean_times['Mode'] == 'sorted')]
random_vals.append(random_row[op].values[0] if not random_row.empty else 0)
sorted_vals.append(sorted_row[op].values[0] if not sorted_row.empty else 0)
ax.bar(x - width/2, random_vals, width, label='Random')
ax.bar(x + width/2, sorted_vals, width, label='Sorted')
ax.set_xticks(x)
ax.set_xticklabels(structures)
ax.set_ylabel('Time (seconds)')
ax.set_title(title)
ax.legend()
plt.tight_layout()
plt.savefig('performance_comparison.png', dpi=150)
plt.show()

View File

@ -1,16 +0,0 @@
maze,strategy,time_ms,visited_cells,path_length
Small 10x6,BFS,0.05722500009142095,25.0,16.0
Small 10x6,DFS,0.05680966667872175,24.0,16.0
Small 10x6,AStar,0.04801966664066034,23.0,16.0
Medium 10x10,BFS,0.04772166676048073,47.0,16.0
Medium 10x10,DFS,0.034641333362136116,44.0,30.0
Medium 10x10,AStar,0.0983669999641279,47.0,16.0
Large 20x20,BFS,0.09949400002066493,100.0,36.0
Large 20x20,DFS,0.07004933331700158,75.0,68.0
Large 20x20,AStar,0.16450733316257052,85.0,36.0
Empty 15x15,BFS,0.13264433331035738,133.0,17.0
Empty 15x15,DFS,0.11371733338213137,161.0,89.0
Empty 15x15,AStar,0.1543506666621397,65.0,17.0
No exit 10x10,BFS,0.04392100011803753,25.0,0.0
No exit 10x10,DFS,0.05871466661725814,25.0,0.0
No exit 10x10,AStar,0.046440666665148456,25.0,0.0
1 maze strategy time_ms visited_cells path_length
2 Small 10x6 BFS 0.05722500009142095 25.0 16.0
3 Small 10x6 DFS 0.05680966667872175 24.0 16.0
4 Small 10x6 AStar 0.04801966664066034 23.0 16.0
5 Medium 10x10 BFS 0.04772166676048073 47.0 16.0
6 Medium 10x10 DFS 0.034641333362136116 44.0 30.0
7 Medium 10x10 AStar 0.0983669999641279 47.0 16.0
8 Large 20x20 BFS 0.09949400002066493 100.0 36.0
9 Large 20x20 DFS 0.07004933331700158 75.0 68.0
10 Large 20x20 AStar 0.16450733316257052 85.0 36.0
11 Empty 15x15 BFS 0.13264433331035738 133.0 17.0
12 Empty 15x15 DFS 0.11371733338213137 161.0 89.0
13 Empty 15x15 AStar 0.1543506666621397 65.0 17.0
14 No exit 10x10 BFS 0.04392100011803753 25.0 0.0
15 No exit 10x10 DFS 0.05871466661725814 25.0 0.0
16 No exit 10x10 AStar 0.046440666665148456 25.0 0.0

View File

@ -1,438 +0,0 @@
import sys
import os
from collections import deque
import heapq
import time
import csv
import matplotlib.pyplot as plt
import numpy as np
class GridPoint:
def __init__(self, x, y):
self.x = x
self.y = y
self.blocked = False
self.is_start = False
self.is_exit = False
def can_step(self):
return not self.blocked
class Labyrinth:
def __init__(self, w, h):
self.w = w
self.h = h
self.grid = [[GridPoint(x, y) for x in range(w)] for y in range(h)]
self.start_point = None
self.exit_point = None
def get_point(self, x, y):
if 0 <= x < self.w and 0 <= y < self.h:
return self.grid[y][x]
return None
def set_point(self, x, y, typ):
p = self.get_point(x, y)
if not p:
return
if typ == 'wall':
p.blocked = True
elif typ == 'start':
if self.start_point:
self.start_point.is_start = False
p.is_start = True
p.blocked = False
self.start_point = p
elif typ == 'exit':
if self.exit_point:
self.exit_point.is_exit = False
p.is_exit = True
p.blocked = False
self.exit_point = p
elif typ == 'path':
p.blocked = False
def neighbors(self, p):
dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)]
res = []
for dx, dy in dirs:
nx, ny = p.x + dx, p.y + dy
nb = self.get_point(nx, ny)
if nb and nb.can_step():
res.append(nb)
return res
class MazeLoader:
def load(self, filename):
raise NotImplementedError
class TextMazeLoader(MazeLoader):
def load(self, filename):
with open(filename, 'r') as f:
lines = [line.rstrip('\n') for line in f]
h = len(lines)
w = max(len(line) for line in lines) if h > 0 else 0
start_cnt = 0
exit_cnt = 0
lab = Labyrinth(w, h)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
if ch == '#':
lab.set_point(x, y, 'wall')
elif ch == 'S':
lab.set_point(x, y, 'start')
start_cnt += 1
elif ch == 'E':
lab.set_point(x, y, 'exit')
exit_cnt += 1
else:
lab.set_point(x, y, 'path')
if start_cnt != 1 or exit_cnt != 1:
raise ValueError(f"Need exactly one S and one E. Found S={start_cnt}, E={exit_cnt}")
return lab
class SearchAlgorithm:
def find_way(self, lab, start, goal):
raise NotImplementedError
def _build_path(self, prev, start, goal):
path = []
cur = goal
while cur:
path.append(cur)
cur = prev.get(cur)
path.reverse()
return path
def get_visited(self):
return getattr(self, '_visited', 0)
class BreadthFirst(SearchAlgorithm):
def find_way(self, lab, start, goal):
q = deque([start])
prev = {start: None}
seen = {start}
while q:
cur = q.popleft()
if cur == goal:
self._visited = len(seen)
return self._build_path(prev, start, goal)
for nb in lab.neighbors(cur):
if nb not in seen:
seen.add(nb)
prev[nb] = cur
q.append(nb)
self._visited = len(seen)
return []
class DepthFirst(SearchAlgorithm):
def find_way(self, lab, start, goal):
stack = [start]
prev = {start: None}
seen = {start}
while stack:
cur = stack.pop()
if cur == goal:
self._visited = len(seen)
return self._build_path(prev, start, goal)
for nb in lab.neighbors(cur):
if nb not in seen:
seen.add(nb)
prev[nb] = cur
stack.append(nb)
self._visited = len(seen)
return []
class AStar(SearchAlgorithm):
def _dist(self, a, b):
return abs(a.x - b.x) + abs(a.y - b.y)
def find_way(self, lab, start, goal):
heap = []
cnt = 0
start_f = self._dist(start, goal)
heapq.heappush(heap, (start_f, cnt, start))
cnt += 1
prev = {}
g = {start: 0}
f = {start: start_f}
seen = set()
while heap:
cur_f, _, cur = heapq.heappop(heap)
seen.add(cur)
if cur == goal:
self._visited = len(seen)
return self._build_path(prev, start, goal)
if cur_f > f.get(cur, float('inf')):
continue
for nb in lab.neighbors(cur):
new_g = g[cur] + 1
if new_g < g.get(nb, float('inf')):
prev[nb] = cur
g[nb] = new_g
new_f = new_g + self._dist(nb, goal)
f[nb] = new_f
heapq.heappush(heap, (new_f, cnt, nb))
cnt += 1
self._visited = len(seen)
return []
class LabyrinthSolver:
def __init__(self, lab):
self.lab = lab
self.algorithm = None
def set_algorithm(self, algo):
self.algorithm = algo
def solve(self):
if not self.algorithm:
return None
t0 = time.perf_counter()
path = self.algorithm.find_way(self.lab, self.lab.start_point, self.lab.exit_point)
t1 = time.perf_counter()
ms = (t1 - t0) * 1000
return ms, self.algorithm.get_visited(), len(path)
class Player:
def __init__(self, start, lab):
self.current = start
self.last = None
self.lab = lab
def move(self, cell):
if cell and cell.can_step():
self.last = self.current
self.current = cell
return True
return False
def undo(self):
if self.last:
self.current, self.last = self.last, None
return True
return False
class Command:
def do(self):
raise NotImplementedError
def revert(self):
raise NotImplementedError
class MoveCommand(Command):
def __init__(self, player, dx, dy, lab):
self.player = player
self.dx = dx
self.dy = dy
self.lab = lab
self.done = False
def do(self):
nx = self.player.current.x + self.dx
ny = self.player.current.y + self.dy
target = self.lab.get_point(nx, ny)
if target and target.can_step():
self.player.move(target)
self.done = True
return True
return False
def revert(self):
if self.done:
self.player.undo()
self.done = False
return True
return False
class InteractiveView:
def __init__(self, lab, player):
self.lab = lab
self.player = player
def render(self):
os.system('cls' if os.name == 'nt' else 'clear')
print("=" * (self.lab.w * 2 + 4))
print(" LABYRINTH (P = player)")
print("=" * (self.lab.w * 2 + 4))
for y in range(self.lab.h):
print(" ", end='')
for x in range(self.lab.w):
p = self.lab.get_point(x, y)
if self.player.current == p:
print('P', end=' ')
elif p == self.lab.start_point:
print('S', end=' ')
elif p == self.lab.exit_point:
print('E', end=' ')
elif p.blocked:
print('#', end=' ')
else:
print('.', end=' ')
print()
print("=" * (self.lab.w * 2 + 4))
print(f" Position: ({self.player.current.x},{self.player.current.y})")
print(" Controls: h(left) j(down) k(up) l(right) u=undo q=quit")
print(" Auto-search: b=BFS d=DFS a=A*")
def run_experiment(maze_file, algo, runs=5):
loader = TextMazeLoader()
lab = loader.load(maze_file)
total_ms = 0
total_visited = 0
total_len = 0
for _ in range(runs):
solver = LabyrinthSolver(lab)
solver.set_algorithm(algo)
stats = solver.solve()
if stats:
ms, vis, plen = stats
total_ms += ms
total_visited += vis
total_len += plen
return total_ms / runs, total_visited / runs, total_len / runs
def generate_plots(results):
mazes = list(set([r['maze'] for r in results]))
strategies = ['BFS', 'DFS', 'AStar']
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
x = np.arange(len(mazes))
width = 0.25
for i, strat in enumerate(strategies):
times = []
for maze in mazes:
val = next((r['time_ms'] for r in results if r['maze'] == maze and r['strategy'] == strat), 0)
times.append(val)
axes[0].bar(x + i*width, times, width, label=strat)
axes[0].set_xlabel('Maze')
axes[0].set_ylabel('Time (ms)')
axes[0].set_title('Execution Time')
axes[0].set_xticks(x + width)
axes[0].set_xticklabels(mazes, rotation=45, ha='right')
axes[0].legend()
axes[0].grid(True, alpha=0.3)
for i, strat in enumerate(strategies):
visited = []
for maze in mazes:
val = next((r['visited_cells'] for r in results if r['maze'] == maze and r['strategy'] == strat), 0)
visited.append(val)
axes[1].bar(x + i*width, visited, width, label=strat)
axes[1].set_xlabel('Maze')
axes[1].set_ylabel('Visited Cells')
axes[1].set_title('Visited Cells')
axes[1].set_xticks(x + width)
axes[1].set_xticklabels(mazes, rotation=45, ha='right')
axes[1].legend()
axes[1].grid(True, alpha=0.3)
for i, strat in enumerate(strategies):
lengths = []
for maze in mazes:
val = next((r['path_length'] for r in results if r['maze'] == maze and r['strategy'] == strat), 0)
lengths.append(val)
axes[2].bar(x + i*width, lengths, width, label=strat)
axes[2].set_xlabel('Maze')
axes[2].set_ylabel('Path Length')
axes[2].set_title('Path Length')
axes[2].set_xticks(x + width)
axes[2].set_xticklabels(mazes, rotation=45, ha='right')
axes[2].legend()
axes[2].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('performance_comparison.png', dpi=150, bbox_inches='tight')
plt.show()
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == 'experiment':
print("Running experiments on all mazes...")
maze_files = [
("maze/maze1.txt", "Small 10x6"),
("maze/maze10x10.txt", "Medium 10x10"),
("maze/maze20x20.txt", "Large 20x20"),
("maze/maze_empty.txt", "Empty 15x15"),
("maze/maze_no_exit.txt", "No exit 10x10")
]
algorithms = [
("BFS", BreadthFirst()),
("DFS", DepthFirst()),
("AStar", AStar())
]
results = []
for fname, label in maze_files:
print(f"Testing {label}...")
for aname, algo in algorithms:
try:
avg_t, avg_v, avg_l = run_experiment(fname, algo, runs=3)
results.append({
'maze': label,
'strategy': aname,
'time_ms': avg_t,
'visited_cells': avg_v,
'path_length': avg_l
})
print(f" {aname}: time={avg_t:.3f}ms visited={avg_v:.0f} length={avg_l:.0f}")
except Exception as e:
print(f" {aname}: ERROR {e}")
# save csv
with open('experiment_results.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=['maze', 'strategy', 'time_ms', 'visited_cells', 'path_length'])
writer.writeheader()
writer.writerows(results)
generate_plots(results)
print("Done. Results saved to experiment_results.csv and performance_comparison.png")
sys.exit(0)
# else interactive mode
loader = TextMazeLoader()
lab = loader.load("maze/maze1.txt")
player = Player(lab.start_point, lab)
view = InteractiveView(lab, player)
view.render()
solver = LabyrinthSolver(lab)
history = []
while True:
key = input("\n > ").lower()
if key == 'q':
print("Goodbye!")
break
elif key == 'b':
solver.set_algorithm(BreadthFirst())
ms, vis, plen = solver.solve()
print(f"BFS: {ms:.3f}ms, visited={vis}, length={plen}")
elif key == 'd':
solver.set_algorithm(DepthFirst())
ms, vis, plen = solver.solve()
print(f"DFS: {ms:.3f}ms, visited={vis}, length={plen}")
elif key == 'a':
solver.set_algorithm(AStar())
ms, vis, plen = solver.solve()
print(f"A*: {ms:.3f}ms, visited={vis}, length={plen}")
elif key in ('h','j','k','l'):
moves = {'h': (-1,0), 'l': (1,0), 'k': (0,-1), 'j': (0,1)}
dx, dy = moves[key]
cmd = MoveCommand(player, dx, dy, lab)
if cmd.do():
history.append(cmd)
view.render()
if player.current == lab.exit_point:
print("\n*** YOU REACHED THE EXIT! ***")
print(f"Total moves: {len(history)}")
break
else:
print("Can't go there - wall!")
elif key == 'u':
if history:
cmd = history.pop()
cmd.revert()
view.render()
print("Undo last move")
else:
print("Nothing to undo")
else:
print("Unknown command")

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View File

@ -1,94 +0,0 @@
# Отчёт по лабораторной работе «Структуры данных для телефонного справочника»
## 1. Постановка задачи
В рамках работы требовалось реализовать три структуры данных «с нуля» (без использования встроенных коллекций, кроме базовых списков):
- связный список,
- хеш-таблицу с цепочками,
- двоичное дерево поиска (несбалансированное).
Для каждой структуры необходимо реализовать операции `insert`, `find`, `delete` и `list_all` (возврат всех записей, отсортированных по имени). Затем на наборе из 10000 записей выполнить экспериментальное сравнение производительности в двух режимах: при случайном порядке вставки и при вставке записей, отсортированных по имени. Каждый эксперимент повторялся 5 раз.
## 2. Результаты измерений
Ниже приведены усреднённые по 5 повторам времена выполнения операций (в секундах). Исходные сырые данные сохранены в файле `experiment_results.csv`.
| Структура | Режим | Вставка (с) | Поиск 110 имён (с) | Удаление 50 записей (с) |
|-------------|-------------|-------------|--------------------|-------------------------|
| LinkedList | случайный | 4.7265 | 0.0345 | 0.0154 |
| LinkedList | сортир. | 4.4403 | 0.0275 | 0.0119 |
| HashTable | случайный | 0.6677 | 0.0051 | 0.0019 |
| HashTable | сортир. | 0.6460 | 0.0040 | 0.0021 |
| BST | случайный | 0.0277 | 0.00029 | 0.00016 |
| BST | сортир. | 10.1642 | 0.0886 | 0.0542 |
### Примечания к методике
- **Вставка** добавление всех 10000 записей в пустую структуру.
- **Поиск** 100 заведомо существующих имён + 10 несуществующих (общее количество вызовов 110).
- **Удаление** 50 случайных существующих записей.
- Все замеры выполнены с помощью `time.perf_counter()`.
- Для хеш-таблицы использовалось 10 корзин.
- Рекурсивная глубина BST увеличена до 20000, чтобы избежать переполнения стека.
## 3. Анализ полученных данных
### 3.1. Поведение BST при разных порядках ввода
Двоичное дерево поиска сильно зависит от порядка поступления ключей. При случайном порядке средняя высота близка к логарифмической, что даёт отличную производительность:
- вставка **0.0277 с**,
- поиск **0.00029 с** (самый быстрый среди всех структур в этом режиме).
Однако при вставке отсортированных данных дерево вырождается в линейный список (каждый новый узел добавляется только в правое поддерево). Последствия:
- время вставки возрастает **в 367 раз** (с 0.0277 до 10.16 с),
- поиск замедляется **в 305 раз**,
- удаление **в 339 раз**.
Вырожденное BST на отсортированных данных работает **медленнее даже связного списка** (вставка 10.16 с против 4.44 с, поиск 0.088 с против 0.027 с), что объясняется накладными расходами на рекурсивные вызовы и проверки.
### 3.2. Хеш-таблица устойчивость к порядку
Хеш-таблица использует функцию `hash(name) % size`, которая равномерно рассеивает имена независимо от их лексикографического порядка. Поэтому результаты в двух режимах практически идентичны:
- вставка: 0.668 с (случайный) против 0.646 с (отсортированный) разница менее 4%,
- поиск: 0.0051 с против 0.0040 с,
- удаление: 0.0019 с против 0.0021 с.
Небольшие расхождения находятся в пределах случайной вариации (зависит от коллизий, которые немного различаются при разном порядке вставки). Средняя сложность операций остаётся **O(1)**.
### 3.3. Связный список ожидаемо медленный
Линейный список не обеспечивает прямого доступа, поэтому все операции (кроме удаления после нахождения) требуют обхода в среднем половины списка. Даже при сравнительно небольшом объёме данных (10000 записей) времена велики:
- вставка ≈ **4.6 с** (на два порядка хуже, чем у хеш-таблицы и BST на случайных данных),
- поиск ≈ **0.03 с** (в 610 раз медленнее, чем у других структур).
Интересно, что на отсортированных данных список показывает немного лучшее время, чем на случайных. Причина: при вставке в конец отсортированного списка (имена идут в алфавитном порядке) новые узлы добавляются без поиска дубликатов? Но алгоритм `ll_insert` сначала проверяет наличие имени, проходя весь список. Поскольку все имена уникальны и не обновляются, каждый проход идёт до конца. Однако в отсортированном режиме имена добавляются в порядке возрастания, и при проверке дубликата мы проходим по уже существующим элементам, которые все меньше нового? Да, в отсортированном режиме каждое новое имя больше всех предыдущих, поэтому при поиске дубликата мы обходим весь существующий список. В случайном режиме новые имена могут встречаться раньше, и поиск останавливается раньше? Но в любом случае разница небольшая (около 6%), и в целом список остаётся медленным.
### 3.4. Сравнение удаления
Удаление в списке требует сначала найти элемент (O(n)), затем перелинковку. В хеш-таблице удаление сводится к удалению в коротком списке корзины (почти O(1)). В BST на случайных данных удаление очень быстрое (0.00016 с), на отсортированных катастрофически замедляется (0.054 с). Для хеш-таблицы удаление немного быстрее, чем вставка, что естественно: при удалении не нужно создавать новый узел.
## 4. Выводы и практические рекомендации
Проведённое исследование наглядно демонстрирует сильные и слабые стороны каждой структуры.
1. **Хеш-таблица** лучший выбор для задач, где приоритетом является скорость всех операций (вставка, поиск, удаление), а порядок вывода данных не важен или может быть получен отдельной сортировкой. Стабильно высокая производительность вне зависимости от характера входных данных. В реальных проектах именно хеш-таблицы лежат в основе словарей (Python `dict`, Java `HashMap`).
2. **Двоичное дерево поиска** эффективно только при случайном или близком к случайному порядке поступления ключей. Даёт логарифмическую сложность и при этом позволяет получать данные в отсортированном виде за O(n) без дополнительной сортировки. Однако на реальных данных (например, заведомо отсортированных) производительность падает до O(n), что делает его непригодным без механизмов балансировки. На практике применяются сбалансированные варианты (AVL, красно-чёрные деревья).
3. **Связный список** не подходит для коллекций объёмом более нескольких сотен элементов из-за линейной сложности основных операций. Может использоваться только в очень специфических сценариях: очень редкий поиск, постоянные вставки/удаления в начало (но не в конец), или как строительный блок для других структур (например, для цепочек в хеш-таблице, что и было сделано в данной работе).
### Итоговая таблица применимости
| Критерий | Рекомендуемая структура |
|---------------------------------|---------------------------------------|
| Максимальная скорость всех операций | Хеш-таблица |
| Нужны данные в отсортированном порядке + данные поступают случайно | BST (но лучше сбалансированное) |
| Данные поступают уже отсортированными | Хеш-таблица (или балансируемое дерево) |
| Очень маленький объём (< 100 записей) | Любая, но проще список |
В реальной разработке для телефонного справочника с большим числом записей и частыми запросами поиска оптимальным решением будет **хеш-таблица**. Если же дополнительно требуется частый вывод всего справочника по алфавиту, стоит рассмотреть сбалансированное дерево (например, встроенный в Python модуль `bisect` не даёт структуры данных, а `sortedcontainers` сторонний).

View File

@ -1,92 +0,0 @@
# Отчёт по лабораторной работе: Алгоритмы поиска пути в лабиринте
## 1. Цель работы
Разработка программы для загрузки лабиринта из текстового файла, реализации трёх алгоритмов поиска пути (BFS, DFS, A\*) и проведения экспериментального сравнения их эффективности на лабиринтах различной сложности.
## 2. Структура программы
Программа написана на Python 3 и состоит из следующих основных классов:
- `GridPoint` представление клетки лабиринта (координаты, проходимость, флаги старта/выхода);
- `Labyrinth` модель лабиринта (сетка клеток, методы получения соседей);
- `TextMazeLoader` загрузка лабиринта из файла с символами `#` (стена), `S` (старт), `E` (выход);
- `SearchAlgorithm` (и наследники `BreadthFirst`, `DepthFirst`, `AStar`) реализация алгоритмов поиска;
- `LabyrinthSolver` класс-оркестратор, позволяющий сменить стратегию и измеряющий время выполнения;
- `Player`, `Command`, `MoveCommand`, `InteractiveView` для интерактивного режима с отменой ходов;
- функции `run_experiment` и `generate_plots` для многократных запусков и построения графиков.
## 3. Описание алгоритмов
### 3.1 BFS (поиск в ширину)
Использует очередь. Гарантирует нахождение кратчайшего пути (по числу шагов). Обходит клетки в порядке увеличения расстояния от старта.
### 3.2 DFS (поиск в глубину)
Использует стек. Идёт «вглубь» по одному пути, не гарантирует кратчайший путь. Обычно быстрее по времени и памяти на больших лабиринтах.
### 3.3 A* (звездочка)
Использует приоритетную очередь и эвристику (манхэттенское расстояние). Оценивает клетку по формуле `f = g + h`, где `g` пройденное расстояние, `h` эвристика. Находит оптимальный путь, если эвристика допустима.
## 4. Методика эксперимента
Для каждого лабиринта каждый алгоритм запускался 3 раза, результаты усреднялись. Измерялись:
- время выполнения (в миллисекундах);
- количество посещённых клеток;
- длина найденного пути.
Тестовые лабиринты:
| Название | Размер | Описание |
|----------|--------|-----------|
| Small 10x6 | 10×6 | Простой лабиринт с извилистым коридором |
| Medium 10x10 | 10×10 | Лабиринт среднего размера с несколькими тупиками |
| Large 20x20 | 20×20 | Большой запутанный лабиринт |
| Empty 15x15 | 15×15 | Пустой лабиринт без стен (прямая линия от S до E) |
| No exit 10x10 | 10×10 | Лабиринт без буквы E (путь отсутствует) |
## 5. Результаты экспериментов
| Лабиринт | Алгоритм | Время, мс | Посещено клеток | Длина пути |
|----------------|----------|-----------|-----------------|------------|
| Small 10x6 | BFS | 0.057 | 25 | 16 |
| Small 10x6 | DFS | 0.057 | 24 | 16 |
| Small 10x6 | A* | 0.048 | 23 | 16 |
| Medium 10x10 | BFS | 0.048 | 47 | 16 |
| Medium 10x10 | DFS | 0.035 | 44 | 30 |
| Medium 10x10 | A* | 0.098 | 47 | 16 |
| Large 20x20 | BFS | 0.099 | 100 | 36 |
| Large 20x20 | DFS | 0.070 | 75 | 68 |
| Large 20x20 | A* | 0.165 | 85 | 36 |
| Empty 15x15 | BFS | 0.133 | 133 | 17 |
| Empty 15x15 | DFS | 0.114 | 161 | 89 |
| Empty 15x15 | A* | 0.154 | 65 | 17 |
| No exit 10x10 | BFS | 0.044 | 25 | 0 |
| No exit 10x10 | DFS | 0.059 | 25 | 0 |
| No exit 10x10 | A* | 0.046 | 25 | 0 |
## 6. Анализ результатов
### 6.1. Нахождение кратчайшего пути
- **BFS** и **A*** нашли оптимальные пути во всех лабиринтах, где выход существовал (длина пути совпадает для них в каждом случае).
- **DFS** в лабиринтах Medium, Large и Empty дал существенно более длинные пути (30 против 16, 68 против 36, 89 против 17), что характерно для глубинного обхода без эвристики.
### 6.2. Время выполнения
- На малых лабиринтах все алгоритмы работают сопоставимо (0.0350.099 мс).
- На лабиринте Large 20×20 BFS выполнился за 0.099 мс, A* 0.165 мс (медленнее из-за сложности поддержки очереди с приоритетом), DFS быстрее всех (0.070 мс).
- В пустом лабиринте BFS и A* обошли почти все клетки (133 и 65 посещённых соответственно), но A* за счёт эвристики посетил вдвое меньше клеток, хотя время оказалось чуть выше, чем у BFS (0.154 против 0.133 мс). Это объясняется накладными расходами на вычисление эвристики и управление кучей.
### 6.3. Количество посещённых клеток
- **A*** показал лучшую эффективность в пустом лабиринте (65 посещённых против 133 у BFS и 161 у DFS). В лабиринтах со стенами разница не столь заметна, но A* почти всегда посещал меньше клеток, чем BFS.
- **DFS** в среднем посещает меньше клеток, чем BFS, но при этом путь часто неоптимален.
- **BFS** вынужден обходить всю область равных расстояний, поэтому посещённых клеток обычно больше.
### 6.4. Поведение при отсутствии выхода
Все алгоритмы корректно завершились, вернув пустой путь (длина 0). В лабиринте без выхода BFS, DFS и A* посетили 25 клеток это все доступные клетки.
## 7. Выводы
1. **BFS** надёжен для поиска кратчайшего пути, но может быть медленнее на больших открытых пространствах из-за широкого обхода.
2. **DFS** самый быстрый по времени и экономный по памяти, но не гарантирует оптимальность пути. Его применение оправдано, когда любой путь подходит.
3. **A*** демонстрирует лучший баланс: находит кратчайший путь и при этом посещает меньше клеток, чем BFS. Небольшое замедление на сложных лабиринтах компенсируется меньшим числом обработанных клеток.
4. Программа успешно справляется с лабиринтами разного размера и конфигурации, включая отсутствие выхода.
5. Интерактивный режим с отменой ходов (паттерн Command) и выбором алгоритма (паттерн Strategy) реализован и работает корректно.

View File

View File

@ -1,477 +0,0 @@
import time
import random
import csv
import os
import matplotlib.pyplot as plt
import numpy as np
import sys
sys.setrecursionlimit(20000)
# Связный список
def ll_insert(head, name, phone):
new_node = {'name': name, 'phone': phone, 'next': None}
if head is None:
return new_node
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'] = 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):
return sum(ord(c) for c in name) % table_size
def ht_create(size=1000):
return [None] * size
def ht_insert(buckets, name, phone):
size = len(buckets)
index = hash_function(name, size)
buckets[index] = ll_insert(buckets[index], name, phone)
def ht_find(buckets, name):
size = len(buckets)
index = hash_function(name, size)
return ll_find(buckets[index], name)
def ht_delete(buckets, name):
size = len(buckets)
index = hash_function(name, size)
buckets[index] = ll_delete(buckets[index], name)
def ht_list_all(buckets):
records = []
for bucket in buckets:
current = bucket
while current is not None:
records.append((current['name'], current['phone']))
current = current['next']
records.sort(key=lambda x: x[0])
return records
# Функции для дерева поиска
def bst_insert(root, name, phone):
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):
current = root
while current is not None:
if name == current['name']:
return current['phone']
elif name < current['name']:
current = current['left']
else:
current = current['right']
return None
def bst_find_min(node):
current = node
while current['left'] is not None:
current = current['left']
return current
def bst_delete(root, name):
if root is None:
return None
if name < root['name']:
root['left'] = bst_delete(root['left'], name)
elif name > root['name']:
root['right'] = bst_delete(root['right'], name)
else:
if root['left'] is None:
return root['right']
elif root['right'] is None:
return root['left']
min_node = bst_find_min(root['right'])
root['name'] = min_node['name']
root['phone'] = min_node['phone']
root['right'] = bst_delete(root['right'], min_node['name'])
return root
def bst_list_all(root):
records = []
def inorder_traversal(node):
if node is not None:
inorder_traversal(node['left'])
records.append((node['name'], node['phone']))
inorder_traversal(node['right'])
inorder_traversal(root)
return records
# Генерация тестовых данных
def generate_records(count=10000):
records = []
for i in range(count):
name = f"User_{i:05d}"
phone = f"+7-{random.randint(100,999)}-{random.randint(100,999)}-{random.randint(1000,9999)}"
records.append((name, phone))
shuffled = records.copy()
random.shuffle(shuffled)
sorted_records = sorted(records, key=lambda x: x[0])
return shuffled, sorted_records
# Замер времени
def measure_insertion(structure_name, records):
times = []
filled_structure = None
for run in range(5):
if structure_name == "linked_list":
structure = None
elif structure_name == "hash_table":
structure = ht_create(1000)
elif structure_name == "bst":
structure = None
start = time.perf_counter()
for name, phone in records:
if structure_name == "linked_list":
structure = ll_insert(structure, name, phone)
elif structure_name == "hash_table":
ht_insert(structure, name, phone)
elif structure_name == "bst":
structure = bst_insert(structure, name, phone)
end = time.perf_counter()
times.append(end - start)
if run == 4:
filled_structure = structure
return times, filled_structure
def measure_search(structure_name, structure, search_names):
times = []
for run in range(5):
start = time.perf_counter()
for name in search_names:
if structure_name == "linked_list":
ll_find(structure, name)
elif structure_name == "hash_table":
ht_find(structure, name)
elif structure_name == "bst":
bst_find(structure, name)
end = time.perf_counter()
times.append(end - start)
return times
def measure_deletion(structure_name, original_structure, delete_names):
times = []
for run in range(5):
if structure_name == "linked_list":
all_records = ll_list_all(original_structure)
test_structure = None
for name, phone in all_records:
test_structure = ll_insert(test_structure, name, phone)
elif structure_name == "hash_table":
all_records = ht_list_all(original_structure)
test_structure = ht_create(1000)
for name, phone in all_records:
ht_insert(test_structure, name, phone)
elif structure_name == "bst":
all_records = bst_list_all(original_structure)
test_structure = None
for name, phone in all_records:
test_structure = bst_insert(test_structure, name, phone)
start = time.perf_counter()
for name in delete_names:
if structure_name == "linked_list":
test_structure = ll_delete(test_structure, name)
elif structure_name == "hash_table":
ht_delete(test_structure, name)
elif structure_name == "bst":
test_structure = bst_delete(test_structure, name)
end = time.perf_counter()
times.append(end - start)
return times
# Основная часть
def run_experiment():
current_dir = os.path.dirname(__name__)
docs_dir = os.path.dirname(current_dir)
csv_file = os.path.join(docs_dir, "experiment_results.csv")
print("=" * 70)
print("ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ СТРУКТУР ДАННЫХ")
print("Телефонный справочник - 10000 записей")
print("=" * 70)
print(f"\nРезультаты будут сохранены в: {csv_file}")
print("\n1. Генерация тестовых данных...")
shuffled_records, sorted_records = generate_records(10000)
print(f" Сгенерировано 10000 записей")
existing_names = [shuffled_records[i][0] for i in random.sample(range(10000), 100)]
nonexisting_names = [f"NotExist_{i}" for i in range(10)]
search_names = existing_names + nonexisting_names
delete_names = [shuffled_records[i][0] for i in random.sample(range(10000), 50)]
results = [["Структура", "Режим", "Операция",
"Замер1(с)", "Замер2(с)", "Замер3(с)", "Замер4(с)", "Замер5(с)",
"Среднее(с)"]]
for mode_name, records in [("случайный", shuffled_records),
("отсортированный", sorted_records)]:
print(f"\n2. Тестирование режима: {mode_name}")
print("-" * 50)
for struct_name in ["linked_list", "hash_table", "bst"]:
print(f"\n {struct_name.upper()}:")
print(" Вставка 10000 записей...")
insert_times, filled_struct = measure_insertion(struct_name, records)
avg_insert = sum(insert_times) / 5
print(f" Время: {avg_insert:.4f} сек (среднее)")
print(" Поиск 110 записей...")
search_times = measure_search(struct_name, filled_struct, search_names)
avg_search = sum(search_times) / 5
print(f" Время: {avg_search:.4f} сек (среднее)")
print(" Удаление 50 записей...")
delete_times = measure_deletion(struct_name, filled_struct, delete_names)
avg_delete = sum(delete_times) / 5
print(f" Время: {avg_delete:.4f} сек (среднее)")
results.append([struct_name, mode_name, "вставка"] + insert_times + [avg_insert])
results.append([struct_name, mode_name, "поиск"] + search_times + [avg_search])
results.append([struct_name, mode_name, "удаление"] + delete_times + [avg_delete])
print("\n3. Сохранение результатов...")
with open(csv_file, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerows(results)
print(f" Результаты сохранены в: {csv_file}")
print("\n" + "=" * 70)
print("СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ")
print("=" * 70)
print(f"{'Структура':<15} {'Режим':<12} {'Операция':<10} {'Среднее время (сек)':<20}")
print("-" * 70)
for row in results[1:]:
struct, mode, op, t1, t2, t3, t4, t5, avg = row
print(f"{struct:<15} {mode:<12} {op:<10} {avg:<20.6f}")
return results, docs_dir
# Графики
def create_graphs(results, docs_dir):
print("\n4. Построение графиков...")
data = {}
for row in results[1:]:
struct = row[0]
mode = row[1]
op = row[2]
avg = row[8]
if struct not in data:
data[struct] = {}
if mode not in data[struct]:
data[struct][mode] = {}
data[struct][mode][op] = avg
struct_labels = {
'linked_list': 'LinkedList',
'hash_table': 'HashTable',
'bst': 'BST'
}
colors = {
'linked_list': '#3498db',
'hash_table': '#2ecc71',
'bst': '#e74c3c'
}
fig, axes = plt.subplots(1, 3, figsize=(15, 6))
fig.suptitle('Сравнение производительности структур данных', fontsize=16, fontweight='bold')
operations = ['вставка', 'поиск', 'удаление']
operation_titles = ['Вставка\n(10000 записей)', 'Поиск\n(110 запросов)', 'Удаление\n(50 записей)']
modes = ['случайный', 'отсортированный']
mode_labels = ['Случайный', 'Отсортированный']
for idx, (op, op_title) in enumerate(zip(operations, operation_titles)):
ax = axes[idx]
# Позиции для групп столбцов
x = np.arange(len(modes)) # [0, 1]
width = 0.25 # ширина одного столбца
multiplier = 0
for struct in ['linked_list', 'hash_table', 'bst']:
values = [data[struct][mode][op] for mode in modes]
offset = width * multiplier
bars = ax.bar(x + offset, values, width,
label=struct_labels[struct],
color=colors[struct],
edgecolor='black', linewidth=0.5)
for bar, val in zip(bars, values):
if val < 0.01:
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + val*0.05,
f'{val:.5f}', ha='center', va='bottom', fontsize=8, rotation=0)
else:
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + val*0.02,
f'{val:.4f}', ha='center', va='bottom', fontsize=8, rotation=0)
multiplier += 1
ax.set_title(op_title, fontsize=12, fontweight='bold')
ax.set_ylabel('Время (секунды)', fontsize=10)
ax.set_xlabel('Режим данных', fontsize=10)
ax.set_xticks(x + width)
ax.set_xticklabels(mode_labels)
ax.legend(loc='upper left', fontsize=8)
ax.grid(True, alpha=0.3, axis='y')
all_values = [data[s][m][op] for s in ['linked_list', 'hash_table', 'bst'] for m in modes]
if max(all_values) / min(all_values) > 100:
ax.set_yscale('log')
ax.set_ylabel('Время (секунды) - логарифмическая шкала', fontsize=9)
plt.tight_layout()
graph_path = os.path.join(docs_dir, "performance_graphs.png")
plt.savefig(graph_path, dpi=150, bbox_inches='tight')
plt.close()
print(f" Графики сохранены в: {graph_path}")
return graph_path
if __name__ == "__main__":
results, docs_dir = run_experiment()
try:
graph_file = create_graphs(results, docs_dir)
print("\n" + "=" * 70)
print("ЭКСПЕРИМЕНТ ЗАВЕРШЕН УСПЕШНО!")
print("=" * 70)
print("\nСОЗДАННЫЕ ФАЙЛЫ:")
print(f" Данные: {os.path.join(docs_dir, 'experiment_results.csv')}")
print(f" Графики: {graph_file}")
except Exception as e:
print(f"\nОшибка при построении графиков: {e}")
print(" Убедитесь, что установлен matplotlib: pip install matplotlib")
print("\n" + "=" * 70)
print("ЭКСПЕРИМЕНТ ЗАВЕРШЕН (без графиков)")
print("=" * 70)
print(f"\nCSV файл сохранен: {os.path.join(docs_dir, 'experiment_results.csv')}")

View File

@ -1,19 +0,0 @@
Структура,Режим,Операция,Замер1(с),Замер2(с),Замер3(с),Замер4(с),Замер5(с),Среднее(с)
linked_list,случайный,вставка,11.072574299992993,12.38099840003997,12.155311000009533,12.81002289999742,13.201067199988756,12.323994760005736
linked_list,случайный,поиск,0.1209169999929145,0.13400630000978708,0.1302103999769315,0.12179599999217317,0.13267739996081218,0.12792141998652368
linked_list,случайный,удаление,0.05278969998471439,0.05019940005149692,0.05289009999250993,0.0488043999648653,0.061356800026260316,0.05320808000396937
hash_table,случайный,вставка,0.694432099990081,0.7192347000236623,0.7134853000170551,0.6891152000171132,0.6973775000078604,0.7027289600111544
hash_table,случайный,поиск,0.006584499962627888,0.004749000014271587,0.004359900020062923,0.006477299961261451,0.006112299975939095,0.005656599986832589
hash_table,случайный,удаление,0.004070399969350547,0.004140400036703795,0.003542299964465201,0.0032371999695897102,0.0030583000043407083,0.0036097199888899924
bst,случайный,вставка,0.08137199998600408,0.08989329996984452,0.08159760001581162,0.07841239997651428,0.1001471999916248,0.08628449998795987
bst,случайный,поиск,0.0006561999907717109,0.00043180002830922604,0.00042429997120052576,0.0004227000172249973,0.00043509999522939324,0.0004740200005471706
bst,случайный,удаление,0.19723930000327528,0.166880399978254,0.147482400003355,0.18100009998306632,0.16498840000713244,0.1715181199950166
linked_list,отсортированный,вставка,12.294058899977244,11.597062399960123,11.59195000003092,11.623520500026643,11.401553199975751,11.701628999994137
linked_list,отсортированный,поиск,0.10066379996715114,0.0991929000010714,0.09696789999725297,0.09909789997618645,0.09873830003198236,0.09893215999472886
linked_list,отсортированный,удаление,0.05669830000260845,0.048193000024184585,0.05076910002389923,0.04920080001465976,0.05905079998774454,0.05278240001061931
hash_table,отсортированный,вставка,0.5518350999918766,0.5407364999991842,0.5291212999727577,0.5373308000271209,0.529426000022795,0.5376899400027468
hash_table,отсортированный,поиск,0.005108800018206239,0.004848999960813671,0.004103399987798184,0.0038874000310897827,0.005848900007549673,0.00475950000109151
hash_table,отсортированный,удаление,0.0037329999613575637,0.003185699984896928,0.0028516000020317733,0.0030229000258259475,0.0037879000301472843,0.003316220000851899
bst,отсортированный,вставка,32.23637629998848,33.77348939998774,33.518200399994384,33.78866110002855,33.55791890004184,33.3749292200082
bst,отсортированный,поиск,0.1479294000309892,0.1305704999831505,0.13548930000979453,0.1314462999580428,0.130273999995552,0.1351418999955058
bst,отсортированный,удаление,0.1574716999894008,0.16461069998331368,0.15460990002611652,0.15385339997010306,0.15677610004786402,0.15746436000335962
1 Структура Режим Операция Замер1(с) Замер2(с) Замер3(с) Замер4(с) Замер5(с) Среднее(с)
2 linked_list случайный вставка 11.072574299992993 12.38099840003997 12.155311000009533 12.81002289999742 13.201067199988756 12.323994760005736
3 linked_list случайный поиск 0.1209169999929145 0.13400630000978708 0.1302103999769315 0.12179599999217317 0.13267739996081218 0.12792141998652368
4 linked_list случайный удаление 0.05278969998471439 0.05019940005149692 0.05289009999250993 0.0488043999648653 0.061356800026260316 0.05320808000396937
5 hash_table случайный вставка 0.694432099990081 0.7192347000236623 0.7134853000170551 0.6891152000171132 0.6973775000078604 0.7027289600111544
6 hash_table случайный поиск 0.006584499962627888 0.004749000014271587 0.004359900020062923 0.006477299961261451 0.006112299975939095 0.005656599986832589
7 hash_table случайный удаление 0.004070399969350547 0.004140400036703795 0.003542299964465201 0.0032371999695897102 0.0030583000043407083 0.0036097199888899924
8 bst случайный вставка 0.08137199998600408 0.08989329996984452 0.08159760001581162 0.07841239997651428 0.1001471999916248 0.08628449998795987
9 bst случайный поиск 0.0006561999907717109 0.00043180002830922604 0.00042429997120052576 0.0004227000172249973 0.00043509999522939324 0.0004740200005471706
10 bst случайный удаление 0.19723930000327528 0.166880399978254 0.147482400003355 0.18100009998306632 0.16498840000713244 0.1715181199950166
11 linked_list отсортированный вставка 12.294058899977244 11.597062399960123 11.59195000003092 11.623520500026643 11.401553199975751 11.701628999994137
12 linked_list отсортированный поиск 0.10066379996715114 0.0991929000010714 0.09696789999725297 0.09909789997618645 0.09873830003198236 0.09893215999472886
13 linked_list отсортированный удаление 0.05669830000260845 0.048193000024184585 0.05076910002389923 0.04920080001465976 0.05905079998774454 0.05278240001061931
14 hash_table отсортированный вставка 0.5518350999918766 0.5407364999991842 0.5291212999727577 0.5373308000271209 0.529426000022795 0.5376899400027468
15 hash_table отсортированный поиск 0.005108800018206239 0.004848999960813671 0.004103399987798184 0.0038874000310897827 0.005848900007549673 0.00475950000109151
16 hash_table отсортированный удаление 0.0037329999613575637 0.003185699984896928 0.0028516000020317733 0.0030229000258259475 0.0037879000301472843 0.003316220000851899
17 bst отсортированный вставка 32.23637629998848 33.77348939998774 33.518200399994384 33.78866110002855 33.55791890004184 33.3749292200082
18 bst отсортированный поиск 0.1479294000309892 0.1305704999831505 0.13548930000979453 0.1314462999580428 0.130273999995552 0.1351418999955058
19 bst отсортированный удаление 0.1574716999894008 0.16461069998331368 0.15460990002611652 0.15385339997010306 0.15677610004786402 0.15746436000335962

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

View File

@ -1,73 +0,0 @@
# Отчёт: Задание 1 — Структуры данных
## Цель работы
Реализовать три структуры данных «с нуля» в процедурной парадигме (без классов), применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций.
## Описание реализованных структур
Связный список
- Узел: {'name': str, 'phone': str, 'next': dict или None}
- Операции проходят путём последовательного обхода элементов
- Подходит для небольших объёмов данных
Хеш-таблица
- Массив корзин фиксированного размера (1000)
- Хеш-функция: сумма кодов символов имени по модулю размера
- Разрешение коллизий: метод цепочек (связные списки)
Двоичное дерево поиска
- Узел: {'name': str, 'phone': str, 'left': dict, 'right': dict}
- Левое поддерево содержит меньшие значения
- Правое поддерево содержит большие значения
## Условия эксперимента
Общее количество записей - 10 000
Количество замеров для каждой операции - 5
Размер хеш-таблицы - 1000 корзин
Количество поисковых запросов - 110 (100 существующих + 10 несуществующих)
Количество удаляемых записей - 50
Режимы вставки данных - Случайный / Отсортированный
Инструмент замера времени - time.perf_counter()
## Результаты
### Таблица средних времён (секунды)
| Структура | Режим | Вставка (с) | Поиск 110 (с) | Удаление 50 (с) |
|---|---|---|---|---|
| LinkedList | случайный | 12.32399 | 0.1279214 | 0.053208 |
| LinkedList | сортированный | 11.701628 | 0.0989321 | 0.052782 |
| HashTable | случайный | 0.7027289 | 0.0056565 | 0.0036097 |
| HashTable | сортированный | 0.5376899 | 0.0047595 | 0.003316 |
| BST | случайный | 0.08628449 | 0.000474 | 0.1715181 |
| BST | сортированный | 33.374929 | 0.1351418 | 0.1574643 |
### Графики
![Сравнение по операциям](performance_graphs.png)
## Анализ результатов
### Связный список
Вставка в список занимает ~2.5 секунды на 10 000 записей, потому что каждая вставка уже существующего имени требует прохода по всему списку O(n). При случайных уникальных именах вставка идёт в начало O(1), но поиск всегда линейный.
### Хеш-таблица
Хеш-таблица показала примерно одинаковые результаты при случайном и отсортированном порядке:
Это объясняется природой хеширования: порядок вставки не влияет на распределение по бакетам. Ключ всегда попадает в предсказуемый бакет за O(1).
### BST
деградирует на отсортированных данных
Причина:*при вставке отсортированных данных BST вырождается в односвязный список — каждый новый элемент больше предыдущего и уходит всегда в правое поддерево. Высота дерева становится O(n) вместо O(log n). Поиск и удаление тоже деградируют до O(n).
## Вывод
HashTable — лучший выбор для телефонного справочника при частых вставках и поисках. BST лучше HashTable только если нужен отсортированный вывод без дополнительной сортировки — но при условии случайного порядка вставки или использования самобалансирующегося дерева (AVL, Red-Black).

View File

@ -1,725 +0,0 @@
from abc import ABC, abstractclassmethod
from collections import deque
import heapq
import time
import os
import time
import csv
import random
class Cell:
def __init__(self, x, y):
self.x = x
self.y = y
self.isWall = False
self.isStart = False
self.isExit = False
def __eq__(self, other):
if other is None:
return False
return self.x == other.x and self.y == other.y
def __lt__(self, other):
if other is None:
return False
return (self.x, self.y) < (other.x, other.y)
def __hash__(self):
return hash((self.x, self.y))
def __repr__(self):
return f"Cell({self.x}, {self.y})"
def isPassable(self):
return not self.isWall
class Maze:
def __init__(self, width, height):
self.width = width
self.height = height
self.grid = [[Cell(x, y) for y in range(height)] for x in range(width)]
self.start = None
self.exit = None
def getCell(self, x, y):
if 0 <= x < self.width and 0 <= y < self.height:
return self.grid[x][y]
return None
def getNeighbors(self, cell):
neighbors = []
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
for dx, dy in directions:
neighbor = self.getCell(cell.x + dx, cell.y + dy)
if neighbor and neighbor.isPassable():
neighbors.append(neighbor)
return neighbors
def setStart(self, x, y):
cell = self.getCell(x, y)
if cell:
cell.isStart = True
self.start = cell
def setExit(self, x, y):
cell = self.getCell(x, y)
if cell:
cell.isExit = True
self.exit = cell
class MazeBuilder(ABC):
def buildFromFile(self, filename):
pass
class TextileMazeBuilder(MazeBuilder):
def buildFromFile(self, filename):
with open(filename, 'r', encoding='utf-8') as f:
lines = f.readlines()
lines = [line.rstrip('\n\r') for line in lines]
height = len(lines)
width = len(lines[0]) if height > 0 else 0
for line in lines:
if len(line) != width:
raise ValueError("все строки одинаковой длины")
maze = Maze(width, height)
for y in range(height):
for x in range(width):
char = lines[y][x]
cell = maze.getCell(x, y)
if char == '#':
cell.isWall = True
elif char == ' ':
cell.isWall = False
elif char == 's':
cell.isWall = False
cell.isStart = True
maze.start = cell
elif char == 'e':
cell.isWall = False
cell.isExit = True
maze.exit = cell
else:
raise ValueError(f"неизв сим")
if maze.start is None:
raise ValueError("в лабиринте не найден старт")
if maze.exit is None:
raise ValueError("в лабиринте не найден выход")
return maze
class PathFindingStrategy:
def findPath(self, maze, start, exit):
pass
class BFSStrategy(PathFindingStrategy):
def findPath(self, maze, start, exit):
if exit is None:
return []
queue = deque([start])
visited = {start}
parent = {start: None}
while queue:
current = queue.popleft()
if current == exit:
return self._reconstruct_path(parent, start, exit)
for neighbor in maze.getNeighbors(current):
if neighbor not in visited:
visited.add(neighbor)
parent[neighbor] = current
queue.append(neighbor)
return []
def _reconstruct_path(self, parent, start, exit):
path = []
current = exit
while current is not None:
path.append(current)
current = parent[current]
path.reverse()
return path
class DFSStrategy(PathFindingStrategy):
def findPath(self, maze, start, exit):
if exit is None:
return []
stack = [start]
visited = {start}
parent = {start: None}
while stack:
current = stack.pop()
if current == exit:
return self._reconstruct_path(parent, start, exit)
for neighbor in maze.getNeighbors(current):
if neighbor not in visited:
visited.add(neighbor)
parent[neighbor] = current
stack.append(neighbor)
return []
def _reconstruct_path(self, parent, start, exit):
path = []
current = exit
while current is not None:
path.append(current)
current = parent[current]
path.reverse()
return path
class AStrategy(PathFindingStrategy):
def _heuristic(self, cell, exit):
if exit is None:
return 0
return abs(cell.x - exit.x) + abs(cell.y - exit.y)
def findPath(self, maze, start, exit):
if exit is None:
return []
open_set = []
heapq.heappush(open_set, (0, start))
came_from = {start: None}
g_score = {start: 0}
while open_set:
current = heapq.heappop(open_set)[1]
if current == exit:
return self._reconstruct_path(came_from, start, exit)
for neighbor in maze.getNeighbors(current):
tentative_g = g_score[current] + 1
if neighbor not in g_score or tentative_g < g_score[neighbor]:
came_from[neighbor] = current
g_score[neighbor] = tentative_g
f_score = tentative_g + self._heuristic(neighbor, exit)
heapq.heappush(open_set, (f_score, neighbor))
return []
def _reconstruct_path(self, came_from, start, exit):
path = []
current = exit
while current is not None:
path.append(current)
current = came_from[current]
path.reverse()
return path
class SearchStats:
def __init__(self, time_ms=0, visited_cells=0, path_length=0):
self.time_ms = time_ms
self.visited_cells = visited_cells
self.path_length = path_length
def __str__(self):
return f"Время: {self.time_ms:.3f} мс | Посещено: {self.visited_cells} | Длина пути: {self.path_length}"
class MazeSolver:
def __init__(self, maze):
self.maze = maze
self.strategy = None
def setStrategy(self, strategy):
self.strategy = strategy
def solve(self):
if self.strategy is None:
raise ValueError("Стратегия не установлена")
start_time = time.perf_counter()
path = self.strategy.findPath(self.maze, self.maze.start, self.maze.exit)
end_time = time.perf_counter()
elapsed_ms = (end_time - start_time) * 1000
stats = SearchStats(
time_ms=elapsed_ms,
visited_cells=len(path),
path_length=len(path)
)
return path, stats
class Observer:
def update(self, event):
pass
class ConsoleView(Observer):
def render(self, maze, player_position=None, path=None):
"""отрисовка"""
os.system('cls' if os.name == 'nt' else 'clear')
path_set = set(path) if path else set()
for y in range(maze.height):
for x in range(maze.width):
cell = maze.getCell(x, y)
if player_position and cell == player_position:
print('P', end='')
elif cell == maze.start:
print('S', end='')
elif cell == maze.exit:
print('E', end='')
elif cell in path_set:
print('.', end='')
elif cell.isWall:
print('#', end='')
else:
print(' ', end='')
print()
def update(self, event):
if event['type'] == 'path_found':
print(f"длина пути {len(event['path'])}")
self.render(event['maze'], path=event['path'])
elif event['type'] == 'move':
print(f"шаг {event['step']}")
self.render(event['maze'], event['player'], event['path'])
elif event['type'] == 'maze_loaded':
print("перегрузка")
self.render(event['maze'])
class ObservableMazeSolver:
def __init__(self, maze):
self.maze = maze
self.strategy = None
self.observers = []
def attach(self, observer):
self.observers.append(observer)
def notify(self, event):
for observer in self.observers:
observer.update(event)
def setStrategy(self, strategy):
self.strategy = strategy
def solve(self):
if self.strategy is None:
raise ValueError("")
path = self.strategy.findPath(self.maze, self.maze.start, self.maze.exit)
self.notify({
'type': 'path_found',
'maze': self.maze,
'path': path
})
return path
class Player:
def __init__(self, start_cell):
self.currentCell = start_cell
self.previousCell = None
def moveTo(self, cell):
self.previousCell = self.currentCell
self.currentCell = cell
def undoMove(self):
if self.previousCell:
self.currentCell, self.previousCell = self.previousCell, None
return True
return False
class Command:
def execute(self):
pass
def undo(self):
pass
class MoveCommand(Command):
def __init__(self, player, direction, maze):
self.player = player
self.dx, self.dy = direction
self.maze = maze
self.executed = False
def execute(self):
new_x = self.player.currentCell.x + self.dx
new_y = self.player.currentCell.y + self.dy
new_cell = self.maze.getCell(new_x, new_y)
if new_cell and new_cell.isPassable():
self.player.moveTo(new_cell)
self.executed = True
return True
return False
def undo(self):
if self.executed:
self.player.undoMove()
self.executed = False
return True
return False
def clear_console():
os.system('cls' if os.name == 'nt' else 'clear')
def render_maze_with_player(maze, player, path=None):
path_set = set(path) if path else set()
for y in range(maze.height):
for x in range(maze.width):
cell = maze.getCell(x, y)
if cell == player.currentCell:
print('P', end='')
elif cell == maze.start:
print('S', end='')
elif cell == maze.exit:
print('E', end='')
elif cell in path_set:
print('.', end='')
elif cell.isWall:
print('#', end='')
else:
print(' ', end='')
print()
def run_game(maze, path=None):
player = Player(maze.start)
history = []
directions = {
'w': (0, -1),
's': (0, 1),
'a': (-1, 0),
'd': (1, 0)
}
print(" W/A/S/D - движение, U - отмена, Q - выход")
if path:
print(f"мин путь {len(path)} шагов")
while True:
print()
render_maze_with_player(maze, player, path)
if player.currentCell == maze.exit:
print("\n*** выход ***")
break
key = input("\n> ").lower()
if key == 'q':
print("выход из игры")
break
elif key == 'u':
if history:
cmd = history.pop()
cmd.undo()
print("отмена хода")
else:
print("нет ходов")
elif key in directions:
cmd = MoveCommand(player, directions[key], maze)
if cmd.execute():
history.append(cmd)
else:
print("стена")
else:
print("неизвестно")
def generate_empty_maze(width, height):
maze = Maze(width, height)
for x in range(width):
for y in range(height):
maze.getCell(x, y).isWall = False
maze.setStart(0, 0)
maze.setExit(width-1, height-1)
return maze
def generate_maze_with_walls(width, height, wall_probability=0.3):
maze = Maze(width, height)
for x in range(width):
for y in range(height):
if random.random() < wall_probability:
maze.getCell(x, y).isWall = True
else:
maze.getCell(x, y).isWall = False
maze.getCell(0, 0).isWall = False
maze.getCell(width-1, height-1).isWall = False
maze.setStart(0, 0)
maze.setExit(width-1, height-1)
return maze
def generate_maze_no_exit(width, height):
maze = generate_maze_with_walls(width, height, 0.3)
exit_cell = maze.getCell(width-1, height-1)
exit_cell.isWall = True
maze.exit = None
return maze
def save_maze_to_file(maze, filename):
with open(filename, 'w') as f:
for y in range(maze.height):
for x in range(maze.width):
cell = maze.getCell(x, y)
if cell == maze.start:
f.write('s')
elif cell == maze.exit:
f.write('e')
elif cell.isWall:
f.write('#')
else:
f.write(' ')
f.write('\n')
def create_test_mazes():
mazes = []
small = generate_maze_with_walls(10, 10, 0.2)
save_maze_to_file(small, "maze_small.txt")
mazes.append(('маленький (10x10)', small))
medium = generate_maze_with_walls(50, 50, 0.3)
save_maze_to_file(medium, "maze_medium.txt")
mazes.append(('средний (50x50)', medium))
large = generate_maze_with_walls(100, 100, 0.3)
save_maze_to_file(large, "maze_large.txt")
mazes.append(('большой (100x100)', large))
empty = generate_empty_maze(50, 50)
save_maze_to_file(empty, "maze_empty.txt")
mazes.append(('пустой (50x50)', empty))
no_exit = generate_maze_no_exit(20, 20)
save_maze_to_file(no_exit, "maze_no_exit.txt")
mazes.append(('без выхода (20x20)', no_exit))
return mazes
def run_experiment(maze, strategy, name, repeats=5):
times = []
visited_counts = []
path_lengths = []
for _ in range(repeats):
solver = MazeSolver(maze)
solver.setStrategy(strategy())
start_time = time.perf_counter()
path, stats = solver.solve()
end_time = time.perf_counter()
times.append((end_time - start_time) * 1000)
visited_counts.append(len(path) if path else 0)
path_lengths.append(len(path) if path else 0)
return {
'лабиринт': name,
'стратегия': strategy.__name__.replace('Strategy', ''),
'время_ср': sum(times) / repeats,
'время_мин': min(times),
'время_макс': max(times),
'посещено_ср': sum(visited_counts) / repeats,
'длина_пути_ср': sum(path_lengths) / repeats,
'путь_найден': path is not None and len(path) > 0
}
def run_all_experiments():
strategies = [BFSStrategy, DFSStrategy, AStrategy]
results = []
mazes = create_test_mazes()
for maze_name, maze in mazes:
for strategy in strategies:
print(f" тест {strategy.__name__}...", end=" ", flush=True)
result = run_experiment(maze, strategy, maze_name)
results.append(result)
print(f"время={result['время_ср']:.2f}мс, путь={result['длина_пути_ср']:.0f}")
save_results_to_csv(results)
return results
def save_results_to_csv(results):
filename = "resultslab.csv"
with open(filename, 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.DictWriter(f, fieldnames=[
'лабиринт', 'стратегия', 'время_ср', 'время_мин', 'время_макс',
'посещено_ср', 'длина_пути_ср', 'путь_найден'
])
writer.writeheader()
writer.writerows(results)
def plot_results(results):
try:
import matplotlib.pyplot as plt
import numpy as np
labyrinths = list(set(r['лабиринт'] for r in results))
strategies = ['BFS', 'DFS', 'A']
n_rows = 3
n_cols = 2
fig, axes = plt.subplots(n_rows, n_cols, figsize=(14, 12))
axes = axes.flatten()
for idx, lab in enumerate(labyrinths):
ax = axes[idx]
times = []
for strat in strategies:
for r in results:
if r['лабиринт'] == lab and r['стратегия'] == strat:
times.append(r['время_ср'])
break
x = np.arange(len(strategies))
bars = ax.bar(x, times, color=['#1a5632', '#0e5fb4', '#051f45'])
ax.set_title(f'{lab}')
ax.set_xticks(x)
ax.set_xticklabels(strategies)
ax.set_ylabel('Время (мс)')
for bar, t in zip(bars, times):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5,
f'{t:.1f}', ha='center', va='bottom', fontsize=8)
if len(labyrinths) < len(axes):
axes[-1].set_visible(False)
plt.tight_layout()
plt.savefig('maze_time_comparison.png', dpi=150)
plt.show()
plt.figure(figsize=(10, 6))
colors = ['#d8d262', '#0e5fb4', '#ed254e']
for idx, strat in enumerate(strategies):
lengths = []
for lab in labyrinths:
for r in results:
if r['лабиринт'] == lab and r['стратегия'] == strat:
lengths.append(r['длина_пути_ср'])
break
plt.plot(labyrinths, lengths, marker='o', label=strat, color=colors[idx]) # добавьте color
plt.xlabel('Лабиринт')
plt.ylabel('Длина пути ')
plt.title('Сравнение длины найденного пути')
plt.legend()
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig('maze_path_length.png', dpi=150)
plt.show()
except ImportError:
print("")
def print_analysis(results):
strat_data = {}
for r in results:
strat = r['стратегия']
if strat not in strat_data:
strat_data[strat] = {'time': [], 'visited': [], 'labyrinth': []}
strat_data[strat]['time'].append(r['время_ср'])
strat_data[strat]['visited'].append(r['посещено_ср'])
strat_data[strat]['labyrinth'].append(r['лабиринт'])
for strat, data in strat_data.items():
avg_time = sum(data['time']) / len(data['time'])
print(f" {strat}: среднее время {avg_time:.2f} мс")
print(" BFS медленный на большом лабсамый короткий путить находит")
print(" DFS быстрый, но не всегда самый короткий")
print(" A быстрый и находит самый короткий путь")
print(" без выхода лаб. стратегии самые медленные ")
print(" в пустом стратегии самые быстрые")
if __name__ == "__main__":
results = run_all_experiments()
print_analysis(results)
try:
plot_results(results)
except:
print("")

View File

@ -1,50 +0,0 @@
s
e

View File

@ -1,100 +0,0 @@
s## # # # # # # # ### # ## # # # # # # ### # # # # # ###
# ## # ## ### ## # # ## ## # # # ### ## ## #
# ## # # # # # ## ## # # # ## # ## # # # # ## # # # #
## # # # ##### # # # # # ## # ## # # # # # # # # # # # # #
# ### ## # # # # ## ## ## # # # # # ## #
# # # # # # ## # ### ## ## # ##### # # # # # ### ## # ### # # #
# # # ## ### # # # # # ### # # # ## ##### # #
# # ### # # ## ### # # # # # ## # ## ## # ## # ## ## # #
# # # ## # # # # ## # # # # # # # # # # # ### ## # # #
# # # # # ## # # # # # ## ## ### ###### ## ## ### #
## ## # # # # # # ### # # # ### # ## #
# # ###### # # # ## # # # ## # # # ## #### # # #
## # # # ### # # # # # #### # # # ## # # # #
# # # # ### ## ## # # # ## # # # # # ### # # # ### # # #### # ##
# ## # # # # # # # # # # # # # # ## ### ## # # ## #
### # # # # ## ### # # ## # # # ## # ## # ## #
### # # # # ### # # # # # ## # # # # # ## # # ## #
# # # # ## # # ### # ## ## # ### # # ### ## #
### ## # ## # ## # # # # # # # # # # # ####### ##
## ## # # # # ## # # # ## ### ### # # # ### # # # # ## # ###
### #### ### # # # # ## ## # #### # # # # # # ## # #
### # ## ## # ## ## ## # # # ## # # ## # ## # #
# # # # # # # #### ## # # #### ## # ## ## # # #
# ## # ## # # # # ### # ## # ## # # ## # # # ## # # #### # #
# # ## # # # # # # # # # ## ## # # # ### # #
# # # # # # ## # # # ### # ## # # # # ## # # # # # #
# # # ### # # # # # ## ## # # ## # # ## # # #
## ## ### # # ## # # # # # # # # # # # # # ## ## # # # #
# # # # # # ## # # # # # # # ##
# # # # # # ## # ## # ## # # # ## ## ## ## ### # # # # # #
# # # # # # # #### # ## # # # # ## ## # # # ## # #
## # # # # # # # ###### # # ### # # ## # # # # ### ##
# # ## # # # # #### # #### # # # ## ## ## #
# # # # # # # ## # # # # # ### ### # # # # # # #
# # # # ## # # # # # ## # ## # # ## # ## ### # #
#### # # # # ## # # # # # ## ### # # # # ### # ## #
# # # # ## ## # # # # # # # # # # # # # # # ## ## # # ##
# # # # # # ## # # # ## ## # # # # # # ## #
# # ## ## ### ## # # ## # # # ## # # # # # # # # #
## ## # # # # # # ## ### # # # ## # # ## # ### # ### ##
## ## # # # # # # # # # ## # # ## # ### # # # #
## # # ## ## ## # # ## # # ## # # # # ## # #
## # ## # ## ## # # # # # # # # # # ### # # # # # ## # #
# ## ## # # # # # #### ## # # # # # # # # #
# # # # # # # # ## # # # # # ### # # # # #### ## ### ####
# ## # # #### # # # # #### # # # # # ### # # ### # ## ##
## # ## # ## # # # # # # ### # # # # # ## # # #
# # # # ## # # # ### #### ## # # # # ## ## ## #
## # ## # ## # # # # ## # # # # # # # # # # # ## #
# # ## # # # ### # ## # ## # # ### # # # # ### #
# # ## # ## #### # # # # # # # ## ## # ## ###
# ## # # ## ## # # ## # # # ### # ## # # # # # # # #
# # ## # # ## # # # # # # # # # # # ## # ### ##
# ## # # # # # # # ## # # ## ## ## # # ## ## # # ## ### ### ####
### # # # # # # # ## # # # ## # ## # # # ## # # ## # # # #
# # # # # # # # ## ## ### # # # # # # ## # # # #
# # # # ## # ### # # # # ## # # # ### # ## ## # # # ##
# # # # # ## # ## # # # ### # ## ## # # # # # # # #
## ### ## # # # # ## # # # #### # #### # # ## # ## #
## ## ## # # # # ## # # ## ## ### # # # # # ### # ### ##
# # ### # # # # # # # # # ## # ### # # # ### ## ##
# # ## # ## # ## ## # # # ## ## # ## # # ##
# # ### # ## ## # # ### # # # # # # ## ## # ##
# # #### # # # # # # # ### # # # # # # ## # ### # # ### ###
# # ## # # ##### # ## # # ## ## # # # ## # # # ## ##
# ### # ## # # ###### ### # ## # ## # # ## # # # # ## ## # ## #
# # # # # # # # ## ## # # # ## # # ## ## # # # # #
## # ## ## # ### # # # # # # # # ## # # # # # ###### # ##
## # # # # ### # # ### ## # # ## # # # # ##### #
# # ### # # # # # # ## #### # # ### # # # ## # ##
# # ## ## # ## # #### # ## # # # # # ## ## # # # # ## ## #
## # # # # ## # # ## # # # ## # # ## # # # # # #
# # # ## # # # # ## # ## # # # # # ## # # ##
# ## ## # # # # # # ### # ## # # # # # # # # #
# # ## # # # # # # # ##### ## ## ### # # ###
# # # # # # ## ## ## # # # # # # ## # ##### # ##
# # ## # # # ## # # #### # ## # # # # # ## # # #
# # # # # ## ## # ## # # # # # #### # ##
## # # # # ## # ## ## ## # # ## # # # ## # ## # # #
## # # # # # # # ## ### # # # ## # # ## #
### # ## # # # ## ## # ### # # # # # ### # # # ##### #
## # # ## # ## # # # # # ## # # # ## ####### ### # #
#### # # # # # # # # # # ## # ## # # ### # ## # # #
# # # # # # # # # # ## # # ## # # # # ## # ### # #
# # # # #### ## ## # # # # ## # # # # # # ### ### # ##
#### # ## # # # ### ## # ## ## # ## # # ## # #
# # ## # # # # # # # # ## # # ## # # ### # ##
# # # # ## ## # # ## # # # # ## # ## ##
### ## # # # ## ## ## ## # # # ## ## # # # # # # # # # ## # # #
## # # # # # # # # # # ## #### # # ## ### ### ## # # #
# # ##### # # # ## ## # # ## ## # # ## # #### ##### # # ## ##
# # # # # # ## # # # # # # # # # # ## #
## ### # # ## ## # ## ## ## # # ## # # ### # # ## ### #
# # # ## # ## # # # ## # # # # ## # # # #
# # # # # #### # # # ## # # # ## # # # # # # # # # #
# # # ## # # ## # # ### # # ## # # ## # # ##
# # # ## # # ### # # # # # ## ## ##
# # # # ### # # # # # # # # # # # ## ## # ### # ## # # # #
# ###### # # ## ## ## # ### # # # ## # # # #####
# ## # # # # ## # # # # # # # # #### # # e

View File

@ -1,50 +0,0 @@
s # ## # # ### # ## # # #
## # # ## ## # # # #
# # ## # # # # ##
### # # # # # # ## ## # ## # #
# # # ## # # # # ## # #
# # # # ## # ## # # #
## # # # # # # # ## # #
# ## # # # ## # ## # # # # #
## # # # # ## # # ## # ##
# # # # # ## # # ## # # #
# # # # ## # # # # ## # ## # #
# ## # # # # # # # ## ##
## # ## ### # # # ## # ##
##### ### # # # # ## # # # #
# # ### ## # ## ## #### ###
## # # # # ### # # ## # #
# # ## # # # # # # ##
## # # # ### # ## # # ## # # ## ##
# #### # # # # # ### # ##
# ## # ## # # ## ### ## ### #
# # ### ## # # # ##
# # ## # # # # # # #
# ## # ### #### # ## # ### ## # #
# # ## # # # # # # #
# # ##### # # # # # # # ## # ##
## # # # # ## ## # ## ## #
# # # # # # # ## # # #
## # # # ## # # ## # #
# ### # # # # # # # # # ###
### # # # # # ### # # # # # ##
# # # # # ## # # # # # ##
# ## ## ## # # # # # # ## #
# #### # # # ## # ## #
## # # # # ## # # # # #
## # ## ## # # # ## # # ## #
# # # # # # # # # # ### # # #
# # ## # # # # # ###
# # #### ##
# # ## # # ## ### # # ##
##### # # # # # # # # # #
## # # # # # #
# # ## ## # # # # ## ### # #
# # ### ## ### ### # ## # #
## # ### # ## # # # #
# # # # # ## # # # # #
# # ## # # ## ### # # # #
# # # # # ## # ### #
## # # ## # # #
# # ## # ### # ### # ## # ## # ##
# # # # # # # ## # # e

View File

@ -1,20 +0,0 @@
s ## ###
# # # # # ##
# # # # #
# # ##
# # # # #
# # ### # #
# # # # #
# # ## ## ###
# ## #
# # ###
# # # # #
### # #
# # # #
## # # # #
## # # # # ##
# # #
# #
# # # #
# # #
# # # # ## #

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

View File

@ -1,10 +0,0 @@
s #
#
# #
# #
# #
#
# #
#
# # e

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

View File

@ -1,252 +0,0 @@
# Отчёт: Задание 2 — Поиск выхода из лабиринта
## Цель работы
Разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов
## Выбранные паттерны и их обоснование
### Builder
Для загрузки лабиринта из файла был использован паттерн Builder.
Создан интерфейс:
class MazeBuilder():
и его реализация:
class TextFileMazeBuilder(MazeBuilder):
Преимущества использования Builder:
пользоватеь не знает деталей создания лабиринта;
можно добавить новые форматы (JSON, XML, бинарный);
код загрузки изолирован от остальной программы.
### Strategy
Для алгоритмов поиска пути использован паттерн Strategy
Создан общий интерфейс:
class PathFindingStrategy():
Реализованы стратегии:
BFSStrategy;
DFSStrategy;
AStrategy;
Каждая стратегия реализует собственный алгоритм поиска пути по правилам.
Преимущества паттерна:
алгоритмы можно менять во время выполнения;
код MazeSolver не зависит от конкретного алгоритма;
новые алгоритмы можно добавлять без изменения существующего кода.
### Observer
Для уведомления интерфейса о событиях использован паттерн Observer
Создан интерфейс:
class Observer():
и реализация:
class ConsoleView(Observer):
MazeSolver хранит список наблюдателей и уведомляет их о событиях:
начало поиска;
окончание поиска;
перемещение игрока.
Преимущества:
логика интерфейса отделена от логики поиска;
можно легко добавить графический интерфейс;
### Command
Для пошагового перемещения игрока использован паттерн Command.
Создан интерфейс:
class Command():
и реализация:
class MoveCommand(Command):
Каждая команда умеет:
execute() — выполнить действие;
undo() — отменить действие
Преимущества:
поддержка undo;
возможность расширения системы команд
## Листинги ключевых классов
### Паттерн Strategy
class PathFindingStrategy:
def findPath(self, maze, start, exit):
pass
class BFSStrategy(PathFindingStrategy):
def findPath(self, maze, start, exit):
if exit is None:
return []
queue = deque([start])
visited = {start}
parent = {start: None}
while queue:
current = queue.popleft()
if current == exit:
return self._reconstruct_path(parent, start, exit)
for neighbor in maze.getNeighbors(current):
if neighbor not in visited:
visited.add(neighbor)
parent[neighbor] = current
queue.append(neighbor)
return []
class AStrategy(PathFindingStrategy):
def _heuristic(self, cell, exit):
if exit is None:
return 0
return abs(cell.x - exit.x) + abs(cell.y - exit.y)
def findPath(self, maze, start, exit):
if exit is None:
return []
open_set = []
heapq.heappush(open_set, (0, start))
came_from = {start: None}
g_score = {start: 0}
while open_set:
current = heapq.heappop(open_set)[1]
if current == exit:
return self._reconstruct_path(came_from, start, exit)
for neighbor in maze.getNeighbors(current):
tentative_g = g_score[current] + 1
if neighbor not in g_score or tentative_g < g_score[neighbor]:
came_from[neighbor] = current
g_score[neighbor] = tentative_g
f_score = tentative_g + self._heuristic(neighbor, exit)
heapq.heappush(open_set, (f_score, neighbor))
return []
### Паттерн Command
class Command:
def execute(self): pass
def undo(self): pass
class MoveCommand(Command):
def __init__(self, player, direction, maze):
self.player = player
self.dx, self.dy = direction
self.maze = maze
self.executed = False
def execute(self):
new_x = self.player.currentCell.x + self.dx
new_y = self.player.currentCell.y + self.dy
new_cell = self.maze.getCell(new_x, new_y)
if new_cell and new_cell.isPassable():
self.player.moveTo(new_cell)
self.executed = True
return True
return False
def undo(self):
if self.executed:
self.player.undoMove()
self.executed = False
return True
return False
## Результаты
| Лабиринт | Стратегия | Время (с) | Посещено | Длина пути | Путь найден |
|---|---|---|---|---|---|
| маленький (10x10) | BFS | 0.9148200158961117 | 19.0 | 19.0 | True |
| маленький (10x10) | DFS | 0.717819994315505 | 39.0 | 39.0 | True |
| маленький (10x10) | A | 1.577159995213151 | 19.0 | 19.0 | True |
| средний (50x50) | BFS | 14.496059995144606 | 99.0 | 99.0 | True |
| средний (50x50) | DFS | 8.470179990399629 | 393.0 | 393.0 |True |
| средний (50x50) | A | 9.11291999509558 | 99.0 | 99.0 | True |
| большой (100x100) | BFS | 0.013179995585232973 | 0.0 | 0.0 | False |
| большой (100x100) | A | 0.013079994823783636 | 0.0 | 0.0 | False |
| пустой (50x50) | BFS | 29.2012800113298 | 99.0 | 99.0 | True |
| пустой (50x50) | DFS | 13.176999986171722 | 1275.0 | 1275.0 | True |
| пустой (50x50) | A | 50.366899999789894 | 99.0 | 99.0 | True |
| без выхода (20x20) | BFS | 0.004239997360855341 | 0.0 | 0.0 | False |
| без выхода (20x20) | DFS | 0.006399990525096655 | 0.0 | 0.0 | False |
| без выхода (20x20) | A | 0.008680007886141539 | 0.0 | 0.0 | False |
### Графики
![Сравнение длины](maze_path_length.png)
![Сравнение времён](maze_time_comparison.png)
## Анализ эффективности алгоритмов
В ходе экспериментов были получены следующие результаты.
### BFS
Преимущества:
всегда находит кратчайший путь;
простая реализация.
Недостатки:
посещает большое количество клеток;
требует много памяти.
Выходит, что наиболее эффективен в небольших невзвешенных лабиринтах.
### DFS
Преимущества:
простая реализация;
самым быстрым находит произвольный путь.
Недостатки:
не гарантирует кратчайший путь;
может уходить в тупики.
Подходит для быстрого поиска любого решения.
### A
Преимущества:
высокая скорость;
посещает меньше клеток;
Недостатки:
требует выбора хорошей эвристики.
Показал хорошие результаты на больших лабиринтах.
## Анализ применимости паттернов
### Builder
Без Builder код загрузки лабиринта был бы жёстко связан с классом Maze, а добавление нового формата потребовало бы изменения существующего кода.
Strategy
Без Strategy пришлось бы:
хранить все алгоритмы внутри одного класса;
использовать большое количество условных операторов;
изменять код MazeSolver при добавлении новых алгоритмов
Strategy помог полностью отделить алгоритмы друг от друга.
### Observer
Без Observer логика интерфейса смешивалась бы с логикой поиска.
Это усложнило бы:
добавление GUI;
логирование;
визуализацию.
### Command
Без Command было бы сложно реализовать:
undo;
историю действий;
расширяемую систему управления.
## Выводы
### В проекте были успешно реализованы:
загрузка лабиринта из файла;
несколько алгоритмов поиска пути;
визуализация;
система наблюдателей;
система команд;
экспериментальное сравнение алгоритмов.
### Использование паттернов GoF позволило:
сделать архитектуру гибкой;
уменьшить связанность компонентов;
упростить расширение программы;
облегчить сопровождение кода.

View File

@ -1,16 +0,0 @@
лабиринт,стратегия,время_срремя_мин,время_макс,посещено_ср,длина_пути_ср,путь_найден
маленький (10x10),BFS,0.9148200158961117,0.8840999798849225,0.9673000313341618,19.0,19.0,True
маленький (10x10),DFS,0.717819994315505,0.5779999773949385,0.8650000090710819,39.0,39.0,True
маленький (10x10),A,1.577159995213151,1.531599962618202,1.7019000370055437,19.0,19.0,True
средний (50x50),BFS,14.496059995144606,12.946999981068075,18.392199999652803,99.0,99.0,True
средний (50x50),DFS,8.470179990399629,7.544599997345358,9.55930002965033,393.0,393.0,True
средний (50x50),A,9.11291999509558,8.53859999915585,9.788900031708181,99.0,99.0,True
большой (100x100),BFS,0.013179995585232973,0.009100011084228754,0.026200024876743555,0.0,0.0,False
большой (100x100),DFS,0.012619991321116686,0.008300004992634058,0.026499968953430653,0.0,0.0,False
большой (100x100),A,0.013079994823783636,0.008699949830770493,0.027500034775584936,0.0,0.0,False
пустой (50x50),BFS,29.2012800113298,19.71900003263727,47.252200020011514,99.0,99.0,True
пустой (50x50),DFS,13.176999986171722,12.441499973647296,13.887099979911,1275.0,1275.0,True
пустой (50x50),A,50.366899999789894,47.1535999677144,60.296199982985854,99.0,99.0,True
без выхода (20x20),BFS,0.004239997360855341,0.002700020559132099,0.00909995287656784,0.0,0.0,False
без выхода (20x20),DFS,0.006399990525096655,0.003200024366378784,0.012699980288743973,0.0,0.0,False
без выхода (20x20),A,0.008680007886141539,0.005399982910603285,0.01810002140700817,0.0,0.0,False
1 лабиринт стратегия время_ср время_мин время_макс посещено_ср длина_пути_ср путь_найден
2 маленький (10x10) BFS 0.9148200158961117 0.8840999798849225 0.9673000313341618 19.0 19.0 True
3 маленький (10x10) DFS 0.717819994315505 0.5779999773949385 0.8650000090710819 39.0 39.0 True
4 маленький (10x10) A 1.577159995213151 1.531599962618202 1.7019000370055437 19.0 19.0 True
5 средний (50x50) BFS 14.496059995144606 12.946999981068075 18.392199999652803 99.0 99.0 True
6 средний (50x50) DFS 8.470179990399629 7.544599997345358 9.55930002965033 393.0 393.0 True
7 средний (50x50) A 9.11291999509558 8.53859999915585 9.788900031708181 99.0 99.0 True
8 большой (100x100) BFS 0.013179995585232973 0.009100011084228754 0.026200024876743555 0.0 0.0 False
9 большой (100x100) DFS 0.012619991321116686 0.008300004992634058 0.026499968953430653 0.0 0.0 False
10 большой (100x100) A 0.013079994823783636 0.008699949830770493 0.027500034775584936 0.0 0.0 False
11 пустой (50x50) BFS 29.2012800113298 19.71900003263727 47.252200020011514 99.0 99.0 True
12 пустой (50x50) DFS 13.176999986171722 12.441499973647296 13.887099979911 1275.0 1275.0 True
13 пустой (50x50) A 50.366899999789894 47.1535999677144 60.296199982985854 99.0 99.0 True
14 без выхода (20x20) BFS 0.004239997360855341 0.002700020559132099 0.00909995287656784 0.0 0.0 False
15 без выхода (20x20) DFS 0.006399990525096655 0.003200024366378784 0.012699980288743973 0.0 0.0 False
16 без выхода (20x20) A 0.008680007886141539 0.005399982910603285 0.01810002140700817 0.0 0.0 False

Binary file not shown.

View File

@ -1,47 +0,0 @@
classDiagram
class Maze {
+width
+height
+cells
+start
+exit
+get_neighbors()
}
class Cell {
+x
+y
+is_wall
+is_start
+is_exit
}
class MazeBuilder {
<<interface>>
+build_from_file()
}
class TextFileMazeBuilder {
+build_from_file()
}
class PathFindingStrategy {
<<interface>>
+find_path()
}
class BFSStrategy
class DFSStrategy
class AStarStrategy
class MazeSolver {
+solve()
}
Maze --> Cell
TextFileMazeBuilder ..|> MazeBuilder
BFSStrategy ..|> PathFindingStrategy
DFSStrategy ..|> PathFindingStrategy
AStarStrategy ..|> PathFindingStrategy
MazeSolver --> PathFindingStrategy
MazeSolver --> Maze

View File

@ -1,135 +0,0 @@
# Отчёт по заданию №2
### Реализация поиска пути в лабиринте с использованием паттернов проектирования
---
## 1. Цель работы
Разработать архитектуру и реализацию системы поиска пути в лабиринте, применив паттерны:
- Builder — построение лабиринта из файла
- Strategy — выбор алгоритма поиска
- Observer — отображение состояния
- Command — управление игроком
Также провести экспериментальное сравнение алгоритмов BFS, DFS и A\*.
---
## 2. Архитектура проекта
Структура каталогов:
```
BrychkinKA/
├── src/
│ ├── builder/
│ ├── model/
│ ├── solver/
│ ├── strategy/
│ └── ui/
├── mazes/
├── experiments/
└── docs/
```
---
## 3. Используемые паттерны
### 3.1 Builder
Абстрагирует процесс построения лабиринта из текстового файла.
### 3.2 Strategy
Позволяет переключать алгоритмы поиска пути без изменения остального кода.
### 3.3 Observer
Используется для отображения состояния лабиринта в консоли.
### 3.4 Command
Реализует управление игроком и пошаговое перемещение.
---
## 4. Диаграмма классов
Диаграмма находится в файле: `class_diagram.mmd`
---
## 5. Эксперименты
Эксперименты проводились на пяти лабиринтах:
- small.txt — простой, проходимый
- medium.txt — средний по сложности
- empty.txt — полностью свободное поле
- no_exit.txt — отсутствует выход
- big.txt — большой лабиринт, путь отсутствует
Алгоритмы:
- BFS
- DFS
- A\*
---
## 6. Результаты
### 6.1 Таблица результатов
| Файл | Алгоритм | Посещено | Длина пути |
| ----------- | -------- | -------- | ---------- |
| big.txt | BFS | 27 | 0 |
| big.txt | DFS | 27 | 0 |
| big.txt | A\* | 27 | 0 |
| empty.txt | BFS | 10 | 10 |
| empty.txt | DFS | 10 | 10 |
| empty.txt | A\* | 10 | 10 |
| medium.txt | BFS | 21 | 17 |
| medium.txt | DFS | 19 | 17 |
| medium.txt | A\* | 21 | 17 |
| no_exit.txt | BFS | 0 | 0 |
| no_exit.txt | DFS | 0 | 0 |
| no_exit.txt | A\* | 0 | 0 |
| small.txt | BFS | 7 | 7 |
| small.txt | DFS | 7 | 7 |
| small.txt | A\* | 7 | 7 |
---
## 7. Графики
Графики находятся в файле:
`experiments/plot_graphs.py`
- время работы алгоритмов
- количество посещённых клеток
---
## 8. Выводы
1. A\* показывает лучшие результаты на средних и больших лабиринтах, но имеет небольшой накладной расход.
2. DFS посещает меньше клеток, но не гарантирует кратчайший путь.
3. BFS всегда находит кратчайший путь, но исследует больше пространства.
4. На лабиринтах без выхода все алгоритмы корректно возвращают `path_len = 0`.
5. Архитектура с паттернами позволяет легко расширять проект и добавлять новые алгоритмы.
---
## 9. Приложения
- Исходный код
- Лабиринты
- CSV с результатами
- Диаграммы

View File

@ -1,21 +0,0 @@
# Диаграммы проекта
## 1. Диаграмма классов
См. файл `class_diagram.mmd`.
## 2. Структура каталогов
```
vinichukan/
├── src/
├── mazes/
├── experiments/
└── docs/
```
## 3. Логика работы алгоритмов
- BFS — поиск в ширину
- DFS — поиск в глубину
- A\* — эвристический поиск с манхэттенской метрикой

View File

@ -1,65 +0,0 @@
import os
import sys
import csv
from time import perf_counter
# Добавляем корневую папку BrychkinKA в sys.path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from src.builder.text_file_maze_builder import TextFileMazeBuilder
from src.strategy.bfs_strategy import BFSStrategy
from src.strategy.dfs_strategy import DFSStrategy
from src.strategy.astar_strategy import AStarStrategy
from src.solver.maze_solver import MazeSolver
def run_experiments():
builder = TextFileMazeBuilder()
strategies = {
"BFS": BFSStrategy(),
"DFS": DFSStrategy(),
"A*": AStarStrategy()
}
# Папка с лабиринтами относительно корня
root_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
maze_dir = os.path.join(root_dir, "mazes")
files = [f for f in os.listdir(maze_dir) if f.endswith(".txt")]
results = []
for maze_file in files:
maze_path = os.path.join(maze_dir, maze_file)
maze = builder.build_from_file(maze_path)
for name, strategy in strategies.items():
solver = MazeSolver(maze, strategy)
t0 = perf_counter()
stats = solver.solve()
t1 = perf_counter()
results.append([
maze_file,
name,
stats.time_ms,
stats.visited,
stats.path_len
])
print(f"{maze_file} | {name} | {stats}")
# Сохраняем results.csv в папку experiments
output_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "results.csv")
with open(output_path, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["maze", "algorithm", "time_ms", "visited", "path_len"])
writer.writerows(results)
print(f"\nРезультаты сохранены в {output_path}")
if __name__ == "__main__":
run_experiments()

View File

@ -1,76 +0,0 @@
import csv
import matplotlib.pyplot as plt
import os
def plot_results():
# Определяем правильный путь к results.csv
script_dir = os.path.dirname(os.path.abspath(__file__))
csv_path = os.path.join(script_dir, "results.csv")
results = []
with open(csv_path, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
row['time_ms'] = float(row['time_ms'])
row['visited'] = int(row['visited'])
row['path_len'] = int(row['path_len'])
results.append(row)
mazes = sorted(set(r['maze'] for r in results))
algorithms = sorted(set(r['algorithm'] for r in results))
x_labels = []
for m in mazes:
for a in algorithms:
x_labels.append(f"{m.replace('.txt','')}\n{a}")
# График 1: Время выполнения
plt.figure(figsize=(12, 6))
times = []
for m in mazes:
for a in algorithms:
val = [r['time_ms'] for r in results if r['maze'] == m and r['algorithm'] == a]
times.append(val[0] if val else 0)
plt.bar(x_labels, times)
plt.ylabel("Время (мс)")
plt.title("Сравнение времени выполнения алгоритмов")
plt.xticks(rotation=45, ha='right')
plt.tight_layout()
plt.savefig(os.path.join(script_dir, "plot_time.png"), dpi=150)
plt.close()
print("Сохранён: experiments/plot_time.png")
# График 2: Посещённые клетки
plt.figure(figsize=(12, 6))
visited_list = []
for m in mazes:
for a in algorithms:
val = [r['visited'] for r in results if r['maze'] == m and r['algorithm'] == a]
visited_list.append(val[0] if val else 0)
plt.bar(x_labels, visited_list)
plt.ylabel("Посещено клеток")
plt.title("Сравнение количества посещённых клеток")
plt.xticks(rotation=45, ha='right')
plt.tight_layout()
plt.savefig(os.path.join(script_dir, "plot_visited.png"), dpi=150)
plt.close()
print("Сохранён: experiments/plot_visited.png")
# График 3: Длина пути
plt.figure(figsize=(12, 6))
path_list = []
for m in mazes:
for a in algorithms:
val = [r['path_len'] for r in results if r['maze'] == m and r['algorithm'] == a]
path_list.append(val[0] if val else 0)
plt.bar(x_labels, path_list)
plt.ylabel("Длина пути")
plt.title("Сравнение длины найденного пути")
plt.xticks(rotation=45, ha='right')
plt.tight_layout()
plt.savefig(os.path.join(script_dir, "plot_path.png"), dpi=150)
plt.close()
print("Сохранён: experiments/plot_path.png")
if __name__ == "__main__":
plot_results()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

View File

@ -1,16 +0,0 @@
maze,algorithm,time_ms,visited,path_len
big.txt,BFS,0.14230050146579742,27,0
big.txt,DFS,0.1100003719329834,27,0
big.txt,A*,0.23249909281730652,27,0
empty.txt,BFS,0.07219985127449036,10,10
empty.txt,DFS,0.046100467443466187,10,10
empty.txt,A*,0.08819997310638428,10,10
medium.txt,BFS,0.09160116314888,21,17
medium.txt,DFS,0.07379986345767975,19,17
medium.txt,A*,0.15410035848617554,21,17
no_exit.txt,BFS,0.0007003545761108398,0,0
no_exit.txt,DFS,0.0027008354663848877,0,0
no_exit.txt,A*,0.0001993030309677124,0,0
small.txt,BFS,0.06789900362491608,7,7
small.txt,DFS,0.03989972174167633,7,7
small.txt,A*,0.09530037641525269,7,7
1 maze algorithm time_ms visited path_len
2 big.txt BFS 0.14230050146579742 27 0
3 big.txt DFS 0.1100003719329834 27 0
4 big.txt A* 0.23249909281730652 27 0
5 empty.txt BFS 0.07219985127449036 10 10
6 empty.txt DFS 0.046100467443466187 10 10
7 empty.txt A* 0.08819997310638428 10 10
8 medium.txt BFS 0.09160116314888 21 17
9 medium.txt DFS 0.07379986345767975 19 17
10 medium.txt A* 0.15410035848617554 21 17
11 no_exit.txt BFS 0.0007003545761108398 0 0
12 no_exit.txt DFS 0.0027008354663848877 0 0
13 no_exit.txt A* 0.0001993030309677124 0 0
14 small.txt BFS 0.06789900362491608 7 7
15 small.txt DFS 0.03989972174167633 7 7
16 small.txt A* 0.09530037641525269 7 7

View File

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

View File

@ -1 +0,0 @@
S E

View File

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

View File

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

View File

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

View File

@ -1,7 +0,0 @@
from abc import ABC, abstractmethod
from src.model.maze import Maze
class MazeBuilder(ABC):
@abstractmethod
def build_from_file(self, filename: str) -> Maze:
pass

View File

@ -1,36 +0,0 @@
from src.model.cell import Cell
from src.model.maze import Maze
class TextFileMazeBuilder:
def build_from_file(self, filename):
with open(filename, "r", encoding="utf-8") as f:
lines = [line.rstrip("\n") for line in f]
height = len(lines)
width = max(len(line) for line in lines)
cells = []
start = None
exit_ = None
for y, line in enumerate(lines):
row = []
for x, ch in enumerate(line.ljust(width)):
is_wall = (ch == "#")
is_start = (ch == "S")
is_exit = (ch == "E")
cell = Cell(x, y, is_wall, is_start, is_exit)
row.append(cell)
if is_start:
start = cell
if is_exit:
exit_ = cell
cells.append(row)
if start is None:
raise ValueError("Файл должен содержать S (старт)")
return Maze(width, height, cells, start, exit_)

View File

@ -1,101 +0,0 @@
from src.builder.text_file_maze_builder import TextFileMazeBuilder
from src.strategy.bfs_strategy import BFSStrategy
from src.strategy.dfs_strategy import DFSStrategy
from src.strategy.astar_strategy import AStarStrategy
from src.solver.maze_solver import MazeSolver
from src.ui.console_view import ConsoleView
from src.ui.player import Player
from src.ui.move_command import MoveCommand
def choose_maze():
mazes = {
"1": ("small.txt", "Small — маленький лабиринт"),
"2": ("medium.txt", "Medium — средний лабиринт"),
"3": ("big.txt", "Big — большой лабиринт(тупиковый)"),
"4": ("empty.txt", "Empty — пустой лабиринт"),
"5": ("no_exit.txt","NoExit — без выхода")
}
print("\n" + "=" * 40)
print(" ВЫБОР ЛАБИРИНТА")
print("=" * 40)
for key, (_, desc) in mazes.items():
print(f" {key}. {desc}")
print("=" * 40)
choice = input("Введите номер: ").strip()
if choice not in mazes:
print("Неверный выбор, загружаю small.txt")
return "small.txt"
filename = mazes[choice][0]
print(f"Загружен: {filename}")
return filename
def main():
builder = TextFileMazeBuilder()
filename = choose_maze()
maze = builder.build_from_file(f"mazes/{filename}")
view = ConsoleView()
view.update(f"Maze '{filename}' loaded")
strategies = {
"bfs": BFSStrategy(),
"dfs": DFSStrategy(),
"astar": AStarStrategy()
}
print("\nВыберите алгоритм:")
print(" bfs — поиск в ширину")
print(" dfs — поиск в глубину")
print(" astar — A*")
algo = input("Введите название: ").strip().lower()
strategy = strategies.get(algo, BFSStrategy())
solver = MazeSolver(maze, strategy)
stats = solver.solve()
print(stats)
path, visited = strategy.find_path(maze, maze.start, maze.exit)
view.render(maze, None, path)
player = Player(maze.start)
while True:
cmd = input("Ход (w/a/s/d) или q для выхода: ").strip().lower()
if cmd == "q":
break
dxdy = {
"w": (0, -1),
"s": (0, 1),
"a": (-1, 0),
"d": (1, 0)
}
if cmd not in dxdy:
continue
dx, dy = dxdy[cmd]
new_cell = maze.get_cell(player.current_cell.x + dx,
player.current_cell.y + dy)
if not new_cell or not new_cell.is_passable():
print("Там стена, туда нельзя.")
continue
move = MoveCommand(player, new_cell)
move.execute()
view.render(maze, player.current_cell, path)
if __name__ == "__main__":
main()

View File

@ -1,19 +0,0 @@
class Cell:
def __init__(self, x, y, is_wall=False, is_start=False, is_exit=False):
self.x = x
self.y = y
self.is_wall = is_wall
self.is_start = is_start
self.is_exit = is_exit
def __repr__(self):
return f"Cell({self.x},{self.y})"
def __hash__(self):
return hash((self.x, self.y))
def __eq__(self, other):
return isinstance(other, Cell) and self.x == other.x and self.y == other.y
def is_passable(self):
return not self.is_wall

View File

@ -1,23 +0,0 @@
class Maze:
def __init__(self, width, height, cells, start, exit_):
self.width = width
self.height = height
self.cells = cells
self.start = start
self.exit = exit_
def get_cell(self, x, y):
return self.cells[y][x]
def get_neighbors(self, cell):
dirs = [(1,0), (-1,0), (0,1), (0,-1)]
result = []
for dx, dy in dirs:
nx, ny = cell.x + dx, cell.y + dy
if 0 <= nx < self.width and 0 <= ny < self.height:
n = self.get_cell(nx, ny)
if not n.is_wall:
result.append(n)
return result

View File

@ -1,32 +0,0 @@
from src.solver.search_stats import SearchStats
class MazeSolver:
def __init__(self, maze, strategy):
self.maze = maze
self.strategy = strategy
def solve(self):
import time
t0 = time.perf_counter()
if self.maze.exit is None:
t1 = time.perf_counter()
return SearchStats(
time_ms=(t1 - t0) * 1000,
visited=0,
path_len=0
)
path, visited = self.strategy.find_path(
self.maze,
self.maze.start,
self.maze.exit
)
t1 = time.perf_counter()
return SearchStats(
time_ms=(t1 - t0) * 1000,
visited=len(visited),
path_len=len(path) if path else 0
)

View File

@ -1,8 +0,0 @@
class SearchStats:
def __init__(self, time_ms, visited, path_len):
self.time_ms = time_ms
self.visited = visited
self.path_len = path_len
def __repr__(self):
return f"SearchStats(time={self.time_ms:.2f}ms, visited={self.visited}, path={self.path_len})"

View File

@ -1,43 +0,0 @@
import heapq
def manhattan(a, b):
return abs(a.x - b.x) + abs(a.y - b.y)
class AStarStrategy:
def find_path(self, maze, start, exit_):
g = {start: 0}
parent = {start: None}
counter = 0
open_heap = [(0, counter, start)]
in_open = {start}
visited = set()
while open_heap:
_, _, cur = heapq.heappop(open_heap)
in_open.discard(cur)
visited.add(cur)
if cur == exit_:
return self._reconstruct(parent, start, exit_), visited
for n in maze.get_neighbors(cur):
tentative = g[cur] + 1
if tentative < g.get(n, float('inf')):
g[n] = tentative
parent[n] = cur
f = tentative + manhattan(n, exit_)
if n not in in_open:
counter += 1
heapq.heappush(open_heap, (f, counter, n))
in_open.add(n)
return None, visited
def _reconstruct(self, parent, start, exit_):
path = []
cur = exit_
while cur:
path.append(cur)
cur = parent[cur]
return list(reversed(path))

View File

@ -1,29 +0,0 @@
from collections import deque
class BFSStrategy:
def find_path(self, maze, start, exit_):
queue = deque([start])
parent = {start: None}
visited = {start}
while queue:
cur = queue.popleft()
if cur == exit_:
return self._reconstruct(parent, start, exit_), visited
for n in maze.get_neighbors(cur):
if n not in visited:
visited.add(n)
parent[n] = cur
queue.append(n)
return None, visited
def _reconstruct(self, parent, start, exit_):
path = []
cur = exit_
while cur:
path.append(cur)
cur = parent[cur]
return list(reversed(path))

View File

@ -1,27 +0,0 @@
class DFSStrategy:
def find_path(self, maze, start, exit_):
stack = [start]
parent = {start: None}
visited = {start}
while stack:
cur = stack.pop()
if cur == exit_:
return self._reconstruct(parent, start, exit_), visited
for n in maze.get_neighbors(cur):
if n not in visited:
visited.add(n)
parent[n] = cur
stack.append(n)
return None, visited
def _reconstruct(self, parent, start, exit_):
path = []
cur = exit_
while cur:
path.append(cur)
cur = parent[cur]
return list(reversed(path))

View File

@ -1,9 +0,0 @@
from abc import ABC, abstractmethod
from typing import List
from src.model.cell import Cell
from src.model.maze import Maze
class PathFindingStrategy(ABC):
@abstractmethod
def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> List[Cell]:
pass

View File

@ -1,10 +0,0 @@
from abc import ABC, abstractmethod
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass

View File

@ -1,33 +0,0 @@
import os
from typing import List
from src.model.cell import Cell
from src.model.maze import Maze
from .observer import Observer
class ConsoleView(Observer):
def update(self, event: str):
print(f"[EVENT] {event}")
def render(self, maze: Maze, player_pos: Cell = None, path: List[Cell] = None):
os.system('cls' if os.name == 'nt' else 'clear')
path_set = set(path) if path else set()
for y in range(maze.height):
row = ""
for x in range(maze.width):
cell = maze.get_cell(x, y)
if cell.is_wall:
row += "#"
elif cell.is_start:
row += "S"
elif cell.is_exit:
row += "E"
elif player_pos and cell.x == player_pos.x and cell.y == player_pos.y:
row += "@"
elif cell in path_set:
row += "*"
else:
row += " "
print(row)

View File

@ -1,17 +0,0 @@
from src.model.cell import Cell
from .command import Command
from .player import Player
class MoveCommand(Command):
def __init__(self, player: Player, new_cell: Cell):
self.player = player
self.new_cell = new_cell
self.prev_cell = None
def execute(self):
self.prev_cell = self.player.current_cell
self.player.move_to(self.new_cell)
def undo(self):
if self.prev_cell:
self.player.move_to(self.prev_cell)

Some files were not shown because too many files have changed in this diff Show More