Compare commits
No commits in common. "develop" and "Task2" have entirely different histories.
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -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))
|
||||
|
|
@ -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()
|
||||
|
|
@ -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,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))
|
||||
|
|
@ -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))
|
||||
|
Before Width: | Height: | Size: 46 KiB |
|
|
@ -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()
|
||||
|
|
@ -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,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")
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
##########
|
||||
#S #
|
||||
# ####### #
|
||||
# # # #
|
||||
# # ### # #
|
||||
# # E #
|
||||
##########
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
##########
|
||||
#S #
|
||||
# # #### #
|
||||
# # #
|
||||
# #### # #
|
||||
# # #
|
||||
# #### # #
|
||||
# # #
|
||||
# #
|
||||
########E#
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
####################
|
||||
#S #
|
||||
# ############### #
|
||||
# # # #
|
||||
# # ######### # # #
|
||||
# # # # # # #
|
||||
# # # ##### # # # #
|
||||
# # # # # # # # #
|
||||
# # # # # # # # # #
|
||||
# # # # # # # # #
|
||||
# # # ##### # # # #
|
||||
# # # # # # #
|
||||
# # ######### # # #
|
||||
# # # #
|
||||
# ############### #
|
||||
# #
|
||||
# ############### #
|
||||
# # # #
|
||||
# # ########### # #
|
||||
# E#
|
||||
####################
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
###############
|
||||
#S #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# E #
|
||||
###############
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
##########
|
||||
#S #
|
||||
# # #
|
||||
# # #### #
|
||||
# # #
|
||||
##########
|
||||
E#########
|
||||
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
|
@ -1,94 +0,0 @@
|
|||
# Отчёт по лабораторной работе «Структуры данных для телефонного справочника»
|
||||
|
||||
## 1. Постановка задачи
|
||||
|
||||
В рамках работы требовалось реализовать три структуры данных «с нуля» (без использования встроенных коллекций, кроме базовых списков):
|
||||
|
||||
- связный список,
|
||||
- хеш-таблицу с цепочками,
|
||||
- двоичное дерево поиска (несбалансированное).
|
||||
|
||||
Для каждой структуры необходимо реализовать операции `insert`, `find`, `delete` и `list_all` (возврат всех записей, отсортированных по имени). Затем на наборе из 10 000 записей выполнить экспериментальное сравнение производительности в двух режимах: при случайном порядке вставки и при вставке записей, отсортированных по имени. Каждый эксперимент повторялся 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 |
|
||||
|
||||
### Примечания к методике
|
||||
|
||||
- **Вставка** – добавление всех 10 000 записей в пустую структуру.
|
||||
- **Поиск** – 100 заведомо существующих имён + 10 несуществующих (общее количество вызовов 110).
|
||||
- **Удаление** – 50 случайных существующих записей.
|
||||
- Все замеры выполнены с помощью `time.perf_counter()`.
|
||||
- Для хеш-таблицы использовалось 10 корзин.
|
||||
- Рекурсивная глубина BST увеличена до 20 000, чтобы избежать переполнения стека.
|
||||
|
||||
## 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. Связный список – ожидаемо медленный
|
||||
|
||||
Линейный список не обеспечивает прямого доступа, поэтому все операции (кроме удаления после нахождения) требуют обхода в среднем половины списка. Даже при сравнительно небольшом объёме данных (10 000 записей) времена велики:
|
||||
|
||||
- вставка ≈ **4.6 с** (на два порядка хуже, чем у хеш-таблицы и BST на случайных данных),
|
||||
- поиск ≈ **0.03 с** (в 6–10 раз медленнее, чем у других структур).
|
||||
|
||||
Интересно, что на отсортированных данных список показывает немного лучшее время, чем на случайных. Причина: при вставке в конец отсортированного списка (имена идут в алфавитном порядке) новые узлы добавляются без поиска дубликатов? Но алгоритм `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` – сторонний).
|
||||
|
|
@ -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.035–0.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) реализован и работает корректно.
|
||||
|
|
@ -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("")
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
s
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
e
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
s## # # # # # # # ### # ## # # # # # # ### # # # # # ###
|
||||
# ## # ## ### ## # # ## ## # # # ### ## ## #
|
||||
# ## # # # # # ## ## # # # ## # ## # # # # ## # # # #
|
||||
## # # # ##### # # # # # ## # ## # # # # # # # # # # # # #
|
||||
# ### ## # # # # ## ## ## # # # # # ## #
|
||||
# # # # # # ## # ### ## ## # ##### # # # # # ### ## # ### # # #
|
||||
# # # ## ### # # # # # ### # # # ## ##### # #
|
||||
# # ### # # ## ### # # # # # ## # ## ## # ## # ## ## # #
|
||||
# # # ## # # # # ## # # # # # # # # # # # ### ## # # #
|
||||
# # # # # ## # # # # # ## ## ### ###### ## ## ### #
|
||||
## ## # # # # # # ### # # # ### # ## #
|
||||
# # ###### # # # ## # # # ## # # # ## #### # # #
|
||||
## # # # ### # # # # # #### # # # ## # # # #
|
||||
# # # # ### ## ## # # # ## # # # # # ### # # # ### # # #### # ##
|
||||
# ## # # # # # # # # # # # # # # ## ### ## # # ## #
|
||||
### # # # # ## ### # # ## # # # ## # ## # ## #
|
||||
### # # # # ### # # # # # ## # # # # # ## # # ## #
|
||||
# # # # ## # # ### # ## ## # ### # # ### ## #
|
||||
### ## # ## # ## # # # # # # # # # # # ####### ##
|
||||
## ## # # # # ## # # # ## ### ### # # # ### # # # # ## # ###
|
||||
### #### ### # # # # ## ## # #### # # # # # # ## # #
|
||||
### # ## ## # ## ## ## # # # ## # # ## # ## # #
|
||||
# # # # # # # #### ## # # #### ## # ## ## # # #
|
||||
# ## # ## # # # # ### # ## # ## # # ## # # # ## # # #### # #
|
||||
# # ## # # # # # # # # # ## ## # # # ### # #
|
||||
# # # # # # ## # # # ### # ## # # # # ## # # # # # #
|
||||
# # # ### # # # # # ## ## # # ## # # ## # # #
|
||||
## ## ### # # ## # # # # # # # # # # # # # ## ## # # # #
|
||||
# # # # # # ## # # # # # # # ##
|
||||
# # # # # # ## # ## # ## # # # ## ## ## ## ### # # # # # #
|
||||
# # # # # # # #### # ## # # # # ## ## # # # ## # #
|
||||
## # # # # # # # ###### # # ### # # ## # # # # ### ##
|
||||
# # ## # # # # #### # #### # # # ## ## ## #
|
||||
# # # # # # # ## # # # # # ### ### # # # # # # #
|
||||
# # # # ## # # # # # ## # ## # # ## # ## ### # #
|
||||
#### # # # # ## # # # # # ## ### # # # # ### # ## #
|
||||
# # # # ## ## # # # # # # # # # # # # # # # ## ## # # ##
|
||||
# # # # # # ## # # # ## ## # # # # # # ## #
|
||||
# # ## ## ### ## # # ## # # # ## # # # # # # # # #
|
||||
## ## # # # # # # ## ### # # # ## # # ## # ### # ### ##
|
||||
## ## # # # # # # # # # ## # # ## # ### # # # #
|
||||
## # # ## ## ## # # ## # # ## # # # # ## # #
|
||||
## # ## # ## ## # # # # # # # # # # ### # # # # # ## # #
|
||||
# ## ## # # # # # #### ## # # # # # # # # #
|
||||
# # # # # # # # ## # # # # # ### # # # # #### ## ### ####
|
||||
# ## # # #### # # # # #### # # # # # ### # # ### # ## ##
|
||||
## # ## # ## # # # # # # ### # # # # # ## # # #
|
||||
# # # # ## # # # ### #### ## # # # # ## ## ## #
|
||||
## # ## # ## # # # # ## # # # # # # # # # # # ## #
|
||||
# # ## # # # ### # ## # ## # # ### # # # # ### #
|
||||
# # ## # ## #### # # # # # # # ## ## # ## ###
|
||||
# ## # # ## ## # # ## # # # ### # ## # # # # # # # #
|
||||
# # ## # # ## # # # # # # # # # # # ## # ### ##
|
||||
# ## # # # # # # # ## # # ## ## ## # # ## ## # # ## ### ### ####
|
||||
### # # # # # # # ## # # # ## # ## # # # ## # # ## # # # #
|
||||
# # # # # # # # ## ## ### # # # # # # ## # # # #
|
||||
# # # # ## # ### # # # # ## # # # ### # ## ## # # # ##
|
||||
# # # # # ## # ## # # # ### # ## ## # # # # # # # #
|
||||
## ### ## # # # # ## # # # #### # #### # # ## # ## #
|
||||
## ## ## # # # # ## # # ## ## ### # # # # # ### # ### ##
|
||||
# # ### # # # # # # # # # ## # ### # # # ### ## ##
|
||||
# # ## # ## # ## ## # # # ## ## # ## # # ##
|
||||
# # ### # ## ## # # ### # # # # # # ## ## # ##
|
||||
# # #### # # # # # # # ### # # # # # # ## # ### # # ### ###
|
||||
# # ## # # ##### # ## # # ## ## # # # ## # # # ## ##
|
||||
# ### # ## # # ###### ### # ## # ## # # ## # # # # ## ## # ## #
|
||||
# # # # # # # # ## ## # # # ## # # ## ## # # # # #
|
||||
## # ## ## # ### # # # # # # # # ## # # # # # ###### # ##
|
||||
## # # # # ### # # ### ## # # ## # # # # ##### #
|
||||
# # ### # # # # # # ## #### # # ### # # # ## # ##
|
||||
# # ## ## # ## # #### # ## # # # # # ## ## # # # # ## ## #
|
||||
## # # # # ## # # ## # # # ## # # ## # # # # # #
|
||||
# # # ## # # # # ## # ## # # # # # ## # # ##
|
||||
# ## ## # # # # # # ### # ## # # # # # # # # #
|
||||
# # ## # # # # # # # ##### ## ## ### # # ###
|
||||
# # # # # # ## ## ## # # # # # # ## # ##### # ##
|
||||
# # ## # # # ## # # #### # ## # # # # # ## # # #
|
||||
# # # # # ## ## # ## # # # # # #### # ##
|
||||
## # # # # ## # ## ## ## # # ## # # # ## # ## # # #
|
||||
## # # # # # # # ## ### # # # ## # # ## #
|
||||
### # ## # # # ## ## # ### # # # # # ### # # # ##### #
|
||||
## # # ## # ## # # # # # ## # # # ## ####### ### # #
|
||||
#### # # # # # # # # # # ## # ## # # ### # ## # # #
|
||||
# # # # # # # # # # ## # # ## # # # # ## # ### # #
|
||||
# # # # #### ## ## # # # # ## # # # # # # ### ### # ##
|
||||
#### # ## # # # ### ## # ## ## # ## # # ## # #
|
||||
# # ## # # # # # # # # ## # # ## # # ### # ##
|
||||
# # # # ## ## # # ## # # # # ## # ## ##
|
||||
### ## # # # ## ## ## ## # # # ## ## # # # # # # # # # ## # # #
|
||||
## # # # # # # # # # # ## #### # # ## ### ### ## # # #
|
||||
# # ##### # # # ## ## # # ## ## # # ## # #### ##### # # ## ##
|
||||
# # # # # # ## # # # # # # # # # # ## #
|
||||
## ### # # ## ## # ## ## ## # # ## # # ### # # ## ### #
|
||||
# # # ## # ## # # # ## # # # # ## # # # #
|
||||
# # # # # #### # # # ## # # # ## # # # # # # # # # #
|
||||
# # # ## # # ## # # ### # # ## # # ## # # ##
|
||||
# # # ## # # ### # # # # # ## ## ##
|
||||
# # # # ### # # # # # # # # # # # ## ## # ### # ## # # # #
|
||||
# ###### # # ## ## ## # ### # # # ## # # # #####
|
||||
# ## # # # # ## # # # # # # # # #### # # e
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
s # ## # # ### # ## # # #
|
||||
## # # ## ## # # # #
|
||||
# # ## # # # # ##
|
||||
### # # # # # # ## ## # ## # #
|
||||
# # # ## # # # # ## # #
|
||||
# # # # ## # ## # # #
|
||||
## # # # # # # # ## # #
|
||||
# ## # # # ## # ## # # # # #
|
||||
## # # # # ## # # ## # ##
|
||||
# # # # # ## # # ## # # #
|
||||
# # # # ## # # # # ## # ## # #
|
||||
# ## # # # # # # # ## ##
|
||||
## # ## ### # # # ## # ##
|
||||
##### ### # # # # ## # # # #
|
||||
# # ### ## # ## ## #### ###
|
||||
## # # # # ### # # ## # #
|
||||
# # ## # # # # # # ##
|
||||
## # # # ### # ## # # ## # # ## ##
|
||||
# #### # # # # # ### # ##
|
||||
# ## # ## # # ## ### ## ### #
|
||||
# # ### ## # # # ##
|
||||
# # ## # # # # # # #
|
||||
# ## # ### #### # ## # ### ## # #
|
||||
# # ## # # # # # # #
|
||||
# # ##### # # # # # # # ## # ##
|
||||
## # # # # ## ## # ## ## #
|
||||
# # # # # # # ## # # #
|
||||
## # # # ## # # ## # #
|
||||
# ### # # # # # # # # # ###
|
||||
### # # # # # ### # # # # # ##
|
||||
# # # # # ## # # # # # ##
|
||||
# ## ## ## # # # # # # ## #
|
||||
# #### # # # ## # ## #
|
||||
## # # # # ## # # # # #
|
||||
## # ## ## # # # ## # # ## #
|
||||
# # # # # # # # # # ### # # #
|
||||
# # ## # # # # # ###
|
||||
# # #### ##
|
||||
# # ## # # ## ### # # ##
|
||||
##### # # # # # # # # # #
|
||||
## # # # # # #
|
||||
# # ## ## # # # # ## ### # #
|
||||
# # ### ## ### ### # ## # #
|
||||
## # ### # ## # # # #
|
||||
# # # # # ## # # # # #
|
||||
# # ## # # ## ### # # # #
|
||||
# # # # # ## # ### #
|
||||
## # # ## # # #
|
||||
# # ## # ### # ### # ## # ## # ##
|
||||
# # # # # # # ## # # e
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
s ## ###
|
||||
# # # # # ##
|
||||
# # # # #
|
||||
# # ##
|
||||
# # # # #
|
||||
# # ### # #
|
||||
# # # # #
|
||||
# # ## ## ###
|
||||
# ## #
|
||||
# # ###
|
||||
# # # # #
|
||||
### # #
|
||||
# # # #
|
||||
## # # # #
|
||||
## # # # # ##
|
||||
# # #
|
||||
# #
|
||||
# # # #
|
||||
# # #
|
||||
# # # # ## #
|
||||
|
Before Width: | Height: | Size: 101 KiB |
|
|
@ -1,10 +0,0 @@
|
|||
s #
|
||||
|
||||
#
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
#
|
||||
# #
|
||||
#
|
||||
# # e
|
||||
|
Before Width: | Height: | Size: 103 KiB |
|
|
@ -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 |
|
||||
|
||||
### Графики
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Анализ эффективности алгоритмов
|
||||
|
||||
В ходе экспериментов были получены следующие результаты.
|
||||
|
||||
### BFS
|
||||
Преимущества:
|
||||
всегда находит кратчайший путь;
|
||||
простая реализация.
|
||||
Недостатки:
|
||||
посещает большое количество клеток;
|
||||
требует много памяти.
|
||||
Выходит, что наиболее эффективен в небольших невзвешенных лабиринтах.
|
||||
|
||||
### DFS
|
||||
Преимущества:
|
||||
простая реализация;
|
||||
самым быстрым находит произвольный путь.
|
||||
Недостатки:
|
||||
не гарантирует кратчайший путь;
|
||||
может уходить в тупики.
|
||||
Подходит для быстрого поиска любого решения.
|
||||
|
||||
### A
|
||||
Преимущества:
|
||||
высокая скорость;
|
||||
посещает меньше клеток;
|
||||
Недостатки:
|
||||
требует выбора хорошей эвристики.
|
||||
Показал хорошие результаты на больших лабиринтах.
|
||||
|
||||
## Анализ применимости паттернов
|
||||
|
||||
### Builder
|
||||
Без Builder код загрузки лабиринта был бы жёстко связан с классом Maze, а добавление нового формата потребовало бы изменения существующего кода.
|
||||
Strategy
|
||||
Без Strategy пришлось бы:
|
||||
хранить все алгоритмы внутри одного класса;
|
||||
использовать большое количество условных операторов;
|
||||
изменять код MazeSolver при добавлении новых алгоритмов
|
||||
Strategy помог полностью отделить алгоритмы друг от друга.
|
||||
|
||||
### Observer
|
||||
Без Observer логика интерфейса смешивалась бы с логикой поиска.
|
||||
Это усложнило бы:
|
||||
добавление GUI;
|
||||
логирование;
|
||||
визуализацию.
|
||||
|
||||
### Command
|
||||
Без Command было бы сложно реализовать:
|
||||
undo;
|
||||
историю действий;
|
||||
расширяемую систему управления.
|
||||
|
||||
## Выводы
|
||||
|
||||
### В проекте были успешно реализованы:
|
||||
загрузка лабиринта из файла;
|
||||
несколько алгоритмов поиска пути;
|
||||
визуализация;
|
||||
система наблюдателей;
|
||||
система команд;
|
||||
экспериментальное сравнение алгоритмов.
|
||||
|
||||
### Использование паттернов GoF позволило:
|
||||
сделать архитектуру гибкой;
|
||||
уменьшить связанность компонентов;
|
||||
упростить расширение программы;
|
||||
облегчить сопровождение кода.
|
||||
|
|
@ -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,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
|
||||
|
|
@ -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 с результатами
|
||||
- Диаграммы
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
# Диаграммы проекта
|
||||
|
||||
## 1. Диаграмма классов
|
||||
|
||||
См. файл `class_diagram.mmd`.
|
||||
|
||||
## 2. Структура каталогов
|
||||
|
||||
```
|
||||
vinichukan/
|
||||
├── src/
|
||||
├── mazes/
|
||||
├── experiments/
|
||||
└── docs/
|
||||
```
|
||||
|
||||
## 3. Логика работы алгоритмов
|
||||
|
||||
- BFS — поиск в ширину
|
||||
- DFS — поиск в глубину
|
||||
- A\* — эвристический поиск с манхэттенской метрикой
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 37 KiB |
|
|
@ -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,13 +0,0 @@
|
|||
####################################################################################################
|
||||
#S # ########### # # ######### # #
|
||||
# ####### ######### ########### ###### ######## ######## ######## ######## ######## ########## #####
|
||||
# # # # # # # # # # # #
|
||||
######## ######### ######### ######## ######## ######## ######## ######## ######## ######## #######
|
||||
# # # # # # # # # # # # #
|
||||
# ######## ##### # # ##### ######## ######## ######## ######## ######## ######## ######## #########
|
||||
# # # # # # # # # # # # # #
|
||||
######## ####### # ####### ######## ######## ######## ######## ######## ######## ######## #########
|
||||
# # # # # # # # # # # #
|
||||
# #### ######## ######## ######## ######## ######## ######## ######## ######## ######## ###########
|
||||
# # # # # # # # # # # E#
|
||||
####################################################################################################
|
||||
|
|
@ -1 +0,0 @@
|
|||
S E
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
###############
|
||||
#S # E#
|
||||
# ### ####### #
|
||||
# #
|
||||
###############
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#######
|
||||
#S #
|
||||
#######
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
##########
|
||||
#S E#
|
||||
##########
|
||||
|
|
@ -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
|
||||
|
|
@ -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_)
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
@ -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})"
|
||||
|
|
@ -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))
|
||||
|
|
@ -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))
|
||||
|
|
@ -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))
|
||||
|
|
@ -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
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
from abc import ABC, abstractmethod
|
||||
|
||||
class Command(ABC):
|
||||
@abstractmethod
|
||||
def execute(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def undo(self):
|
||||
pass
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
from abc import ABC, abstractmethod
|
||||
|
||||
class Observer(ABC):
|
||||
@abstractmethod
|
||||
def update(self, event: str):
|
||||
pass
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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,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()
|
||||
|
|
@ -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,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!")
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
##########
|
||||
# S#
|
||||
# #
|
||||
# #####
|
||||
# E#
|
||||
##########
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
##########
|
||||
#S #### E#
|
||||
## #### ##
|
||||
# ##
|
||||
## ###
|
||||
## #######
|
||||
##########
|
||||
##########
|
||||
##########
|
||||
##########
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
####################
|
||||
#S ############
|
||||
# ############
|
||||
# E ##############
|
||||
# #################
|
||||
# ################
|
||||
## ###############
|
||||
# ###############
|
||||
# #################
|
||||
# #################
|
||||
# #################
|
||||
####################
|
||||
####################
|
||||
####################
|
||||
####################
|
||||
####################
|
||||
####################
|
||||
####################
|
||||
####################
|
||||
####################
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
S
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
E
|
||||
|
|
@ -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)"
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
S
|
||||
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 82 KiB |
|
|
@ -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")
|
||||
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 81 KiB |
|
|
@ -1,60 +0,0 @@
|
|||
# Отчёт по лабораторной работе "Структуры данных"
|
||||
|
||||
## 1. Введение
|
||||
В рамках работы были реализованы три структуры данных для хранения телефонного справочника: связный список, хеш-таблица и двоичное дерево поиска. Проведено экспериментальное сравнение производительности операций вставки, поиска и удаления на наборе из **10 000 записей**. Для каждой структуры тестирование выполнялось на двух вариантах входных данных: случайный порядок и отсортированный по имени. Каждый эксперимент повторялся 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 |
|
||||
|
||||
Графическое представление результатов приведено на рисунке ниже.
|
||||
|
||||

|
||||
|
||||
## 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.000085–0.00014 с) значительно меньше, чем в списке.
|
||||
- **BST**: на случайных данных удаление очень быстрое (0.000053 с) благодаря логарифмической высоте. На отсортированных данных время возрастает до 0.00268 с (в 50 раз), что отражает деградацию до O(n).
|
||||
|
||||
## 4. Выводы и рекомендации по выбору структуры
|
||||
|
||||
На основе полученных результатов можно сформулировать следующие рекомендации:
|
||||
|
||||
- **Хеш-таблица** – оптимальный выбор, если требуется максимальная скорость поиска, вставки и удаления, а порядок хранения не важен. Примеры: реализация словарей, кэшей, индексов по ключу. В эксперименте хеш-таблица показала стабильно высокую производительность во всех режимах.
|
||||
|
||||
- **Двоичное дерево поиска** – следует применять, когда необходимо получать данные в отсортированном порядке (например, вывод телефонного справочника по алфавиту). Однако важно учитывать, что при поступлении отсортированных данных дерево вырождается, и производительность резко падает. В таких случаях лучше использовать сбалансированные деревья (AVL, красно-чёрные). В эксперименте BST на случайных данных показал отличные результаты, близкие к хеш-таблице, а на отсортированных – стал самым медленным.
|
||||
|
||||
- **Связный список** – практически непригоден для больших объёмов данных из-за линейной сложности основных операций. Может использоваться лишь для очень маленьких коллекций, при частых вставках в начало списка (здесь не рассматривалось) или в учебных целях.
|
||||
|
||||
Таким образом, для реальных задач чаще всего выбирают хеш-таблицы или сбалансированные деревья в зависимости от требований к упорядоченности данных.
|
||||
|
||||
|
||||
I use arch BTW
|
||||
|
|
@ -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 |
|
||||
|
||||
### Графики
|
||||
|
||||

|
||||
|
||||
На графике представлено сравнение трех алгоритмов по трем метрикам: время выполнения, количество посещенных клеток и длина найденного пути.
|
||||
|
||||
## 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* является наиболее сбалансированным алгоритмом для поиска пути в лабиринте, обеспечивая оптимальный путь при приемлемой скорости работы.
|
||||
|
|
@ -1 +0,0 @@
|
|||
hi
|
||||
|
|
@ -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()
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
ОТЧЁТ ПО ЗАДАНИЮ 1
|
||||
|
||||
1. Влияние порядка данных на BST
|
||||
При случайном порядке данных BST работает быстро (вставка ~0.005 сек).
|
||||
При отсортированном порядке дерево вырождается в цепочку, и время вставки
|
||||
возрастает примерно в 50–60 раз (~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 имеет смысл использовать только при случайном порядке данных и
|
||||
необходимости частого получения отсортированного списка.
|
||||
|
|
@ -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()
|
||||
|
|
@ -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 сделало систему легко расширяемой и удобной для проведения экспериментов.
|
||||
|
|
@ -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
|
||||
|
|
Before Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 75 KiB |
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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']]
|
||||
|
|
@ -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)
|
||||
|
|
@ -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,10 +0,0 @@
|
|||
##########
|
||||
#S# #
|
||||
# # #### #
|
||||
# # # #
|
||||
### # ## #
|
||||
# # #
|
||||
# ##### ##
|
||||
# # #
|
||||
##### ##E#
|
||||
##########
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
###################################################
|
||||
#S# # # # #
|
||||
# # ##### # ######### # ##### # ################# #
|
||||
# # # # # # # # # #
|
||||
##### # ########### # ### # ############# ####### #
|
||||
# # # # # # # # #
|
||||
# ##### # ############# # # # ############# ##### #
|
||||
# # # # # # # # # # #
|
||||
# # ##### # ############# # # # ######### ##### # #
|
||||
# # # # # # # # # # # # #
|
||||
# # # ##### # ######### # # ##### ##### ##### # # #
|
||||
# # # # # # # # # # # # #
|
||||
# # ######### # ##### ########### # # # # ##### # #
|
||||
# # # # # # # # # # #
|
||||
# ############# ### # # ########### ##### # ##### #
|
||||
# # # # # # #
|
||||
############### # ### ########### ############### #
|
||||
# # # # # # #
|
||||
# ############# ### # # ##### # ######### # ##### #
|
||||
# # # # # # # # # # # # # #
|
||||
# # ############# ### # # # # # ####### # # # # # #
|
||||
# # # # # # # # # # # #
|
||||
# # ############### # ####### ####### # # ####### #
|
||||
# # # # # # # # # #
|
||||
# # # ############# ####### ####### # # ####### # #
|
||||
# # # # # # # # # # # #
|
||||
# # # # ######### ####### # # ##### # ####### # # #
|
||||
# # # # # # # # # # # # # #
|
||||
##### # # ##### ####### # ##### # # # # ### # #####
|
||||
# # # # # # # # # # # # #
|
||||
# ##### ##### # # ############# # # # ### # ##### #
|
||||
# # # # # # # # # # # # #
|
||||
##### ##### # # # # ############# # ### # ##### # #
|
||||
# # # # # # # # # # # # #
|
||||
# # ##### # # # # ################### # ### # ### #
|
||||
# # # # # # # # # # # # # #
|
||||
# ##### # # # # # # ############### # ### # ### # #
|
||||
# # # # # # # # # # # # #
|
||||
##### # ######### ############### # ### # # ### # #
|
||||
# # # # # # # # #
|
||||
# ############# ##################### ####### # # #
|
||||
# # # # #
|
||||
############# ######################### ###########
|
||||
# # # #
|
||||
# ########### # ####################### # ####### #
|
||||
# # # # # # #
|
||||
########### # # # ####################### ####### #
|
||||
# # # # #
|
||||
# ########### ################################### #
|
||||
# E#
|
||||
###################################################
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
#####################################################################################################
|
||||
#S# # # # # #
|
||||
# # ##### # ################# # ######### # ################# # ################################### #
|
||||
# # # # # # # # # # # # # #
|
||||
##### # ############# ####### # # ##### # # # ############# # # # ################################# #
|
||||
# # # # # # # # # # # # # # # # #
|
||||
# ##### # ############# ####### # # # # # # # # ############# # # # ############################### #
|
||||
# # # # # # # # # # # # # # # # # #
|
||||
# # ##### # ######### ########### # # # # # # ############### # # # # ############################# #
|
||||
# # # # # # # # # # # # # # # #
|
||||
# # # ##### # ##### ############### ##### ################# # # # # # ############################# #
|
||||
# # # # # # # # # # #
|
||||
# # ############# ######################### ############### # ##### # ############################# #
|
||||
# # # # # # # # #
|
||||
# ############### # ####################### # ############# # # ##### # ########################### #
|
||||
# # # # # # # # # # # #
|
||||
############### # # # ##################### # # ############# # # ##### # ######################### #
|
||||
# # # # # # # # # # # # # #
|
||||
# ############# # # # # ################### # # # ############# # # ##### # ####################### #
|
||||
# # # # # # # # # # # # # # # # #
|
||||
# # ############# # # # # ################# # # # # ############# # ##### # # ##################### #
|
||||
# # # # # # # # # # # # # # # # # #
|
||||
# # # ############# # # # # ############### # # # ############### # ##### # # # ################### #
|
||||
# # # # # # # # # # # # # # # #
|
||||
# # # # ############# ##################### # # ######################### # # # # ################# #
|
||||
# # # # # # # # # # # # #
|
||||
# # # # # ################################# # ########################### # # # # # ############### #
|
||||
# # # # # # # # # # #
|
||||
# # # ####### ############################### ############################# ####### ############### #
|
||||
# # # # # # # #
|
||||
# # ######### # ############################# # ########################### ####### # ############# #
|
||||
# # # # # # # # # #
|
||||
# ########### # # ########################### # # ######################### ####### # ############# #
|
||||
# # # # # # # # # #
|
||||
############# # # # ######################### # ########################### # ##### # ############# #
|
||||
# # # # # # # # # # #
|
||||
# ########### # # # # ####################### ############################# # # ##### # #############
|
||||
# # # # # # # # # # # # #
|
||||
# # ######### # # # # # ##################### # ########################### # # # ##### ########### #
|
||||
# # # # # # # # # # # # # # # # #
|
||||
# # # ####### # # # # # # ################### # # ######################### # # # # ##### ######### #
|
||||
# # # # # # # # # # # # # # # # # # #
|
||||
# # # # ##### # ##### # # # ################# # ########################### # # # # # ##### ####### #
|
||||
# # # # # # # # # # # # # # # # # # #
|
||||
# ##### # # # # # ##### # # # ############### ############################# ####### # # ##### ##### #
|
||||
# # # # # # # # # # # # # # # # # # #
|
||||
# # ##### # # # # # ##### # # # ############################################# ####### # # # # # ### #
|
||||
# # # # # # # # # # # # # # # #
|
||||
# ######### ####### ########### # ############################################# ####### ### # ### # #
|
||||
# # # # # # # # # # #
|
||||
########### # ####### ########### # ########################################### ######### # ### # # #
|
||||
# # # # # # # # # # # #
|
||||
# ########### # ##### # ########### # ######################################### # ######### ### # # #
|
||||
# # # # # # # # # # # # # # # #
|
||||
# # ######### # # # # # # ######### # ######################################### # # ##### ### # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # #
|
||||
# # # ####### # # # # # # # ####### # ######################################### # # # # ### # # # # #
|
||||
# # # # # # # # # # # # # # # # # # #
|
||||
# ##### ####### ######### # # ##### ########################################### # # # ### # ### # # #
|
||||
# # # # # # # # # # # # # # #
|
||||
####### # ############### # # ##### # ######################################### # # ### # ### # # # #
|
||||
# # # # # # # # # # # # # # # #
|
||||
# ####### # ############# # ### # ### # ######################################### ### # ### # ### # #
|
||||
# # # # # # # # # # # # # # # # #
|
||||
# # ##### # ############### ### # # ### # ######################################### # # ### # ### # #
|
||||
# # # # # # # # # # # # # # # # #
|
||||
# # # # ##### ############### # # ##### # # ####################################### # # ### # ### # #
|
||||
# # # # # # # # # # # # # # # # #
|
||||
# # # ######### ############# # # # ##### # # ##################################### ##### # ### # # #
|
||||
# # # # # # # # # # # # # # #
|
||||
# # ############# ############### # # ##### # # ################################### # ##### ### # # #
|
||||
# # # # # # # # # # # # # #
|
||||
# ############### # ############# # # # ##### # # ################################# # # ##### ##### #
|
||||
# # # # # # # # # # # # # # #
|
||||
# ################# # ########### # # # # ##### # # ############################### # # # ##### ### #
|
||||
# # # # # # # # # # # # # # # # #
|
||||
# # ############### # # ######### # ##### # ##### # # ############################# # # # # ##### # #
|
||||
# # # # # # # # # # # # # # # # # #
|
||||
# # # ############# # # # ####### ######### # ##### # # ########################### ####### # ##### #
|
||||
# # # # # # # # # # # # # # # # # #
|
||||
# # # # ############# # # # ##### # ####### # # ##### # # ######################### # ####### # #####
|
||||
# # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # # # # ########### # # # # # # # # ##### # # # ##### # # ####################### # # ##### # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
|
||||
# # ##### # ######### # ##### # # # # # # # # # # # ##### # # ##################### # # # # # # # # #
|
||||
# # # # # # # # # # # # # # # # # # # # #
|
||||
# ######### # ######### ############### ######### # ##### # # ##################### ##### # ##### # #
|
||||
# # # # # # # # # # # # # # # #
|
||||
########### # ######### # ##################### # # # ##### # # ################### # ##### # # # # #
|
||||
# # # # # # # # # # # # # # # # # #
|
||||
# ####################### # ################### # # # # ##### # # ################# # # ##### # # # #
|
||||
# # # # # # # # # # # # # # # # #
|
||||
# # ##################### # # ################# ##### # # ##### # # ############### # # # ##### ### #
|
||||
# # # # # # # # # # # # # # # # # # #
|
||||
# # # ################### # # # ############### # ##### # # ##### # # ############# # # # # ##### # #
|
||||
# # # # # # # # # # # # # # # # E
|
||||
#####################################################################################################
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
S
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
E
|
||||