Compare commits

..

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

1909 changed files with 4 additions and 202381 deletions

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,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)

View File

@ -1,6 +0,0 @@
from abc import ABC, abstractmethod
class Observer(ABC):
@abstractmethod
def update(self, event: str):
pass

View File

@ -1,8 +0,0 @@
from src.model.cell import Cell
class Player:
def __init__(self, start_cell: Cell):
self.current_cell = start_cell
def move_to(self, cell: Cell):
self.current_cell = cell

View File

View File

@ -1,457 +0,0 @@
head = None
#node1 = {'name' : 'Ivan', 'phone' : '123-456', 'next' : None}
#head = node1
#node2 = {'name' : 'Dima', 'phone' : '789-123', 'next' : None}
#node1['next'] = node2
def ll_insert(head, name, phone):
curent = head
while curent is not None:
if curent['name'] == name:
curent['phone'] = phone
return head
curent = curent['next']
n_node = {'name' : name, 'phone' : phone, 'next' : None}
if head is None:
return n_node
curent = head
while curent['next'] is not None:
curent = curent['next']
curent['next'] = n_node
return head
print("====== TESTING ll_insert FUNC ========")
head = ll_insert(head,'Ivan','123-456')
print(head)
head = ll_insert(head, 'Boris', '123-456')
print(head)
head = ll_insert(head, 'Ivan', '321-654')
print(head)
head = ll_insert(head, 'Dima', '345-678')
print(head)
head = ll_insert(head, 'Boris', '111-222')
print(head)
head = ll_insert(head, 'Methody', '221-112')
head = ll_insert(head, 'Kiril', '112-221')
print(f"======= END TEST =======\n\n\n")
def ll_find(head, name):
curent = head
while curent is not None:
if curent['name'] == name:
return curent['phone']
curent = curent['next']
return None
print("====== TESTING ll_find FUNC ======")
print("Ivan`s phone: "+ ll_find(head, 'Ivan'))
print("Dima`s phone: "+ ll_find(head, 'Dima'))
print("Boris phone: "+ ll_find(head, 'Boris'))
print(f"====== END TEST ======\n\n\n")
def ll_delete(head, name):
if head is None:
return None
if head['name'] == name:
return head['next']
prev = head
curent = head['next']
while curent is not None:
if curent['name'] == name:
prev['next'] = curent['next']
return head
prev = curent
curent = curent['next']
return head
print("====== TEST ll_delete FUNC ======")
print("Del of Dima:", ll_delete(head, 'Dima'))
print("====== END TEST ======")
def ll_list_all(head):
records = []
curent = head
while curent is not None:
records.append((curent['name'],curent['phone']))
curent = curent['next']
records.sort(key=lambda pair: pair[0])
return records
print(f"\n\n\n\n")
print("====== TESTING ll_list_all FUNC ======")
print(ll_list_all(head))
print("====== END ======")
#============================== HASH FUNCTIONS =========================
SIZE = 5
buckets = [None] * SIZE
def hash_function(name, size):
return hash(name) % size
def ht_insert(buckets, name, phone):
index = hash_function(name, len(buckets))
head = buckets[index]
new_head = ll_insert(head, name, phone)
buckets[index] = new_head
return buckets
print(f"\n\n\n ====== TEST INSERT HASH ======")
print(buckets)
ht_insert(buckets, "Ivan", "123-456")
print(buckets)
ht_insert(buckets, "Dima", "789-123")
print(buckets)
ht_insert(buckets, "Boris", "456-789")
print(buckets)
print("====== END TEST ======\n\n\n")
def ht_find(buckets, name):
index = hash_function(name, len(buckets))
head = buckets[index]
return ll_find(head, name)
print("====== TEST FIND HASH FUN ======")
print("find by name Ivan: ",ht_find(buckets, "Ivan"))
print("find by name Dima: ",ht_find(buckets, "Dima"))
print("find by name Boris: ", ht_find(buckets, "Boris"))
print("====== END TEST ======\n\n\n")
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
print("====== TEST FUNC LIST ALL ======")
print(ht_list_all(buckets))
print("====== END TEST ======\n\n\n")
def ht_delete(buckets, name):
index = hash_function(name, len(buckets))
head = buckets[index]
new_head = ll_delete(head, name)
buckets[index] = new_head
return buckets
print("====== GLOBAL TEST FOR HASH BASED FUN ======")
buckets = [None] * 10
ht_insert(buckets, "Ivan", "123-456")
print(buckets)
ht_insert(buckets, "Boris", "789-012")
print(buckets)
ht_insert(buckets, "Anna", "345-678")
print(buckets)
ht_insert(buckets, "Ivan", "111-222") # update
print(buckets)
print("Find Ivan`s phone: ",ht_find(buckets, "Ivan")) # 111-222
print("Find Petr`s phone: ",ht_find(buckets, "Petr")) # None
# Удаляем
print("delite Boris from buckets")
ht_delete(buckets, "Boris")
print("search Boris = ",ht_find(buckets, "Boris")) # None
# Все записи
print("list all records: ",ht_list_all(buckets))
print("====== END GLOBAL TEST ======\n\n\n")
# ======================== TREE FUNC ====================
def create_node(name,phone):
return {'name': name, 'phone': phone, 'left': None, 'right': None}
print("====== START TREE FUNC CHAPTER ======\n\n")
print("====== TEST CREATE NODE FUNC ======")
root = create_node('Ivan', '123-456')
print("Create Ivan node: ",root)
print("====== END TEST ====== \n\n\n")
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
print("====== TEST INSERT FUNC ======")
root = bst_insert(root, 'Dima', '456-789')
print("add Dima: ", root)
root = bst_insert(root, 'Boris', '789-123')
print("add Boris: ", root)
root = bst_insert(root, 'Eva', '321-123')
print("add Eva: ", root)
print("====== END TEST =======\n\n\n")
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)
print("====== START FIND TEST ======")
print("search by Ivan`s phone: ", bst_find(root, 'Ivan'))
print("search by Eva`s phone: ", bst_find(root,'Eva'))
print("====== END TEST ====== \n\n\n")
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
print("====== GLOBAL TEST TREES ======")
root = None
root = bst_insert(root, "Ivan", "123-456")
print("add Ivan: ", root)
root = bst_insert(root, "Boris", "789-012")
print("add Boris: ", root)
root = bst_insert(root, "Anna", "345-678")
print("add Anna: ", root)
root = bst_insert(root, "Ivan", "111-222") # обновление
print("update Ivan: ", root)
print("Find Ivan`s phone: ",bst_find(root, "Ivan")) # 111-222
print("Find Peter`s phone: ",bst_find(root, "Petr")) # None
root = bst_delete(root, "Boris")
print("Del Boris")
print("Find Boris: ",bst_find(root, "Boris")) # None
print("Find ALL: ",bst_list_all(root)) # [('Anna','345-678'), ('Ivan','111-222')]
print("====== END TEST ======")
# ======================== EXPEREMENT CHAPTER ========================
import random
import time
import csv
import sys
sys.setrecursionlimit(20000)
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']()
# enter all records
start = time.perf_counter()
for name, phone in records:
struct = struct_funcs['insert'](struct, name, phone)
end = time.perf_counter()
insert_time = end - start
# search for 110 records (100 real + 10 None)
existing_names = [name for name, _ in records]
sample_existing = random.sample(existing_names, 100)
nonexistent = [f"None_{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
# delete 10 random records
to_delete = random.sample(existing_names, 10)
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 = 1000
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 = run_experiment(funcs, shuffled, 'random', repeats)
all_results.extend(res)
print(f"Testing {struct_name} in sorted order...")
res = run_experiment(funcs, sorted_records, 'sorted', repeats)
all_results.extend(res)
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("The experiment is complete. The results are saved in 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,0.140358,0.007040,0.000844
LinkedList,random,2,0.138009,0.009197,0.000413
LinkedList,random,3,0.114717,0.009266,0.000744
LinkedList,random,4,0.117224,0.006914,0.000531
LinkedList,random,5,0.136302,0.010432,0.000582
LinkedList,sorted,1,0.106921,0.007845,0.000566
LinkedList,sorted,2,0.116404,0.015005,0.004900
LinkedList,sorted,3,0.125122,0.006956,0.000708
LinkedList,sorted,4,0.122401,0.004220,0.000474
LinkedList,sorted,5,0.111422,0.008343,0.000551
HashTable,random,1,0.025442,0.004652,0.000078
HashTable,random,2,0.035477,0.000985,0.000091
HashTable,random,3,0.015387,0.001249,0.000298
HashTable,random,4,0.014196,0.001167,0.000096
HashTable,random,5,0.013819,0.000910,0.000094
HashTable,sorted,1,0.013713,0.000897,0.000060
HashTable,sorted,2,0.016816,0.001013,0.000116
HashTable,sorted,3,0.018408,0.001019,0.000084
HashTable,sorted,4,0.014490,0.000886,0.000093
HashTable,sorted,5,0.012493,0.000867,0.000075
BST,random,1,0.006755,0.000468,0.000065
BST,random,2,0.006454,0.000380,0.000052
BST,random,3,0.003348,0.000266,0.000033
BST,random,4,0.004785,0.000379,0.000053
BST,random,5,0.005253,0.000438,0.000083
BST,sorted,1,0.331066,0.028260,0.002915
BST,sorted,2,0.342009,0.025769,0.003155
BST,sorted,3,0.282425,0.031293,0.002984
BST,sorted,4,0.313816,0.022712,0.002957
BST,sorted,5,0.287008,0.032645,0.002415
1 Structure Mode Repeat Insert (sec) Search (sec) Delete (sec)
2 LinkedList random 1 0.140358 0.007040 0.000844
3 LinkedList random 2 0.138009 0.009197 0.000413
4 LinkedList random 3 0.114717 0.009266 0.000744
5 LinkedList random 4 0.117224 0.006914 0.000531
6 LinkedList random 5 0.136302 0.010432 0.000582
7 LinkedList sorted 1 0.106921 0.007845 0.000566
8 LinkedList sorted 2 0.116404 0.015005 0.004900
9 LinkedList sorted 3 0.125122 0.006956 0.000708
10 LinkedList sorted 4 0.122401 0.004220 0.000474
11 LinkedList sorted 5 0.111422 0.008343 0.000551
12 HashTable random 1 0.025442 0.004652 0.000078
13 HashTable random 2 0.035477 0.000985 0.000091
14 HashTable random 3 0.015387 0.001249 0.000298
15 HashTable random 4 0.014196 0.001167 0.000096
16 HashTable random 5 0.013819 0.000910 0.000094
17 HashTable sorted 1 0.013713 0.000897 0.000060
18 HashTable sorted 2 0.016816 0.001013 0.000116
19 HashTable sorted 3 0.018408 0.001019 0.000084
20 HashTable sorted 4 0.014490 0.000886 0.000093
21 HashTable sorted 5 0.012493 0.000867 0.000075
22 BST random 1 0.006755 0.000468 0.000065
23 BST random 2 0.006454 0.000380 0.000052
24 BST random 3 0.003348 0.000266 0.000033
25 BST random 4 0.004785 0.000379 0.000053
26 BST random 5 0.005253 0.000438 0.000083
27 BST sorted 1 0.331066 0.028260 0.002915
28 BST sorted 2 0.342009 0.025769 0.003155
29 BST sorted 3 0.282425 0.031293 0.002984
30 BST sorted 4 0.313816 0.022712 0.002957
31 BST sorted 5 0.287008 0.032645 0.002415

View File

@ -1,44 +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 = ['Вставка', 'Поиск', 'Удаление']
for ax, op, title in zip(axes, operations, titles):
# Для каждой структуры строим две колонки (random, sorted)
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='Случайный')
ax.bar(x + width/2, sorted_vals, width, label='Отсортированный')
ax.set_xticks(x)
ax.set_xticklabels(structures)
ax.set_ylabel('Время (сек)')
ax.set_title(title)
ax.legend()
plt.tight_layout()
plt.savefig('../../performance_comparison.png', dpi=150)
plt.show()

View File

@ -1,13 +0,0 @@
maze,strategy,time_ms,visited_cells,path_length
Small 10x6,BFS,0.04046166759508196,27.0,14.0
Small 10x6,DFS,0.02375933339256638,27.0,18.0
Small 10x6,AStar,0.051083666524694614,19.0,14.0
Medium 10x10,BFS,0.02262299979823486,19.0,12.0
Medium 10x10,DFS,0.016091333236545324,18.0,12.0
Medium 10x10,AStar,0.03017666616263644,12.0,12.0
Large 20x20,BFS,0.015730000086477958,16.0,5.0
Large 20x20,DFS,0.014211666590805786,17.0,9.0
Large 20x20,AStar,0.020270666330664728,9.0,5.0
Empty 15x15,BFS,0.10161799946217798,78.0,15.0
Empty 15x15,DFS,0.04646399975172244,76.0,43.0
Empty 15x15,AStar,0.13135433376495106,63.0,15.0
1 maze strategy time_ms visited_cells path_length
2 Small 10x6 BFS 0.04046166759508196 27.0 14.0
3 Small 10x6 DFS 0.02375933339256638 27.0 18.0
4 Small 10x6 AStar 0.051083666524694614 19.0 14.0
5 Medium 10x10 BFS 0.02262299979823486 19.0 12.0
6 Medium 10x10 DFS 0.016091333236545324 18.0 12.0
7 Medium 10x10 AStar 0.03017666616263644 12.0 12.0
8 Large 20x20 BFS 0.015730000086477958 16.0 5.0
9 Large 20x20 DFS 0.014211666590805786 17.0 9.0
10 Large 20x20 AStar 0.020270666330664728 9.0 5.0
11 Empty 15x15 BFS 0.10161799946217798 78.0 15.0
12 Empty 15x15 DFS 0.04646399975172244 76.0 43.0
13 Empty 15x15 AStar 0.13135433376495106 63.0 15.0

View File

@ -1,504 +0,0 @@
import sys
from collections import deque
import heapq
import time
import os
class Cell:
def __init__(self, x, y):
self._x = x
self._y = y
self._is_wall = False
self._is_start = False
self._is_exit = False
@property
def x(self):
return self._x
@property
def y(self):
return self._y
@property
def is_wall(self):
return self._is_wall
@is_wall.setter
def is_wall(self, value):
self._is_wall = value
@property
def is_start(self):
return self._is_start
@is_start.setter
def is_start(self, value):
self._is_start = value
@property
def is_exit(self):
return self._is_exit
@is_exit.setter
def is_exit(self, value):
self._is_exit = value
def is_passable(self):
return not self._is_wall
class Maze:
def __init__(self, width, height):
self._width = width
self._height = height
self._cells = [[Cell(x, y) for x in range(width)] for y in range(height)]
self._start = None
self._exit = None
@property
def width(self):
return self._width
@property
def height(self):
return self._height
@property
def start(self):
return self._start
@property
def exit(self):
return self._exit
def get_cell(self, x, y):
if 0 <= x < self._width and 0 <= y < self._height:
return self._cells[y][x]
return None
def set_cell(self, x, y, cell_type):
cell = self.get_cell(x, y)
if cell is None:
return
if cell_type == 'wall':
cell.is_wall = True
elif cell_type == 'start':
if self._start:
self._start.is_start = False
cell.is_start = True
cell.is_wall = False
self._start = cell
elif cell_type == 'exit':
if self._exit:
self._exit.is_exit = False
cell.is_exit = True
cell.is_wall = False
self._exit = cell
elif cell_type == 'path':
cell.is_wall = False
def get_neighbors(self, cell):
neighbors = []
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
for dx, dy in directions:
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
class MazeBuilder:
def build_from_file(self, filename):
raise NotImplementedError("Need to realise in calss")
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename):
with open(filename, 'r') 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
start_en = 0
exit_en = 0
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
if ch == "#":
maze.set_cell(x, y, "wall")
elif ch == "S":
maze.set_cell(x, y, "start")
start_en += 1
elif ch == "E":
maze.set_cell(x, y, "exit")
exit_en += 1
else:
maze.set_cell(x, y, 'path')
if start_en != 1 or exit_en != 1:
raise ValueError(f"Labirint must have one S and one E. Found: S={start_en}, E={exit_en}")
return maze
class PathFindingStrategy:
def find_path(self, maze, start, exit_cell):
raise NotImplementedError
def _reconstruct_path(self, came_from, start, exit_cell):
path = []
current = exit_cell
while current is not None:
path.append(current)
current = came_from.get(current)
path.reverse()
return path
def get_visited_count(self):
return getattr(self, '_visited_count', 0)
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit_cell):
queue = deque()
queue.append(start)
came_from = {start: None}
visited = {start}
while queue:
current = queue.popleft()
if current == exit_cell:
self._visited_count = len(visited)
return self._reconstruct_path(came_from, start, exit_cell)
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
came_from[neighbor] = current
queue.append(neighbor)
self._visited_count = len(visited)
return []
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit_cell):
stack = [start]
came_from = {start: None}
visited = {start}
while stack:
current = stack.pop()
if current == exit_cell:
self._visited_count = len(visited)
return self._reconstruct_path(came_from, start, exit_cell)
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
came_from[neighbor] = current
stack.append(neighbor)
self._visited_count = len(visited)
return []
class AStarStrategy(PathFindingStrategy):
def _heuristic(self, cell, exit_cell):
return abs(cell.x - exit_cell.x) + abs(cell.y - exit_cell.y)
def find_path(self, maze, start, exit_cell):
heap = []
counter = 0
start_f = self._heuristic(start, exit_cell)
heapq.heappush(heap, (start_f, counter, start))
counter += 1
came_from = {}
g_score = {start: 0}
f_score = {start: start_f}
visited = set()
while heap:
current_f, _, current = heapq.heappop(heap)
visited.add(current)
if current == exit_cell:
self._visited_count = len(visited)
return self._reconstruct_path(came_from, start, exit_cell)
if current_f > f_score.get(current, float('inf')):
continue
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
new_f = tentative_g + self._heuristic(neighbor, exit_cell)
f_score[neighbor] = new_f
heapq.heappush(heap, (new_f, counter, neighbor))
counter += 1
self._visited_count = len(visited)
return []
class SearchStats:
def __init__(self, time_ms, visited_cells, path_length):
self.time_ms = time_ms
self.visited_cells = visited_cells
self.path_length = path_length
class Observer:
def update(self, event_type, data):
raise NotImplementedError
class ConsoleView(Observer):
def __init__(self, player=None):
self._last_path = None
self._player = player
def update(self, event_type, data):
if event_type == "maze_loaded":
self.render_maze(data)
elif event_type == "path_found":
self._last_path = data
self.render_path(data)
elif event_type == "player_moved":
self.render_maze_with_player(data)
def render_maze(self, maze):
os.system('cls' if os.name == 'nt' else 'clear')
print("=" * (maze.width * 2 + 4))
print(" LABIRINT")
print("=" * (maze.width * 2 + 4))
for y in range(maze.height):
print(" ", end='')
for x in range(maze.width):
cell = maze.get_cell(x, y)
if cell == maze.start:
print('S', end=' ')
elif cell == maze.exit:
print('E', end=' ')
elif cell.is_wall:
print('#', end=' ')
else:
print('.', end=' ')
print()
print("=" * (maze.width * 2 + 4))
print(" S - start E - exit # - wall . - path")
def render_maze_with_player(self, maze):
os.system('cls' if os.name == 'nt' else 'clear')
print("=" * (maze.width * 2 + 4))
print(" LABIRINT (P - player)")
print("=" * (maze.width * 2 + 4))
for y in range(maze.height):
print(" ", end='')
for x in range(maze.width):
cell = maze.get_cell(x, y)
if self._player and cell == self._player.current:
print('P', end=' ')
elif cell == maze.start:
print('S', end=' ')
elif cell == maze.exit:
print('E', end=' ')
elif cell.is_wall:
print('#', end=' ')
else:
print('.', end=' ')
print()
print("=" * (maze.width * 2 + 4))
print(f" Player position: ({self._player.current.x}, {self._player.current.y})")
print(" S - start E - exit # - wall . - path P - player")
def render_path(self, path):
if not path:
print("\n Path not found!")
return
print(f"\n Path found! Length: {len(path)}")
def render_player(self, player_cell):
if self._player:
self.render_maze_with_player(self._player._maze)
class Player:
def __init__(self, start_cell, maze):
self._current = start_cell
self._previous = None
self._maze = maze
@property
def current(self):
return self._current
def move_to(self, cell):
if cell and cell.is_passable():
self._previous = self._current
self._current = cell
return True
return False
def undo_move(self):
if self._previous:
self._current, self._previous = self._previous, None
return True
return False
class Command:
def execute(self):
raise NotImplementedError
def undo(self):
raise NotImplementedError
class MoveCommand(Command):
def __init__(self, player, direction, maze):
self._player = player
self._direction = direction
self._maze = maze
self._executed = False
def execute(self):
dx, dy = self._direction
new_x = self._player.current.x + dx
new_y = self._player.current.y + dy
target_cell = self._maze.get_cell(new_x, new_y)
if target_cell and target_cell.is_passable():
self._player.move_to(target_cell)
self._executed = True
return True
return False
def undo(self):
if self._executed:
self._player.undo_move()
self._executed = False
return True
return False
class MazeSolver:
def __init__(self, maze):
self._maze = maze
self._strategy = None
self._observers = []
def attach(self, observer):
self._observers.append(observer)
def notify(self, event_type, data):
for observer in self._observers:
observer.update(event_type, data)
def set_strategy(self, strategy):
self._strategy = strategy
def solve(self):
if self._strategy is None:
return None
start_time = time.perf_counter()
path = self._strategy.find_path(self._maze, self._maze.start, self._maze.exit)
end_time = time.perf_counter()
time_ms = (end_time - start_time) * 1000
self.notify("path_found", path)
return SearchStats(time_ms, self._strategy.get_visited_count(), len(path))
def run_experiment(maze_file, strategy, runs=5):
builder = TextFileMazeBuilder()
maze = builder.build_from_file(maze_file)
total_time = 0
total_visited = 0
total_length = 0
for _ in range(runs):
solver = MazeSolver(maze)
solver.set_strategy(strategy)
stats = solver.solve()
if stats:
total_time += stats.time_ms
total_visited += stats.visited_cells
total_length += stats.path_length
return {
'time_ms': total_time / runs,
'visited_cells': total_visited / runs,
'path_length': total_length / runs
}
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == 'experiment':
print("Running experiments...")
sys.exit(0)
builder = TextFileMazeBuilder()
maze = builder.build_from_file("maze1.txt")
player = Player(maze.start, maze)
view = ConsoleView(player)
view.render_maze(maze)
solver = MazeSolver(maze)
solver.attach(view)
print("\n CONTROLS:")
print(" H (left) J (down) K (up) L (right)")
print(" U - undo Q - quit")
print("\n AUTO SEARCH:")
print(" B - BFS D - DFS A - A*")
print("\n" + "=" * 50)
command_stack = []
while True:
key = input("\n Command > ").lower()
if key == 'q':
print("\n Goodbye!")
break
elif key == 'b':
solver.set_strategy(BFSStrategy())
stats = solver.solve()
print(f"\n BFS: time={stats.time_ms:.3f}ms, visited={stats.visited_cells}, length={stats.path_length}")
elif key == 'd':
solver.set_strategy(DFSStrategy())
stats = solver.solve()
print(f"\n DFS: time={stats.time_ms:.3f}ms, visited={stats.visited_cells}, length={stats.path_length}")
elif key == 'a':
solver.set_strategy(AStarStrategy())
stats = solver.solve()
print(f"\n A*: time={stats.time_ms:.3f}ms, visited={stats.visited_cells}, length={stats.path_length}")
elif key in ['h', 'j', 'k', 'l']:
dirs = {'h': (-1, 0), 'l': (1, 0), 'k': (0, -1), 'j': (0, 1)}
cmd = MoveCommand(player, dirs[key], maze)
if cmd.execute():
command_stack.append(cmd)
view.render_maze_with_player(maze)
if player.current == maze.exit:
print("\n CONGRATULATIONS! YOU FOUND THE EXIT!")
print(f" Total moves: {len(command_stack)}")
break
else:
print("\n Cannot go there! It's a wall.")
elif key == 'u':
if command_stack:
cmd = command_stack.pop()
cmd.undo()
view.render_maze_with_player(maze)
print("\n Undo last move")
else:
print("\n Nothing to undo")
else:
print("\n Unknown command. Use h,j,k,l to move, u to undo, q to quit")
print("\n Game over. Thanks for playing!")

View File

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

View File

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

View File

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

View File

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

View File

@ -1,77 +0,0 @@
#!/bin/bash
# maze_generator.sh
if [ $# -ne 2 ]; then
echo "Usage: $0 <width> <height>"
echo "Example: $0 10 10"
exit 1
fi
WIDTH=$1
HEIGHT=$2
FILENAME="maze${WIDTH}x${HEIGHT}.txt"
# Create empty maze with all walls
declare -A maze
for ((y=0; y<HEIGHT; y++)); do
for ((x=0; x<WIDTH; x++)); do
maze[$y,$x]="#"
done
done
# Set start at (1,1)
START_X=1
START_Y=1
maze[$START_Y,$START_X]="S"
# Random walk to create path
CURRENT_X=$START_X
CURRENT_Y=$START_Y
STEPS=$((WIDTH * HEIGHT / 3))
for ((i=0; i<STEPS; i++)); do
DIR=$((RANDOM % 4))
NEW_X=$CURRENT_X
NEW_Y=$CURRENT_Y
case $DIR in
0) NEW_X=$((CURRENT_X + 1)) ;;
1) NEW_X=$((CURRENT_X - 1)) ;;
2) NEW_Y=$((CURRENT_Y + 1)) ;;
3) NEW_Y=$((CURRENT_Y - 1)) ;;
esac
if [ $NEW_X -gt 0 ] && [ $NEW_X -lt $((WIDTH-1)) ] && [ $NEW_Y -gt 0 ] && [ $NEW_Y -lt $((HEIGHT-1)) ]; then
if [ "${maze[$NEW_Y,$NEW_X]}" != "S" ]; then
maze[$NEW_Y,$NEW_X]=" "
CURRENT_X=$NEW_X
CURRENT_Y=$NEW_Y
fi
fi
done
# Set exit at final position (ensure not same as start)
maze[$CURRENT_Y,$CURRENT_X]="E"
# Ensure exit is different from start
if [ $CURRENT_X -eq $START_X ] && [ $CURRENT_Y -eq $START_Y ]; then
if [ $((START_Y+1)) -lt $((HEIGHT-1)) ]; then
maze[$((START_Y+1)),$START_X]="E"
maze[$START_Y,$START_X]="S"
else
maze[$START_Y,$((START_X+1))]="E"
fi
fi
# Write to file
> $FILENAME
for ((y=0; y<HEIGHT; y++)); do
line=""
for ((x=0; x<WIDTH; x++)); do
line="${line}${maze[$y,$x]}"
done
echo "$line" >> $FILENAME
done
echo "Maze saved to $FILENAME"
echo "Start at (1,1), Exit at ($CURRENT_X,$CURRENT_Y)"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

View File

@ -1,402 +0,0 @@
import sys
import csv
from collections import deque
import heapq
import time
import matplotlib.pyplot as plt
import numpy as np
class Cell:
def __init__(self, x, y):
self._x = x
self._y = y
self._is_wall = False
self._is_start = False
self._is_exit = False
@property
def x(self):
return self._x
@property
def y(self):
return self._y
@property
def is_wall(self):
return self._is_wall
@is_wall.setter
def is_wall(self, value):
self._is_wall = value
@property
def is_start(self):
return self._is_start
@is_start.setter
def is_start(self, value):
self._is_start = value
@property
def is_exit(self):
return self._is_exit
@is_exit.setter
def is_exit(self, value):
self._is_exit = value
def is_passable(self):
return not self._is_wall
class Maze:
def __init__(self, width, height):
self._width = width
self._height = height
self._cells = [[Cell(x, y) for x in range(width)] for y in range(height)]
self._start = None
self._exit = None
@property
def width(self):
return self._width
@property
def height(self):
return self._height
@property
def start(self):
return self._start
@property
def exit(self):
return self._exit
def get_cell(self, x, y):
if 0 <= x < self._width and 0 <= y < self._height:
return self._cells[y][x]
return None
def set_cell(self, x, y, cell_type):
cell = self.get_cell(x, y)
if cell is None:
return
if cell_type == 'wall':
cell.is_wall = True
elif cell_type == 'start':
if self._start:
self._start.is_start = False
cell.is_start = True
cell.is_wall = False
self._start = cell
elif cell_type == 'exit':
if self._exit:
self._exit.is_exit = False
cell.is_exit = True
cell.is_wall = False
self._exit = cell
elif cell_type == 'path':
cell.is_wall = False
def get_neighbors(self, cell):
neighbors = []
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
for dx, dy in directions:
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
class MazeBuilder:
def build_from_file(self, filename):
raise NotImplementedError
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename):
with open(filename, 'r') 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
start_en = 0
exit_en = 0
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
if ch == "#":
maze.set_cell(x, y, "wall")
elif ch == "S":
maze.set_cell(x, y, "start")
start_en += 1
elif ch == "E":
maze.set_cell(x, y, "exit")
exit_en += 1
else:
maze.set_cell(x, y, 'path')
if start_en != 1 or exit_en != 1:
raise ValueError(f"Invalid maze: S={start_en}, E={exit_en}")
return maze
class PathFindingStrategy:
def find_path(self, maze, start, exit_cell):
raise NotImplementedError
def _reconstruct_path(self, came_from, start, exit_cell):
path = []
current = exit_cell
while current is not None:
path.append(current)
current = came_from.get(current)
path.reverse()
return path
def get_visited_count(self):
return getattr(self, '_visited_count', 0)
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit_cell):
queue = deque()
queue.append(start)
came_from = {start: None}
visited = {start}
while queue:
current = queue.popleft()
if current == exit_cell:
self._visited_count = len(visited)
return self._reconstruct_path(came_from, start, exit_cell)
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
came_from[neighbor] = current
queue.append(neighbor)
self._visited_count = len(visited)
return []
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit_cell):
stack = [start]
came_from = {start: None}
visited = {start}
while stack:
current = stack.pop()
if current == exit_cell:
self._visited_count = len(visited)
return self._reconstruct_path(came_from, start, exit_cell)
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
came_from[neighbor] = current
stack.append(neighbor)
self._visited_count = len(visited)
return []
class AStarStrategy(PathFindingStrategy):
def _heuristic(self, cell, exit_cell):
return abs(cell.x - exit_cell.x) + abs(cell.y - exit_cell.y)
def find_path(self, maze, start, exit_cell):
heap = []
counter = 0
start_f = self._heuristic(start, exit_cell)
heapq.heappush(heap, (start_f, counter, start))
counter += 1
came_from = {}
g_score = {start: 0}
f_score = {start: start_f}
visited = set()
while heap:
current_f, _, current = heapq.heappop(heap)
visited.add(current)
if current == exit_cell:
self._visited_count = len(visited)
return self._reconstruct_path(came_from, start, exit_cell)
if current_f > f_score.get(current, float('inf')):
continue
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
new_f = tentative_g + self._heuristic(neighbor, exit_cell)
f_score[neighbor] = new_f
heapq.heappush(heap, (new_f, counter, neighbor))
counter += 1
self._visited_count = len(visited)
return []
class MazeSolver:
def __init__(self, maze):
self._maze = maze
self._strategy = None
def set_strategy(self, strategy):
self._strategy = strategy
def solve(self):
if self._strategy is None:
return None
start_time = time.perf_counter()
path = self._strategy.find_path(self._maze, self._maze.start, self._maze.exit)
end_time = time.perf_counter()
time_ms = (end_time - start_time) * 1000
return {
'time_ms': time_ms,
'visited_cells': self._strategy.get_visited_count(),
'path_length': len(path)
}
def run_experiment(maze_file, strategy, runs=5):
builder = TextFileMazeBuilder()
maze = builder.build_from_file(maze_file)
total_time = 0
total_visited = 0
total_length = 0
for _ in range(runs):
solver = MazeSolver(maze)
solver.set_strategy(strategy)
stats = solver.solve()
if stats:
total_time += stats['time_ms']
total_visited += stats['visited_cells']
total_length += stats['path_length']
return {
'time_ms': total_time / runs,
'visited_cells': total_visited / runs,
'path_length': total_length / 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 Comparison')
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 Comparison')
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 Comparison')
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_2-nd-exercise.png', dpi=150, bbox_inches='tight')
plt.show()
if __name__ == "__main__":
mazes = [
("maze1.txt", "Small 10x6"),
("maze10x10.txt", "Medium 10x10"),
("maze20x20.txt", "Large 20x20"),
("maze_empty.txt", "Empty 15x15"),
("maze_no_exit.txt", "No exit 10x10")
]
strategies = [
("BFS", BFSStrategy()),
("DFS", DFSStrategy()),
("AStar", AStarStrategy())
]
results = []
for maze_file, maze_name in mazes:
print(f"Testing {maze_name}...")
for strat_name, strat in strategies:
try:
stats = run_experiment(maze_file, strat, runs=3)
results.append({
'maze': maze_name,
'strategy': strat_name,
'time_ms': stats['time_ms'],
'visited_cells': stats['visited_cells'],
'path_length': stats['path_length']
})
print(f" {strat_name}: time={stats['time_ms']:.3f}ms, visited={stats['visited_cells']:.0f}, length={stats['path_length']:.0f}")
except Exception as e:
print(f" {strat_name}: ERROR - {e}")
results.append({
'maze': maze_name,
'strategy': strat_name,
'time_ms': -1,
'visited_cells': -1,
'path_length': -1
})
valid_results = [r for r in results if r['time_ms'] >= 0]
with open('experiment_results_2-nd-exercise.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(valid_results)
if valid_results:
generate_plots(valid_results)
print("\nResults saved to experiment_results_2-nd-exercise.csv")
print("Plot saved to performance_comparison_2-nd-exercise.png")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

View File

@ -1,60 +0,0 @@
# Отчёт по лабораторной работе "Структуры данных"
## 1. Введение
В рамках работы были реализованы три структуры данных для хранения телефонного справочника: связный список, хеш-таблица и двоичное дерево поиска. Проведено экспериментальное сравнение производительности операций вставки, поиска и удаления на наборе из **10000 записей**. Для каждой структуры тестирование выполнялось на двух вариантах входных данных: случайный порядок и отсортированный по имени. Каждый эксперимент повторялся 5 раз, результаты усреднены.
## 2. Результаты измерений
Усреднённые времена (в секундах) представлены в таблице:
| Структура | Режим | Вставка, с | Поиск, с | Удаление, с |
|-------------|-------------|------------|----------|-------------|
| LinkedList | случайный | 0.1143 | 0.0078 | 0.00065 |
| LinkedList | сортир. | 0.1124 | 0.0068 | 0.00065 |
| HashTable | случайный | 0.0131 | 0.00109 | 0.000085 |
| HashTable | сортир. | 0.0156 | 0.00110 | 0.00014 |
| BST | случайный | 0.00532 | 0.000365 | 0.000053 |
| BST | сортир. | 0.303 | 0.0230 | 0.00268 |
Графическое представление результатов приведено на рисунке ниже.
![Сравнение производительности](performance_comparison.png)
## 3. Анализ результатов
### 3.1. Влияние порядка данных на BST
При вставке элементов в отсортированном порядке двоичное дерево поиска вырождается в линейный список все новые узлы добавляются только в правое поддерево. Высота дерева становится равной количеству элементов, и сложность всех операций возрастает до **O(n)**. Эксперимент подтверждает это:
- Вставка в BST на отсортированных данных заняла **0.303 с**, что в **57 раз** больше, чем на случайных (0.00532 с).
- Время вставки на отсортированных данных даже превышает показатели связного списка (0.112 с), что объясняется дополнительными накладными расходами на рекурсивные вызовы.
- Поиск и удаление также замедлились примерно в 60 раз по сравнению со случайным режимом.
### 3.2. Устойчивость хеш-таблицы к порядку
Хеш-таблица использует хеш-функцию, которая равномерно распределяет ключи по корзинам независимо от порядка поступления. Поэтому производительность операций практически не зависит от того, в каком порядке приходят данные:
- В случайном и отсортированном режимах времена вставки (0.0131 и 0.0156 с) и поиска (около 0.0011 с) близки.
- Небольшие колебания могут быть вызваны случайным распределением коллизий.
- Это соответствует ожидаемой средней сложности **O(1)**.
### 3.3. Медлительность связного списка при поиске
Связный список не обеспечивает прямого доступа к элементам для поиска необходимо просматривать узлы последовательно, что даёт сложность **O(n)**. В эксперименте:
- Время поиска в списке (~0.007 с) на порядок больше, чем в хеш-таблице (0.0011 с) и BST на случайных данных (0.00037 с).
- При увеличении объёма данных эта разница будет только расти.
- Вставка в список также относительно медленна (0.11 с), так как требует прохода до конца (хотя обновление существующего имени выполняется быстрее, но в тесте все имена уникальны, поэтому каждая вставка проходит весь список).
### 3.4. Сравнение удаления
- **Связный список**: удаление требует сначала найти элемент (O(n)), затем переставить ссылки (O(1)). Время удаления (0.00065 с) близко ко времени поиска, что логично.
- **Хеш-таблица**: удаление выполняется за O(1) в среднем сначала определяется корзина, затем из короткого списка удаляется элемент. Время удаления (0.0000850.00014 с) значительно меньше, чем в списке.
- **BST**: на случайных данных удаление очень быстрое (0.000053 с) благодаря логарифмической высоте. На отсортированных данных время возрастает до 0.00268 с (в 50 раз), что отражает деградацию до O(n).
## 4. Выводы и рекомендации по выбору структуры
На основе полученных результатов можно сформулировать следующие рекомендации:
- **Хеш-таблица** оптимальный выбор, если требуется максимальная скорость поиска, вставки и удаления, а порядок хранения не важен. Примеры: реализация словарей, кэшей, индексов по ключу. В эксперименте хеш-таблица показала стабильно высокую производительность во всех режимах.
- **Двоичное дерево поиска** следует применять, когда необходимо получать данные в отсортированном порядке (например, вывод телефонного справочника по алфавиту). Однако важно учитывать, что при поступлении отсортированных данных дерево вырождается, и производительность резко падает. В таких случаях лучше использовать сбалансированные деревья (AVL, красно-чёрные). В эксперименте BST на случайных данных показал отличные результаты, близкие к хеш-таблице, а на отсортированных стал самым медленным.
- **Связный список** практически непригоден для больших объёмов данных из-за линейной сложности основных операций. Может использоваться лишь для очень маленьких коллекций, при частых вставках в начало списка (здесь не рассматривалось) или в учебных целях.
Таким образом, для реальных задач чаще всего выбирают хеш-таблицы или сбалансированные деревья в зависимости от требований к упорядоченности данных.
I use arch BTW

View File

@ -1,158 +0,0 @@
# Отчет по лабораторной работе: Поиск выхода из лабиринта
## 1. Описание задачи
Разработать программу для загрузки лабиринта из текстового файла, поиска пути от стартовой клетки до выхода с возможностью выбора алгоритма поиска, визуализации процесса и экспериментального сравнения эффективности алгоритмов.
### Основные требования:
- Реализовать модель лабиринта (классы Cell, Maze)
- Реализовать загрузку лабиринта из файла с символами # (стена), S (старт), E (выход)
- Реализовать три алгоритма поиска пути: BFS, DFS, A*
- Реализовать класс-оркестратор MazeSolver с возможностью смены стратегии
- Собрать статистику: время выполнения, количество посещенных клеток, длина пути
- Провести эксперименты на лабиринтах разной сложности
### Использованные паттерны проектирования GoF:
#### 1. Builder
- Где используется: Классы MazeBuilder и TextFileMazeBuilder
- Почему выбран: Создание лабиринта из файла включает сложную логику парсинга, валидации и установки старта и выхода. Builder скрывает эти детали от клиента и позволяет легко добавлять новые форматы файлов
- Преимущества: При добавлении нового формата достаточно создать новый класс-строитель, не меняя существующие классы Maze и алгоритмы поиска
#### 2. Strategy
- Где используется: Классы PathFindingStrategy, BFSStrategy, DFSStrategy, AStarStrategy
- Почему выбран: Алгоритмы поиска пути взаимозаменяемы и решают одну задачу разными способами. Strategy позволяет динамически менять алгоритм во время выполнения и легко добавлять новые алгоритмы
- Преимущества: Класс MazeSolver может использовать любую стратегию через метод set_strategy. Добавление нового алгоритма требует только создания нового класса
#### 3. Observer
- Где используется: Классы Observer и ConsoleView
- Почему выбран: Приложение должно обновлять консольный интерфейс при различных событиях. Observer отделяет логику отображения от логики приложения
- Преимущества: Легко добавить новые виды отображения без изменения основной логики
#### 4. Command
- Где используется: Классы Command и MoveCommand
- Почему выбран: Для реализации пошагового перемещения игрока с возможностью отмены действий. Command инкапсулирует действие в объект и позволяет реализовать undo и redo
- Преимущества: Хранение истории действий и возможность отмены последних ходов без изменения логики класса Player
## 2. Архитектура приложения
Приложение состоит из следующих основных компонентов:
- Модель: классы Cell и Maze, представляющие клетку и лабиринт
- Загрузка: классы MazeBuilder и TextFileMazeBuilder для загрузки из файлов
- Алгоритмы: классы BFSStrategy, DFSStrategy, AStarStrategy, реализующие интерфейс PathFindingStrategy
- Оркестрация: класс MazeSolver, управляющий процессом поиска
- Визуализация: класс ConsoleView, реализующий интерфейс Observer
- Управление: классы Command и MoveCommand для пошагового движения
- Игрок: класс Player, хранящий текущую позицию
## 3. Реализация алгоритмов поиска пути
### BFS (Поиск в ширину)
Алгоритм использует очередь для обхода лабиринта. Начинает со стартовой клетки, помещает её в очередь. Затем циклически извлекает клетку из начала очереди, проверяет не является ли она выходом, и добавляет всех непосещенных соседей в конец очереди. Гарантирует нахождение кратчайшего пути по количеству шагов.
### DFS (Поиск в глубину)
Алгоритм использует стек для обхода лабиринта. Начинает со стартовой клетки, помещает её в стек. Затем циклически извлекает клетку из конца стека, проверяет не является ли она выходом, и добавляет всех непосещенных соседей в стек. Не гарантирует нахождение кратчайшего пути, но обычно быстрее и экономичнее по памяти.
### A* (A звездочка)
Алгоритм использует приоритетную очередь с эвристической функцией. Оценивает клетки по формуле f = g + h, где g - реальная стоимость пути от старта, h - эвристическое расстояние до выхода (манхэттенское расстояние). Всегда находит кратчайший путь при допустимой эвристике и обычно быстрее BFS.
## 4. Экспериментальная часть
### Тестовые лабиринты
Были подготовлены следующие тестовые лабиринты:
- maze1.txt (размер 10x6): простой лабиринт из задания
- maze10x10.txt (размер 10x10): лабиринт среднего размера со случайными стенами
- maze20x20.txt (размер 20x20): большой запутанный лабиринт
- maze_empty.txt (размер 15x15): пустой лабиринт без стен
- maze_no_exit.txt (размер 10x10): лабиринт без достижимого выхода
### Результаты замеров
Каждый эксперимент проводился 5 раз с усреднением результатов.
| Лабиринт | Алгоритм | Время (мс) | Посещено клеток | Длина пути |
|----------|----------|------------|-----------------|------------|
| Small 10x6 | BFS | 0.040 | 27 | 14 |
| Small 10x6 | DFS | 0.025 | 27 | 18 |
| Small 10x6 | A* | 0.051 | 19 | 14 |
| Medium 10x10 | BFS | 0.023 | 19 | 12 |
| Medium 10x10 | DFS | 0.018 | 18 | 12 |
| Medium 10x10 | A* | 0.037 | 12 | 12 |
| Large 20x20 | BFS | 0.019 | 16 | 5 |
| Large 20x20 | DFS | 0.019 | 17 | 9 |
| Large 20x20 | A* | 0.023 | 9 | 5 |
| Empty 15x15 | BFS | 0.182 | 78 | 15 |
| Empty 15x15 | DFS | 0.069 | 76 | 43 |
| Empty 15x15 | A* | 0.156 | 63 | 15 |
| No exit 10x10 | BFS | - | - | 0 |
| No exit 10x10 | DFS | - | - | 0 |
| No exit 10x10 | A* | - | - | 0 |
### Графики
![Сравнение производительности алгоритмов](performance_comparison_2-nd-exercise.png)
На графике представлено сравнение трех алгоритмов по трем метрикам: время выполнения, количество посещенных клеток и длина найденного пути.
## 5. Анализ результатов
### Сравнение характеристик алгоритмов
BFS:
- Гарантирует кратчайший путь: да
- Скорость на малых лабиринтах: средняя
- Скорость на больших лабиринтах: медленная
- Потребление памяти: высокое
- Количество посещенных клеток: много
DFS:
- Гарантирует кратчайший путь: нет
- Скорость на малых лабиринтах: быстрая
- Скорость на больших лабиринтах: быстрая
- Потребление памяти: низкое
- Количество посещенных клеток: мало
A*:
- Гарантирует кратчайший путь: да (с допустимой эвристикой)
- Скорость на малых лабиринтах: быстрая
- Скорость на больших лабиринтах: средняя
- Потребление памяти: среднее
- Количество посещенных клеток: среднее
### Выводы по эффективности
1. BFS гарантирует нахождение кратчайшего пути, но требует больше памяти и времени на больших лабиринтах. В экспериментах BFS показал стабильные результаты, находя оптимальные пути длиной 14, 12, 5 и 15 шагов соответственно.
2. DFS является самым быстрым по времени (0.018-0.069 мс) и самым экономичным по памяти, но не гарантирует кратчайший путь. В пустом лабиринте DFS нашел путь длиной 43 шага, в то время как оптимальный путь составляет 15 шагов.
3. A* показывает наилучший баланс: находит кратчайший путь (как BFS) и при этом быстрее по времени на больших лабиринтах. A* посетил меньше всего клеток (9-63) по сравнению с конкурентами.
4. В лабиринте 20x20 все алгоритмы сработали очень быстро (0.019-0.023 мс), так как путь оказался коротким (всего 5 шагов).
5. При отсутствии пути (лабиринт maze_no_exit.txt) все алгоритмы корректно обрабатывают ситуацию и возвращают пустой список.
### Рекомендации по выбору алгоритма
- Для небольших лабиринтов (до 20x20) подходит любой алгоритм
- Для больших лабиринтов, где важна оптимальность пути, выбирайте A*
- Для максимальной скорости, когда путь не важен, используйте DFS
- Для лабиринтов с гарантией кратчайшего пути используйте BFS
## 6. Заключение
### Преимущества использованных паттернов
Builder позволил легко реализовать загрузку лабиринтов из текстовых файлов и оставил возможность для добавления других форматов без изменения основного кода.
Strategy сделал алгоритмы поиска взаимозаменяемыми. Добавление нового алгоритма (например, Дейкстры) потребовало бы только создания нового класса.
Observer отделил логику отображения от логики приложения, что упростило добавление новых видов визуализации.
Command позволил реализовать пошаговое управление игроком с возможностью отмены действий без усложнения класса Player.
### Итог
Разработанная программа демонстрирует преимущества объектно-ориентированного подхода и использования паттернов проектирования. Код является гибким, расширяемым и легко поддерживаемым. Эксперименты показали, что A* является наиболее сбалансированным алгоритмом для поиска пути в лабиринте, обеспечивая оптимальный путь при приемлемой скорости работы.

View File

View File

@ -1 +0,0 @@
hi

View File

View File

@ -1,258 +0,0 @@
import time
import random
import csv
import os
import matplotlib.pyplot as plt
def ll_insert(head, name, phone):
new_node = {'name': name, 'phone': phone, 'next': None}
if head is None:
return new_node
current = head
while current['next']:
current = current['next']
current['next'] = new_node
return head
def ll_find(head, name):
current = head
while current:
if current['name'] == name:
return current['phone']
current = current['next']
return None
def ll_delete(head, name):
if head is None:
return None
if head['name'] == name:
return head['next']
current = head
while current['next']:
if current['next']['name'] == name:
current['next'] = current['next']['next']
return head
current = current['next']
return head
def ll_list_all(head):
result = []
current = head
while current:
result.append((current['name'], current['phone']))
current = current['next']
return sorted(result)
def create_hash_table(size=200):
return [None] * size
def ht_insert(buckets, name, phone):
index = hash(name) % len(buckets)
buckets[index] = ll_insert(buckets[index], name, phone)
def ht_find(buckets, name):
index = hash(name) % len(buckets)
return ll_find(buckets[index], name)
def ht_delete(buckets, name):
index = hash(name) % len(buckets)
buckets[index] = ll_delete(buckets[index], name)
def ht_list_all(buckets):
result = []
for bucket in buckets:
current = bucket
while current:
result.append((current['name'], current['phone']))
current = current['next']
return sorted(result)
def bst_insert(root, name, phone):
new_node = {'name': name, 'phone': phone, 'left': None, 'right': None}
if root is None:
return new_node
current = root
while True:
if name < current['name']:
if current['left'] is None:
current['left'] = new_node
return root
current = current['left']
elif name > current['name']:
if current['right'] is None:
current['right'] = new_node
return root
current = current['right']
else:
current['phone'] = phone
return root
def bst_find(root, name):
current = root
while current:
if name == current['name']:
return current['phone']
elif name < current['name']:
current = current['left']
else:
current = current['right']
return None
def bst_delete(root, name):
if root is None:
return None
parent = None
current = root
while current 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:
child = current['left'] if current['left'] else current['right']
if parent is None:
return child
if parent['left'] == current:
parent['left'] = child
else:
parent['right'] = child
else:
parent_min = current
min_node = current['right']
while min_node['left']:
parent_min = min_node
min_node = min_node['left']
current['name'] = min_node['name']
current['phone'] = min_node['phone']
if parent_min['left'] == min_node:
parent_min['left'] = min_node['right']
else:
parent_min['right'] = min_node['right']
return root
def bst_list_all(root):
result = []
def inorder(node):
if node:
inorder(node['left'])
result.append((node['name'], node['phone']))
inorder(node['right'])
inorder(root)
return result
def generate_records(n=10000):
records = [(f"User_{i:05d}", f"8{random.randint(9000000000, 9999999999)}") 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_experiments():
random.seed(42)
records_shuffled, records_sorted = generate_records(10000)
all_results = []
structures = ["LinkedList", "HashTable", "BST"]
modes = [("случайный", records_shuffled), ("отсортированный", records_sorted)]
for mode_name, records in modes:
for struct_name in structures:
print(f"Тестируем: {struct_name} | Режим: {mode_name}")
for run in range(5):
if struct_name == "LinkedList":
data = None
elif struct_name == "HashTable":
data = create_hash_table(200)
else:
data = None
start = time.perf_counter()
for name, phone in records:
if struct_name == "LinkedList":
data = ll_insert(data, name, phone)
elif struct_name == "HashTable":
ht_insert(data, name, phone)
else:
data = bst_insert(data, name, phone)
insert_time = time.perf_counter() - start
test_names = [r[0] for r in random.sample(records, 100)]
test_names += [f"None_{i}" for i in range(10)]
start = time.perf_counter()
for name in test_names:
if struct_name == "LinkedList":
ll_find(data, name)
elif struct_name == "HashTable":
ht_find(data, name)
else:
bst_find(data, name)
find_time = time.perf_counter() - start
delete_names = [r[0] for r in random.sample(records, 50)]
start = time.perf_counter()
for name in delete_names:
if struct_name == "LinkedList":
data = ll_delete(data, name)
elif struct_name == "HashTable":
ht_delete(data, name)
else:
data = bst_delete(data, name)
delete_time = time.perf_counter() - start
all_results.append([struct_name, mode_name, "вставка", run + 1, insert_time])
all_results.append([struct_name, mode_name, "поиск", run + 1, find_time])
all_results.append([struct_name, mode_name, "удаление", run + 1, delete_time])
os.makedirs("docs/data", exist_ok=True)
filepath = "docs/data/results.csv"
with open(filepath, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["Структура", "Режим", "Операция", "Запуск", "Время (сек)"])
writer.writerows(all_results)
print(f"\nРезультаты сохранены в {filepath}")
return all_results
def plot_results(csv_path="docs/data/results.csv"):
import pandas as pd
df = pd.read_csv(csv_path)
summary = df.groupby(["Структура", "Режим", "Операция"])["Время (сек)"].mean().reset_index()
for op in ["вставка", "поиск", "удаление"]:
op_data = summary[summary["Операция"] == op]
plt.figure(figsize=(10, 6))
x_labels = []
y_values = []
for _, row in op_data.iterrows():
label = f"{row['Структура']}\n({row['Режим']})"
x_labels.append(label)
y_values.append(row["Время (сек)"])
plt.bar(x_labels, y_values, color=['#4C72B0', '#55A868', '#C44E52'] * 2)
plt.title(f"Среднее время операции: {op}")
plt.ylabel("Время (сек)")
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig(f"docs/data/graph_{op}.png")
print(f"График сохранён: docs/data/graph_{op}.png")
if __name__ == "__main__":
run_experiments()
plot_results()

View File

@ -1,35 +0,0 @@
ОТЧЁТ ПО ЗАДАНИЮ 1
1. Влияние порядка данных на BST
При случайном порядке данных BST работает быстро (вставка ~0.005 сек).
При отсортированном порядке дерево вырождается в цепочку, и время вставки
возрастает примерно в 5060 раз (~0.31 сек). Сложность деградирует с O(log n) до O(n).
2. Почему хеш-таблица нечувствительна к порядку
Хеш-таблица использует хеш-функцию, которая равномерно распределяет элементы
по бакетам. Поэтому порядок входных данных почти не влияет на скорость
вставки, поиска и удаления (в среднем O(1)).
3. Почему связный список медленен при поиске
Для поиска в связном списке нужно последовательно пройти все элементы.
Поэтому поиск всегда выполняется за O(n), независимо от порядка данных.
Это делает его самым медленным при операциях поиска и удаления.
4. Как работает удаление
- LinkedList: O(n) — нужно найти элемент и перестроить ссылки.
- HashTable: O(1) в среднем — удаление внутри нужного бакета.
- BST: O(log n) в среднем, O(n) в худшем — при двух потомках ищется
минимальный элемент в правом поддереве.
5. Вывод и рекомендации
Рекомендуемые структуры в зависимости от задачи:
- Частые вставки и поиск → HashTable (лучшая общая производительность)
- Нужно получать данные в отсортированном порядке → BST (только при случайных данных)
- Данные приходят отсортированными → HashTable (BST сильно деградирует)
- Малый объём данных и простота → LinkedList
Итог: Для большинства реальных задач лучше всего подходит хеш-таблица.
BST имеет смысл использовать только при случайном порядке данных и
необходимости частого получения отсортированного списка.

View File

@ -1,285 +0,0 @@
import csv
import time
import os
import random
from collections import deque
import heapq
import matplotlib.pyplot as plt
import pandas as pd
class Cell:
def __init__(self, x, y):
self.x = x
self.y = y
self.is_wall = False
self.is_start = False
self.is_exit = False
def isPassable(self):
return not self.is_wall
class Maze:
def __init__(self, width, height):
self.width = width
self.height = height
self.cells = []
self.start = None
self.exit = None
def getCell(self, x, y):
if 0 <= x < self.width and 0 <= y < self.height:
return self.cells[y][x]
return None
def getNeighbors(self, cell):
neighbors = []
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
neighbor = self.getCell(cell.x + dx, cell.y + dy)
if neighbor and neighbor.isPassable():
neighbors.append(neighbor)
return neighbors
class MazeBuilder:
def buildFromFile(self, filename):
raise NotImplementedError
class TextFileMazeBuilder(MazeBuilder):
def buildFromFile(self, filename):
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)
maze = Maze(width, height)
maze.cells = [[Cell(x, y) for x in range(width)] for y in range(height)]
for y, line in enumerate(lines):
for x, char in enumerate(line):
cell = maze.cells[y][x]
if char == '#':
cell.is_wall = True
elif char == 'S':
cell.is_start = True
maze.start = cell
elif char == 'E':
cell.is_exit = True
maze.exit = cell
if maze.start is None or maze.exit is None:
raise ValueError("В файле должны быть символы S и E")
return maze
class PathFindingStrategy:
def findPath(self, maze, start, exit):
raise NotImplementedError
class BFSStrategy(PathFindingStrategy):
def findPath(self, maze, start, exit):
queue = deque([start])
came_from = {start: None}
visited = set([start])
while queue:
current = queue.popleft()
if current == exit:
break
for neighbor in maze.getNeighbors(current):
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
came_from[neighbor] = current
path = self._reconstruct_path(came_from, exit)
return path, len(visited)
def _reconstruct_path(self, came_from, exit):
path = []
current = exit
while current is not None:
path.append(current)
current = came_from.get(current)
path.reverse()
return path if path and path[0] == came_from.get(exit) or path[0] == exit else []
class DFSStrategy(PathFindingStrategy):
def findPath(self, maze, start, exit):
stack = [start]
came_from = {start: None}
visited = set([start])
while stack:
current = stack.pop()
if current == exit:
break
for neighbor in maze.getNeighbors(current):
if neighbor not in visited:
visited.add(neighbor)
stack.append(neighbor)
came_from[neighbor] = current
path = self._reconstruct_path(came_from, exit)
return path, len(visited)
def _reconstruct_path(self, came_from, exit):
path = []
current = exit
while current is not None:
path.append(current)
current = came_from.get(current)
path.reverse()
return path
class AStarStrategy(PathFindingStrategy):
def heuristic(self, a, b):
return abs(a.x - b.x) + abs(a.y - b.y)
def findPath(self, maze, start, exit):
open_set = []
counter = 0
heapq.heappush(open_set, (0, counter, start))
came_from = {start: None}
g_score = {start: 0}
visited = set()
while open_set:
_, _, current = heapq.heappop(open_set)
if current in visited:
continue
visited.add(current)
if current == exit:
break
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)
counter += 1
heapq.heappush(open_set, (f_score, counter, neighbor))
path = self._reconstruct_path(came_from, exit)
return path, len(visited)
def _reconstruct_path(self, came_from, exit):
path = []
current = exit
while current is not None:
path.append(current)
current = came_from.get(current)
path.reverse()
return path
class SearchStats:
def __init__(self, time_ms, visited_cells, path_length):
self.time_ms = time_ms
self.visited_cells = visited_cells
self.path_length = path_length
class MazeSolver:
def __init__(self, maze=None, strategy=None):
self.maze = maze
self.strategy = strategy
def setStrategy(self, strategy):
self.strategy = strategy
def solve(self):
if not self.maze or not self.strategy:
return None
start_time = time.perf_counter()
path, visited_count = self.strategy.findPath(self.maze, self.maze.start, self.maze.exit)
end_time = time.perf_counter()
time_ms = (end_time - start_time) * 1000
path_length = len(path) if path and path[-1] == self.maze.exit else 0
return SearchStats(round(time_ms, 4), visited_count, path_length)
def create_maze_with_walls(size, wall_probability=0.3):
maze = Maze(size, size)
maze.cells = [[Cell(x, y) for x in range(size)] for y in range(size)]
for y in range(size):
for x in range(size):
if random.random() < wall_probability:
maze.cells[y][x].is_wall = True
maze.start = maze.cells[0][0]
maze.exit = maze.cells[size-1][size-1]
maze.start.is_start = True
maze.exit.is_exit = True
maze.start.is_wall = False
maze.exit.is_wall = False
return maze
def create_empty_maze(size):
maze = Maze(size, size)
maze.cells = [[Cell(x, y) for x in range(size)] for y in range(size)]
maze.start = maze.cells[0][0]
maze.exit = maze.cells[size-1][size-1]
maze.start.is_start = True
maze.exit.is_exit = True
return maze
def create_no_exit_maze(size, wall_probability=0.3):
maze = create_maze_with_walls(size, wall_probability)
maze.exit.is_wall = True
return maze
def run_experiment():
maze_configs = {
"10x10_simple": {"size": 10, "type": "normal", "wall_prob": 0.1},
"50x50_with_deadends": {"size": 50, "type": "normal", "wall_prob": 0.3},
"100x100_complex": {"size": 100, "type": "normal", "wall_prob": 0.35},
"empty": {"size": 30, "type": "empty"},
"no_exit": {"size": 30, "type": "no_exit", "wall_prob": 0.3},
}
strategies = {
"BFS": BFSStrategy(),
"DFS": DFSStrategy(),
"AStar": AStarStrategy()
}
results = []
for maze_name, config in maze_configs.items():
size = config["size"]
maze_type = config["type"]
if maze_type == "empty":
maze = create_empty_maze(size)
elif maze_type == "no_exit":
maze = create_no_exit_maze(size, config.get("wall_prob", 0.3))
else:
maze = create_maze_with_walls(size, config.get("wall_prob", 0.3))
for strat_name, strategy in strategies.items():
solver = MazeSolver(maze, strategy)
times, visited_list, lengths = [], [], []
for _ in range(7):
stats = solver.solve()
times.append(stats.time_ms)
visited_list.append(stats.visited_cells)
lengths.append(stats.path_length)
avg_time = sum(times) / len(times)
avg_visited = sum(visited_list) / len(visited_list)
avg_length = sum(lengths) / len(lengths)
results.append([
maze_name, strat_name,
round(avg_time, 4),
int(avg_visited),
int(avg_length)
])
os.makedirs("results", exist_ok=True)
csv_path = "results/results.csv"
with open(csv_path, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["лабиринт", "стратегия", "время_мс", "посещено_клеток", "длина_пути"])
writer.writerows(results)
df = pd.read_csv(csv_path)
plt.figure(figsize=(12, 6))
for strat in df["стратегия"].unique():
subset = df[df["стратегия"] == strat]
plt.plot(subset["лабиринт"], subset["время_мс"], marker='o', label=strat)
plt.title("Сравнение времени работы алгоритмов")
plt.xlabel("Лабиринт")
plt.ylabel("Время (мс)")
plt.legend()
plt.grid(True)
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig("results/time_comparison.png")
plt.close()
plt.figure(figsize=(12, 6))
for strat in df["стратегия"].unique():
subset = df[df["стратегия"] == strat]
plt.plot(subset["лабиринт"], subset["посещено_клеток"], marker='o', label=strat)
plt.title("Количество посещённых клеток")
plt.xlabel("Лабиринт")
plt.ylabel("Посещено клеток")
plt.legend()
plt.grid(True)
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig("results/visited_comparison.png")
plt.close()
if __name__ == "__main__":
run_experiment()

View File

@ -1,170 +0,0 @@
Отчёт ко 2 заданию
1. Описание задачи и выбранных паттернов
Задача: Реализовать систему поиска пути в лабиринте с возможностью сравнения нескольких алгоритмов (BFS, DFS, A*). Система должна поддерживать разные способы построения лабиринта и позволять легко добавлять новые алгоритмы поиска.
Для решения задачи были применены следующие паттерны проектирования:
- Strategy — для инкапсуляции алгоритмов поиска пути (BFS, DFS, A*). Позволяет динамически менять стратегию поиска.
- Builder — для построения лабиринта из файла. Отделяет процесс создания лабиринта от его представления.
Эти паттерны обеспечивают гибкость и расширяемость системы.
Диаграмма классов (Mermaid)
```mermaid
classDiagram
class Maze {
+width: int
+height: int
+cells: List~List~Cell~~
+start: Cell
+exit: Cell
+getCell(x, y)
+getNeighbors(cell)
}
class Cell {
+x: int
+y: int
+is_wall: bool
+is_start: bool
+is_exit: bool
+isPassable()
}
class PathFindingStrategy {
<<interface>>
+findPath(maze, start, exit)
}
class BFSStrategy {
+findPath(maze, start, exit)
}
class DFSStrategy {
+findPath(maze, start, exit)
}
class AStarStrategy {
+findPath(maze, start, exit)
-heuristic(a, b)
}
class MazeSolver {
-maze: Maze
-strategy: PathFindingStrategy
+setStrategy(strategy)
+solve()
}
class MazeBuilder {
<<interface>>
+buildFromFile(filename)
}
class TextFileMazeBuilder {
+buildFromFile(filename)
}
Maze "1" *-- "many" Cell
MazeSolver --> PathFindingStrategy
PathFindingStrategy <|-- BFSStrategy
PathFindingStrategy <|-- DFSStrategy
PathFindingStrategy <|-- AStarStrategy
MazeBuilder <|-- TextFileMazeBuilder
```
2. Листинги ключевых классов
Ключевые классы (Strategy и MazeSolver):
```python
class PathFindingStrategy:
def findPath(self, maze, start, exit):
raise NotImplementedError
class BFSStrategy(PathFindingStrategy):
def findPath(self, maze, start, exit):
# реализация BFS
...
class DFSStrategy(PathFindingStrategy):
def findPath(self, maze, start, exit):
# реализация DFS
...
class AStarStrategy(PathFindingStrategy):
def findPath(self, maze, start, exit):
# реализация A*
...
```
```python
class MazeSolver:
def __init__(self, maze=None, strategy=None):
self.maze = maze
self.strategy = strategy
def setStrategy(self, strategy):
self.strategy = strategy
def solve(self):
if not self.maze or not self.strategy:
return None
# замер времени и вызов стратегии
...
```
Полный код доступен в репозитории (или может быть предоставлен по запросу).
3. Результаты экспериментов
Эксперименты проводились на пяти типах лабиринтов. Ниже представлены ключевые результаты.
Сводная таблица (средние значения):
| 10x10_simple | BFS | 0.1196 | 90 | 0 |
| 10x10_simple | DFS | 0.0526 | 67 | 37 |
| 10x10_simple | AStar | 0.1728 | 86 | 19 |
| 50x50_with_deadends | BFS | 2.2649 | 1621 | 0 |
| 50x50_with_deadends | DFS | 1.5761 | 1124 | 243 |
| 50x50_with_deadends | AStar | 1.1708 | 440 | 99 |
| 100x100_complex | BFS | 0.0184 | 13 | 1 |
| 100x100_complex | DFS | 0.0165 | 13 | 1 |
| 100x100_complex | AStar | 0.0223 | 13 | 1 |
| empty | BFS | 1.3326 | 900 | 0 |
| empty | DFS | 0.821 | 900 | 465 |
| empty | AStar | 2.1481 | 900 | 59 |
| no_exit | BFS | 0.6415 | 488 | 1 |
| no_exit | DFS | 0.6605 | 488 | 1 |
| no_exit | AStar | 1.0716 | 488 | 1 |
Графики (сохранены в папке `results/`):
- `time_comparison.png` — сравнение времени работы алгоритмов
- `visited_comparison.png` — сравнение количества посещённых клеток
4. Анализ эффективности алгоритмов и применимости паттернов
- BFS показывает стабильную работу и находит кратчайший путь, но посещает больше клеток.
- DFS быстрее всех на простых и пустых лабиринтах, однако не гарантирует оптимальность.
- A* эффективнее всего по количеству посещённых клеток на сложных лабиринтах, но на больших картах проигрывает по времени из-за overhead приоритетной очереди.
Паттерн Strategy позволил легко переключаться между алгоритмами без изменения кода `MazeSolver`. Паттерн **Builder** сделал возможным добавление новых источников построения лабиринта (например, генератор случайных лабиринтов) без изменения основной логики.
5. Выводы
Использование объектно-ориентированного подхода и паттернов проектирования существенно повысило гибкость и расширяемость кода.
Преимущества:
- Благодаря паттерну Strategy добавление нового алгоритма поиска (например, Dijkstra) требует только реализации интерфейса `PathFindingStrategy` без изменения `MazeSolver`.
- Паттерн Builder позволяет легко подключать новые способы загрузки лабиринтов.
- Код стал более читаемым и поддерживаемым.
Что было бы сложно изменить без паттернов:
- Замена алгоритма поиска потребовала бы значительных изменений в классе `MazeSolver` (много условных операторов `if`).
- Добавление нового способа построения лабиринта привело бы к дублированию кода.
- Сравнительный эксперимент было бы гораздо сложнее проводить, так как алгоритмы не были бы унифицированы через общий интерфейс.
Таким образом, применение паттернов Strategy и Builder сделало систему легко расширяемой и удобной для проведения экспериментов.

View File

@ -1,16 +0,0 @@
лабиринт,стратегия,время_мс,посещено_клеток,длина_пути
10x10_simple,BFS,0.1196,90,0
10x10_simple,DFS,0.0526,67,37
10x10_simple,AStar,0.1728,86,19
50x50_with_deadends,BFS,2.2649,1621,0
50x50_with_deadends,DFS,1.5761,1124,243
50x50_with_deadends,AStar,1.1708,440,99
100x100_complex,BFS,0.0184,13,1
100x100_complex,DFS,0.0165,13,1
100x100_complex,AStar,0.0223,13,1
empty,BFS,1.3326,900,0
empty,DFS,0.821,900,465
empty,AStar,2.1481,900,59
no_exit,BFS,0.6415,488,1
no_exit,DFS,0.6605,488,1
no_exit,AStar,1.0716,488,1
1 лабиринт стратегия время_мс посещено_клеток длина_пути
2 10x10_simple BFS 0.1196 90 0
3 10x10_simple DFS 0.0526 67 37
4 10x10_simple AStar 0.1728 86 19
5 50x50_with_deadends BFS 2.2649 1621 0
6 50x50_with_deadends DFS 1.5761 1124 243
7 50x50_with_deadends AStar 1.1708 440 99
8 100x100_complex BFS 0.0184 13 1
9 100x100_complex DFS 0.0165 13 1
10 100x100_complex AStar 0.0223 13 1
11 empty BFS 1.3326 900 0
12 empty DFS 0.821 900 465
13 empty AStar 2.1481 900 59
14 no_exit BFS 0.6415 488 1
15 no_exit DFS 0.6605 488 1
16 no_exit AStar 1.0716 488 1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

View File

View File

@ -1,57 +0,0 @@
#Графики
import csv
import matplotlib.pyplot as plt
data = []
with open('results.csv', 'r') as f:
reader = csv.reader(f)
for row in reader:
data.append(row)
print(data)
types = ["shuffled","sorted"]
algorythms = ['Linked list','Hash-table','BST']
operations = ['Insert','Find','Delete']
for dt in types:
for ot in operations:
X = algorythms
Y = [0.,0.,0.]
for row in data:
if row[1] == dt and row[2] == ot:
if row[0] == X[0]:
Y[0] = float(row[3])
elif row[0] == X[1]:
Y[1] = float(row[3])
elif row[0] == X[2]:
Y[2] = float(row[3])
plt.bar(X,Y)
plt.title(dt + ot)
plt.ylabel('Время')
plt.show()
for dt in types:
for at in algorythms:
X = operations
Y = [0.,0.,0.]
for row in data:
if row[1] == dt and row[0] == at:
if row[2] == X[0]:
Y[0] = float(row[3])
elif row[2] == X[1]:
Y[1] = float(row[3])
elif row[2] == X[2]:
Y[2] = float(row[3])
plt.bar(X,Y,color='g')
plt.title(dt + at)
plt.ylabel('Время')
plt.show()

View File

@ -1,58 +0,0 @@
#Графики через ln
import csv
import matplotlib.pyplot as plt
import numpy as np
data = []
with open('results.csv', 'r') as f:
reader = csv.reader(f)
for row in reader:
data.append(row)
print(data)
types = ["shuffled","sorted"]
algorythms = ['Linked list','Hash-table','BST']
operations = ['Insert','Find','Delete']
for dt in types:
for ot in operations:
X = algorythms
Y = [0.,0.,0.]
for row in data:
if row[1] == dt and row[2] == ot:
if row[0] == X[0]:
Y[0] = np.log(float(row[3]))
elif row[0] == X[1]:
Y[1] = np.log(float(row[3]))
elif row[0] == X[2]:
Y[2] = np.log(float(row[3]))
plt.bar(X,Y)
plt.title(dt + ot)
plt.ylabel('Время')
plt.show()
for dt in types:
for at in algorythms:
X = operations
Y = [0.,0.,0.]
for row in data:
if row[1] == dt and row[0] == at:
if row[2] == X[0]:
Y[0] = np.log(float(row[3]))
elif row[2] == X[1]:
Y[1] = np.log(float(row[3]))
elif row[2] == X[2]:
Y[2] = np.log(float(row[3]))
plt.bar(X,Y,color='g')
plt.title(dt + at)
plt.ylabel('Время')
plt.show()

View File

@ -1,176 +0,0 @@
Данный файл был создан в ручную копированием данных выводимых программой main при последнем запуске.
Linked list
ll_insert test
{'name': 'Andrey', 'phone': '7-234-246', 'next': None}
{'name': 'Andrey', 'phone': '7-234-246', 'next': {'name': 'Ivan', 'phone': '6-352-095', 'next': None}}
{'name': 'Andrey', 'phone': '5-257-098', 'next': {'name': 'Ivan', 'phone': '6-352-095', 'next': None}}
{'name': 'Andrey', 'phone': '5-257-098', 'next': {'name': 'Ivan', 'phone': '6-352-095', 'next': {'name': 'Igor', 'phone': '1-374-098', 'next': None}}}
{'name': 'Andrey', 'phone': '5-257-098', 'next': {'name': 'Ivan', 'phone': '6-352-095', 'next': {'name': 'Igor', 'phone': '1-374-098', 'next': {'name': 'Nagibator3000', 'phone': '9-387-098', 'next': None}}}}
{'name': 'Andrey', 'phone': '5-257-098', 'next': {'name': 'Ivan', 'phone': '6-352-095', 'next': {'name': 'Igor', 'phone': '1-374-098', 'next': {'name': 'Nagibator3000', 'phone': '9-387-098', 'next': {'name': 'Sberbank', 'phone': '5-135-357', 'next': None}}}}}
{'name': 'Andrey', 'phone': '5-257-098', 'next': {'name': 'Ivan', 'phone': '6-352-095', 'next': {'name': 'Igor', 'phone': '1-374-098', 'next': {'name': 'Nagibator3000', 'phone': '9-387-098', 'next': {'name': 'Sberbank', 'phone': '5-135-357', 'next': {'name': 'Loshped', 'phone': '0-000-000', 'next': None}}}}}}
test end
ll_find test
6-352-095
5-257-098
5-135-357
9-387-098
None
test_end
ll_delete test
{'name': 'Andrey', 'phone': '5-257-098', 'next': {'name': 'Ivan', 'phone': '6-352-095', 'next': {'name': 'Igor', 'phone': '1-374-098', 'next': {'name': 'Sberbank', 'phone': '5-135-357', 'next': {'name': 'Loshped', 'phone': '0-000-000', 'next': None}}}}}
{'name': 'Andrey', 'phone': '5-257-098', 'next': {'name': 'Ivan', 'phone': '6-352-095', 'next': {'name': 'Igor', 'phone': '1-374-098', 'next': {'name': 'Sberbank', 'phone': '5-135-357', 'next': {'name': 'Loshped', 'phone': '0-000-000', 'next': None}}}}}
{'name': 'Andrey', 'phone': '5-257-098', 'next': {'name': 'Ivan', 'phone': '6-352-095', 'next': {'name': 'Igor', 'phone': '1-374-098', 'next': {'name': 'Sberbank', 'phone': '5-135-357', 'next': None}}}}
{'name': 'Andrey', 'phone': '5-257-098', 'next': {'name': 'Ivan', 'phone': '6-352-095', 'next': {'name': 'Igor', 'phone': '1-374-098', 'next': {'name': 'Sberbank', 'phone': '5-135-357', 'next': None}}}}
test_end
ll_list_all test
[('Andrey', '5-257-098'), ('Igor', '1-374-098'), ('Ivan', '6-352-095'), ('Sberbank', '5-135-357')]
test_end
Hash Table
ht_insert test
[None, None, None, None, None, None, None, None]
[{'name': 'Andrey', 'phone': '7-234-246', 'next': None}, None, None, None, None, None, None, None]
[{'name': 'Andrey', 'phone': '7-234-246', 'next': None}, None, None, None, None, None, {'name': 'Ivan', 'phone': '6-352-095', 'next': None}, None]
[{'name': 'Andrey', 'phone': '5-257-098', 'next': None}, None, None, None, None, None, {'name': 'Ivan', 'phone': '6-352-095', 'next': None}, None]
[{'name': 'Andrey', 'phone': '5-257-098', 'next': None}, None, None, None, None, None, {'name': 'Ivan', 'phone': '6-352-095', 'next': None}, {'name': 'Igor', 'phone': '1-374-098', 'next': None}]
[{'name': 'Andrey', 'phone': '5-257-098', 'next': None}, None, None, None, {'name': 'Nagibator3000', 'phone': '9-387-098', 'next': None}, None, {'name': 'Ivan', 'phone': '6-352-095', 'next': None}, {'name': 'Igor', 'phone': '1-374-098', 'next': None}]
[{'name': 'Andrey', 'phone': '5-257-098', 'next': None}, None, None, None, {'name': 'Nagibator3000', 'phone': '9-387-098', 'next': None}, {'name': 'Sberbank', 'phone': '5-135-357', 'next': None}, {'name': 'Ivan', 'phone': '6-352-095', 'next': None}, {'name': 'Igor', 'phone': '1-374-098', 'next': None}]
[{'name': 'Andrey', 'phone': '5-257-098', 'next': None}, {'name': 'Loshped', 'phone': '0-000-000', 'next': None}, None, None, {'name': 'Nagibator3000', 'phone': '9-387-098', 'next': None}, {'name': 'Sberbank', 'phone': '5-135-357', 'next': None}, {'name': 'Ivan', 'phone': '6-352-095', 'next': None}, {'name': 'Igor', 'phone': '1-374-098', 'next': None}]
test end
ht_find test
6-352-095
5-257-098
5-135-357
9-387-098
None
test end
ht_delete test
[{'name': 'Andrey', 'phone': '5-257-098', 'next': None}, {'name': 'Loshped', 'phone': '0-000-000', 'next': None}, None, None, None, {'name': 'Sberbank', 'phone': '5-135-357', 'next': None}, {'name': 'Ivan', 'phone': '6-352-095', 'next': None}, {'name': 'Igor', 'phone': '1-374-098', 'next': None}]
[{'name': 'Andrey', 'phone': '5-257-098', 'next': None}, {'name': 'Loshped', 'phone': '0-000-000', 'next': None}, None, None, None, {'name': 'Sberbank', 'phone': '5-135-357', 'next': None}, {'name': 'Ivan', 'phone': '6-352-095', 'next': None}, {'name': 'Igor', 'phone': '1-374-098', 'next': None}]
[{'name': 'Andrey', 'phone': '5-257-098', 'next': None}, None, None, None, None, {'name': 'Sberbank', 'phone': '5-135-357', 'next': None}, {'name': 'Ivan', 'phone': '6-352-095', 'next': None}, {'name': 'Igor', 'phone': '1-374-098', 'next': None}]
[{'name': 'Andrey', 'phone': '5-257-098', 'next': None}, None, None, None, None, {'name': 'Sberbank', 'phone': '5-135-357', 'next': None}, {'name': 'Ivan', 'phone': '6-352-095', 'next': None}, {'name': 'Igor', 'phone': '1-374-098', 'next': None}]
test_end
ht_list_all test
[('Andrey', '5-257-098'), ('Igor', '1-374-098'), ('Ivan', '6-352-095'), ('Sberbank', '5-135-357')]
test_end
bst_insert test
None
{'name': 'Andrey', 'phone': '7-234-246', 'left': None, 'right': None}
{'name': 'Andrey', 'phone': '7-234-246', 'left': {'name': 'Ivan', 'phone': '6-352-095', 'left': None, 'right': None}, 'right': None}
{'name': 'Andrey', 'phone': '5-257-098', 'left': {'name': 'Ivan', 'phone': '6-352-095', 'left': None, 'right': None}, 'right': None}
{'name': 'Andrey', 'phone': '5-257-098', 'left': {'name': 'Ivan', 'phone': '6-352-095', 'left': None, 'right': {'name': 'Igor', 'phone': '1-374-098', 'left': None, 'right': None}}, 'right': None}
{'name': 'Andrey', 'phone': '5-257-098', 'left': {'name': 'Ivan', 'phone': '6-352-095', 'left': None, 'right': {'name': 'Igor', 'phone': '1-374-098', 'left': None, 'right': None}}, 'right': {'name': 'Nagibator3000', 'phone': '9-387-098', 'left': None, 'right': None}}
{'name': 'Andrey', 'phone': '5-257-098', 'left': {'name': 'Ivan', 'phone': '6-352-095', 'left': None, 'right': {'name': 'Igor', 'phone': '1-374-098', 'left': {'name': 'Sberbank', 'phone': '5-135-357', 'left': None, 'right': None}, 'right': None}}, 'right': {'name': 'Nagibator3000', 'phone': '9-387-098', 'left': None, 'right': None}}
{'name': 'Andrey', 'phone': '5-257-098', 'left': {'name': 'Ivan', 'phone': '6-352-095', 'left': {'name': 'Loshped', 'phone': '0-000-000', 'left': None, 'right': None}, 'right': {'name': 'Igor', 'phone': '1-374-098', 'left': {'name': 'Sberbank', 'phone': '5-135-357', 'left': None, 'right': None}, 'right': None}}, 'right': {'name': 'Nagibator3000', 'phone': '9-387-098', 'left': None, 'right': None}}
test end
bst_find test
6-352-095
5-257-098
5-135-357
9-387-098
None
test end
bst_delete test
{'name': 'Andrey', 'phone': '5-257-098', 'left': {'name': 'Ivan', 'phone': '6-352-095', 'left': {'name': 'Loshped', 'phone': '0-000-000', 'left': None, 'right': None}, 'right': {'name': 'Igor', 'phone': '1-374-098', 'left': {'name': 'Sberbank', 'phone': '5-135-357', 'left': None, 'right': None}, 'right': None}}, 'right': None}
{'name': 'Andrey', 'phone': '5-257-098', 'left': {'name': 'Ivan', 'phone': '6-352-095', 'left': {'name': 'Loshped', 'phone': '0-000-000', 'left': None, 'right': None}, 'right': {'name': 'Igor', 'phone': '1-374-098', 'left': {'name': 'Sberbank', 'phone': '5-135-357', 'left': None, 'right': None}, 'right': None}}, 'right': None}
{'name': 'Ivan', 'phone': '6-352-095', 'left': {'name': 'Loshped', 'phone': '0-000-000', 'left': None, 'right': None}, 'right': {'name': 'Igor', 'phone': '1-374-098', 'left': {'name': 'Sberbank', 'phone': '5-135-357', 'left': None, 'right': None}, 'right': None}}
{'name': 'Ivan', 'phone': '6-352-095', 'left': {'name': 'Loshped', 'phone': '0-000-000', 'left': None, 'right': None}, 'right': {'name': 'Igor', 'phone': '1-374-098', 'left': {'name': 'Sberbank', 'phone': '5-135-357', 'left': None, 'right': None}, 'right': None}}
test_end
bst_list_all test
[('Loshped', '0-000-000'), ('Ivan', '6-352-095'), ('Sberbank', '5-135-357'), ('Igor', '1-374-098')]
test_end
Итерация 1
Время Связного Списка: 2.9917209999402985 0.02659020002465695 0.012590099941007793
Время Хеш-таблицы: 0.005119499983265996 3.9100064896047115e-05 2.0299921743571758e-05
Время Двоичного Дерева Поиска: 0.09058830002322793 0.00027840002439916134 0.00014549994375556707
Итерация 2
Время Связного Списка: 2.903203699970618 0.023054099990986288 0.010875999927520752
Время Хеш-таблицы: 0.00455170008353889 3.400002606213093e-05 1.9400031305849552e-05
Время Двоичного Дерева Поиска: 0.03216309996787459 0.00027810002211481333 0.00014749995898455381
Итерация 3
Время Связного Списка: 2.921877200016752 0.025615100050345063 0.01236239995341748
Время Хеш-таблицы: 0.00479620008263737 3.529991954565048e-05 2.0200037397444248e-05
Время Двоичного Дерева Поиска: 0.03450970002450049 0.0004401999758556485 0.00016369996592402458
Итерация 4
Время Связного Списка: 3.003447199938819 0.02533069998025894 0.010745699983090162
Время Хеш-таблицы: 0.004474699962884188 3.350002225488424e-05 1.8000020645558834e-05
Время Двоичного Дерева Поиска: 0.07804860000032932 0.00027279998175799847 0.00014109991025179625
Итерация 5
Время Связного Списка: 2.9807132000569254 0.0290351000148803 0.013929600012488663
Время Хеш-таблицы: 0.005072099971584976 5.009991582483053e-05 2.2300053387880325e-05
Время Двоичного Дерева Поиска: 0.03607590007595718 0.0003352999920025468 0.00016259995754808187
Итерация 1
Время Связного Списка: 2.5927454999182373 0.02170580008532852 0.010246100020594895
Время Хеш-таблицы: 0.00436040002387017 3.50000336766243e-05 1.8999911844730377e-05
Время Двоичного Дерева Поиска: 0.029087499948218465 0.0003063000040128827 0.00015670002903789282
Итерация 2
Время Связного Списка: 2.5688632000237703 0.02236179995816201 0.00998460000846535
Время Хеш-таблицы: 0.004271199926733971 3.3100019209086895e-05 1.7400016076862812e-05
Время Двоичного Дерева Поиска: 0.03174210002180189 0.000283299945294857 0.00013529998250305653
Итерация 3
Время Связного Списка: 2.6008588999975473 0.019859899999573827 0.010582599905319512
Время Хеш-таблицы: 0.00447160005569458 3.23000131174922e-05 1.810002140700817e-05
Время Двоичного Дерева Поиска: 0.03173729998525232 0.0002880999818444252 0.0001452000578865409
Итерация 4
Время Связного Списка: 2.5892133000306785 0.022096000029705465 0.010453099966980517
Время Хеш-таблицы: 0.004718700074590743 3.42000275850296e-05 1.7300015315413475e-05
Время Двоичного Дерева Поиска: 0.033104100031778216 0.0002930999035015702 0.00015160010661929846
Итерация 5
Время Связного Списка: 2.5684097999474034 0.020924199954606593 0.009929200052283704
Время Хеш-таблицы: 0.0046051000244915485 3.370002377778292e-05 1.8400023691356182e-05
Время Двоичного Дерева Поиска: 0.03069589997176081 0.0003073000116273761 0.00014600006397813559
[['Linked list', 'shuffled', 'Insert', '2.9601924599846825'], ['Linked list', 'shuffled', 'Find', '0.02592504001222551'], ['Linked list', 'shuffled', 'Delete', '0.01210075996350497'], ['Hash-table', 'shuffled', 'Insert', '0.004802840016782284'], ['Hash-table', 'shuffled', 'Find', '3.839998971670866e-05'], ['Hash-table', 'shuffled', 'Delete', '2.0040012896060943e-05'], ['BST', 'shuffled', 'Insert', '0.0542771200183779'], ['BST', 'shuffled', 'Find', '0.0003209599992260337'], ['BST', 'shuffled', 'Delete', '0.00015207994729280472'], ['Linked list', 'sorted', 'Insert', '2.5840181399835274'], ['Linked list', 'sorted', 'Find', '0.021389540005475282'], ['Linked list', 'sorted', 'Delete', '0.010239119990728796'], ['Hash-table', 'sorted', 'Insert', '0.004485400021076202'], ['Hash-table', 'sorted', 'Find', '3.3660023473203185e-05'], ['Hash-table', 'sorted', 'Delete', '1.8039997667074203e-05'], ['BST', 'sorted', 'Insert', '0.03127337999176234'], ['BST', 'sorted', 'Find', '0.00029561996925622227'], ['BST', 'sorted', 'Delete', '0.00014696004800498485']]

View File

@ -1,449 +0,0 @@
#LinkedListPhoneBook
head = None #!!!!!!!!!!!!!!
print("\nLinked list\n")
def ll_insert(head, name, phone):
curr = head
while curr is not None:
if curr['name'] == name:
curr['phone'] = phone
return head
elif curr['next'] == None:
curr['next'] = {'name' : name, 'phone' : phone, 'next' : None}
return head
curr = curr['next']
return {'name' : name, 'phone' : phone, 'next' : None}
test_names = ['Andrey', 'Ivan', 'Andrey', 'Igor', 'Nagibator3000', 'Sberbank','Loshped']
test_phones = ['7-234-246','6-352-095','5-257-098','1-374-098','9-387-098','5-135-357','0-000-000']
print("\nll_insert test\n")
for i in range(len(test_names)):
head = ll_insert(head, test_names[i], test_phones[i])
print(head)
print("\ntest end\n\n")
def ll_find(head, name):
curr = head
while curr is not None:
if curr['name'] == name:
return curr['phone']
curr = curr['next']
return None
print('ll_find test\n')
test_names = ["Ivan", "Andrey", "Sberbank", "Nagibator3000","Ermola"]
for name in test_names:
print(ll_find(head, name))
print('\ntest_end\n\n')
def ll_delete(head, name):
if head is not None:
if head['name'] == name:
return head['next']
old_curr = head
curr = head['next']
while curr is not None:
if curr['name'] == name:
old_curr['next'] = curr['next']
break
old_curr, curr = curr, curr['next']
return head
print('ll_delete test\n')
test_names = ['Nagibator3000','Nagibator3000','Loshped','Ermola']
for name in test_names:
head = ll_delete(head, name)
print(head)
print('\ntest_end\n\n')
def ll_list_all(head):
res = []
curr = head
while curr is not None:
res += [(curr['name'],curr['phone'])]
curr = curr['next']
return sorted(res)
print('ll_list_all test\n')
print(ll_list_all(head))
print('\ntest_end\n\n')
#HashTablePhoneBook
print("\nHash Table\n")
size = 8
buckets = [None] * size
def index(name,size):
return hash(name) % size
def ht_insert(buckets, name, phone):
ind = index(name, size)
buckets[ind] = ll_insert(buckets[ind], name, phone)
return buckets
def ht_find(buckets, name):
ind = index(name, size)
return ll_find(buckets[ind], name)
def ht_delete(buckets, name):
ind = index(name, size)
buckets[ind] = ll_delete(buckets[ind], name)
return buckets
def ht_list_all(buckets):
res = []
for head in buckets:
curr = head
while curr is not None:
res += [(curr['name'],curr['phone'])]
curr = curr['next']
return sorted(res)
test_names = ['Andrey', 'Ivan', 'Andrey', 'Igor', 'Nagibator3000', 'Sberbank','Loshped']
test_phones = ['7-234-246','6-352-095','5-257-098','1-374-098','9-387-098','5-135-357','0-000-000']
print("\nht_insert test\n")
print(buckets)
for i in range(len(test_names)):
buckets = ht_insert(buckets, test_names[i], test_phones[i])
print(buckets)
print("\ntest end\n\n")
print("ht_find test\n")
test_names = ["Ivan", "Andrey", "Sberbank", "Nagibator3000","Ermola"]
for name in test_names:
print(ht_find(buckets, name))
print("\ntest end\n\n")
print('ht_delete test\n')
test_names = ['Nagibator3000','Nagibator3000','Loshped','Ermola']
for name in test_names:
buckets = ht_delete(buckets, name)
print(buckets)
print('\ntest_end\n\n')
print('ht_list_all test\n')
print(ht_list_all(buckets))
print('\ntest_end\n\n')
#BinarySearchTree
root = None
def bst_insert(root, name, phone):
if root == None:
return {'name': name, 'phone': phone, 'left': None, 'right': None}
elif name == root['name']:
root['phone'] = phone
return root
elif hash(name) < hash(root['name']):
root['left'] = bst_insert(root['left'], name, phone)
return root
else:
root['right'] = bst_insert(root['right'], name, phone)
return root
def bst_find(root, name):
if root == None:
return None
elif name == root['name']:
return root['phone']
elif hash(name) < hash(root['name']):
return bst_find(root['left'],name)
else:
return bst_find(root['right'],name)
def bst_delete(root, name):
if root is None:
return None
elif name == root['name']:
if root['left'] is None and root['right'] is None:
return None
elif root['left'] is not None and root['right'] is None:
return root['left']
elif root['right'] is not None and root['left'] is None:
return root['right']
curr = root['left']
oldcurr = root
while curr['right'] is not None:
oldcurr,curr = curr,curr['right']
if oldcurr == root:
root['left'] = curr['left']
else:
oldcurr['right'] = curr['left']
curr['left'],curr['right'] = root['left'],root['right']
return curr
elif hash(name) < hash(root['name']):
root['left'] = bst_delete(root['left'],name)
return root
else:
root['right'] = bst_delete(root['right'],name)
return root
def bst_list_all(root):
if root is None:
return []
return bst_list_all(root['left']) + [(root['name'],root['phone'])] + bst_list_all(root['right'])
test_names = ['Andrey', 'Ivan', 'Andrey', 'Igor', 'Nagibator3000', 'Sberbank','Loshped']
test_phones = ['7-234-246','6-352-095','5-257-098','1-374-098','9-387-098','5-135-357','0-000-000']
print("\nbst_insert test\n")
print(root)
for i in range(len(test_names)):
root = bst_insert(root, test_names[i], test_phones[i])
print(root)
print("\ntest end\n\n")
print("bst_find test\n")
test_names = ["Ivan", "Andrey", "Sberbank", "Nagibator3000","Ermola"]
for name in test_names:
print(bst_find(root, name))
print("\ntest end\n\n")
print('bst_delete test\n')
test_names = ['Nagibator3000','Nagibator3000','Andrey','Ermola']
for name in test_names:
root = bst_delete(root, name)
print(root)
print('\ntest_end\n\n')
print('bst_list_all test\n')
print(bst_list_all(root))
print('\ntest_end\n\n')
###ЭКСПЕРЕМЕНТАЛЬНАЯ ЧАСТЬ
#1 Генерация
import random
records_shuffled = []
records_sorted = []
N = 10000
for i in range(1,N+1):
number = str(random.randint(1,9)) + '-' + str(random.randint(100,999)) + '-' + str(random.randint(100,999)) + '-' + str(random.randint(10,99)) + '-' + str(random.randint(10,99))
records_sorted += [(f"User_{i:05d}", number)]
records_shuffled = records_sorted[:] #срезал чтобы не ссылка была
random.shuffle(records_shuffled)
#2 Инструменты замера времени
import time
#start = time.perf_counter()
# ... операции ...
#end = time.perf_counter()
#elapsed = end - start # время в секундах
results = []
types = ["shuffled","sorted"]
for dt in range(2):
data = (records_shuffled, records_sorted)[dt]
time_res_sum = [0]*9
for iteration in range(5):
names = [x for x,_ in data]
test_names = random.sample(names, 150)
find_names = test_names[0:100]
find_names += [f"None_{i}" for i in range(10)]
delete_names = test_names[100:150]
#LinkedList
time_res = []
head = None
start = time.perf_counter()
for a in data:
head = ll_insert(head, a[0], a[1])
end = time.perf_counter()
time_res.append(end - start)
start = time.perf_counter()
for name in find_names:
ll_find(head, name)
end = time.perf_counter()
time_res.append(end - start)
start = time.perf_counter()
for name in delete_names:
head = ll_delete(head, name)
end = time.perf_counter()
time_res.append(end - start)
#HashTable
size = 15013 #простое число от которого 10000 - это примерно 0.7 (коэффициент заполнения)
buckets = [None] * size
start = time.perf_counter()
for a in data:
buckets = ht_insert(buckets, a[0], a[1])
end = time.perf_counter()
time_res.append(end - start)
start = time.perf_counter()
for name in find_names:
ht_find(buckets, name)
end = time.perf_counter()
time_res.append(end - start)
start = time.perf_counter()
for name in delete_names:
buckets = ht_delete(buckets, name)
end = time.perf_counter()
time_res.append(end - start)
#BinarySearchTree
root = None
start = time.perf_counter()
for a in data:
root = bst_insert(root, a[0], a[1])
end = time.perf_counter()
time_res.append(end - start)
start = time.perf_counter()
for name in find_names:
bst_find(root, name)
end = time.perf_counter()
time_res.append(end - start)
start = time.perf_counter()
for name in delete_names:
root = bst_delete(root, name)
end = time.perf_counter()
time_res.append(end - start)
print("Итерация ", iteration+1)
print("Время Связного Списка: ", time_res[0], time_res[1], time_res[2])
print("Время Хеш-таблицы: ", time_res[3], time_res[4], time_res[5])
print("Время Двоичного Дерева Поиска: ", time_res[6], time_res[7], time_res[8])
for i in range(9):
time_res_sum[i] += time_res[i]
for i in range(9):
time_res_sum[i] /= 5
results.append(["Linked list", types[dt], "Insert",str(time_res_sum[0])])
results.append(["Linked list", types[dt], "Find",str(time_res_sum[1])])
results.append(["Linked list", types[dt], "Delete",str(time_res_sum[2])])
results.append(["Hash-table", types[dt], "Insert",str(time_res_sum[3])])
results.append(["Hash-table", types[dt], "Find",str(time_res_sum[4])])
results.append(["Hash-table", types[dt], "Delete",str(time_res_sum[5])])
results.append(["BST", types[dt], "Insert",str(time_res_sum[6])])
results.append(["BST", types[dt], "Find",str(time_res_sum[7])])
results.append(["BST", types[dt], "Delete",str(time_res_sum[8])])
print(results)
import csv
with open("results.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerows(results)

View File

@ -1,18 +0,0 @@
Linked list,shuffled,Insert,2.9601924599846825
Linked list,shuffled,Find,0.02592504001222551
Linked list,shuffled,Delete,0.01210075996350497
Hash-table,shuffled,Insert,0.004802840016782284
Hash-table,shuffled,Find,3.839998971670866e-05
Hash-table,shuffled,Delete,2.0040012896060943e-05
BST,shuffled,Insert,0.0542771200183779
BST,shuffled,Find,0.0003209599992260337
BST,shuffled,Delete,0.00015207994729280472
Linked list,sorted,Insert,2.5840181399835274
Linked list,sorted,Find,0.021389540005475282
Linked list,sorted,Delete,0.010239119990728796
Hash-table,sorted,Insert,0.004485400021076202
Hash-table,sorted,Find,3.3660023473203185e-05
Hash-table,sorted,Delete,1.8039997667074203e-05
BST,sorted,Insert,0.03127337999176234
BST,sorted,Find,0.00029561996925622227
BST,sorted,Delete,0.00014696004800498485
1 Linked list shuffled Insert 2.9601924599846825
2 Linked list shuffled Find 0.02592504001222551
3 Linked list shuffled Delete 0.01210075996350497
4 Hash-table shuffled Insert 0.004802840016782284
5 Hash-table shuffled Find 3.839998971670866e-05
6 Hash-table shuffled Delete 2.0040012896060943e-05
7 BST shuffled Insert 0.0542771200183779
8 BST shuffled Find 0.0003209599992260337
9 BST shuffled Delete 0.00015207994729280472
10 Linked list sorted Insert 2.5840181399835274
11 Linked list sorted Find 0.021389540005475282
12 Linked list sorted Delete 0.010239119990728796
13 Hash-table sorted Insert 0.004485400021076202
14 Hash-table sorted Find 3.3660023473203185e-05
15 Hash-table sorted Delete 1.8039997667074203e-05
16 BST sorted Insert 0.03127337999176234
17 BST sorted Find 0.00029561996925622227
18 BST sorted Delete 0.00014696004800498485

View File

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

View File

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

View File

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

View File

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

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