Merge pull request '[1,2]newlaba2' (#304) from ivanchenkoam/2026-rff_mp:newlaba2 into develop
Reviewed-on: #304
This commit is contained in:
commit
44919e72ee
537
ivanchenkoam/laba1.txt
Normal file
537
ivanchenkoam/laba1.txt
Normal file
|
|
@ -0,0 +1,537 @@
|
|||
import time
|
||||
import csv
|
||||
import random
|
||||
import sys
|
||||
from typing import List, Tuple, Optional, Any, Dict
|
||||
|
||||
#лимит рекурсии
|
||||
sys.setrecursionlimit(20000)
|
||||
def ll_insert(head: Optional[Dict], name: str, phone: str) -> Dict:
|
||||
"""Вставка в конец связного списка"""
|
||||
new_node = {'name': name, 'phone': phone, 'next': None}
|
||||
|
||||
if head is None:
|
||||
return new_node
|
||||
|
||||
current = head
|
||||
while current['next'] is not None:
|
||||
# Обновляем, если уже есть
|
||||
if current['name'] == name:
|
||||
current['phone'] = phone
|
||||
return head
|
||||
current = current['next']
|
||||
|
||||
if current['name'] == name:
|
||||
current['phone'] = phone
|
||||
else:
|
||||
current['next'] = new_node
|
||||
|
||||
return head
|
||||
|
||||
|
||||
def ll_find(head: Optional[Dict], name: str) -> Optional[str]:
|
||||
"""Поиск в связном списке"""
|
||||
current = head
|
||||
while current is not None:
|
||||
if current['name'] == name:
|
||||
return current['phone']
|
||||
current = current['next']
|
||||
return None
|
||||
|
||||
|
||||
def ll_delete(head: Optional[Dict], name: str) -> Optional[Dict]:
|
||||
"""Удаление из связного списка"""
|
||||
if head is None:
|
||||
return None
|
||||
|
||||
if head['name'] == name:
|
||||
return head['next']
|
||||
|
||||
current = head
|
||||
while current['next'] is not None:
|
||||
if current['next']['name'] == name:
|
||||
current['next'] = current['next']['next']
|
||||
return head
|
||||
current = current['next']
|
||||
|
||||
return head
|
||||
|
||||
|
||||
def ll_list_all(head: Optional[Dict]) -> List[Tuple[str, str]]:
|
||||
"""Сбор всех записей из связного списка с сортировкой"""
|
||||
records = []
|
||||
current = head
|
||||
while current is not None:
|
||||
records.append((current['name'], current['phone']))
|
||||
current = current['next']
|
||||
def hash_function(name: str, size: int) -> int:
|
||||
"""Простая хеш-функция"""
|
||||
return sum(ord(c) for c in name) % size
|
||||
|
||||
|
||||
def ht_create(size: int = 1000) -> List[Optional[Dict]]:
|
||||
"""Создание хеш-таблицы"""
|
||||
return [None] * size
|
||||
|
||||
|
||||
def ht_insert(buckets: List[Optional[Dict]], name: str, phone: str) -> None:
|
||||
"""Вставка в хеш-таблицу"""
|
||||
index = hash_function(name, len(buckets))
|
||||
buckets[index] = ll_insert(buckets[index], name, phone)
|
||||
|
||||
|
||||
def ht_find(buckets: List[Optional[Dict]], name: str) -> Optional[str]:
|
||||
"""Поиск в хеш-таблице"""
|
||||
index = hash_function(name, len(buckets))
|
||||
return ll_find(buckets[index], name)
|
||||
|
||||
|
||||
def ht_delete(buckets: List[Optional[Dict]], name: str) -> None:
|
||||
"""Удаление из хеш-таблицы"""
|
||||
index = hash_function(name, len(buckets))
|
||||
buckets[index] = ll_delete(buckets[index], name)
|
||||
|
||||
|
||||
def ht_list_all(buckets: List[Optional[Dict]]) -> List[Tuple[str, str]]:
|
||||
"""Сбор всех записей из хеш-таблицы с сортировкой"""
|
||||
records = []
|
||||
for head in buckets:
|
||||
current = head
|
||||
while current is not None:
|
||||
records.append((current['name'], current['phone']))
|
||||
current = current['next']
|
||||
|
||||
records.sort(key=lambda x: x[0])
|
||||
return records
|
||||
def bst_insert(root: Optional[Dict], name: str, phone: str) -> Dict:
|
||||
"""Вставка в BST (итеративная)"""
|
||||
new_node = {'name': name, 'phone': phone, 'left': None, 'right': None}
|
||||
|
||||
if root is None:
|
||||
return new_node
|
||||
|
||||
current = root
|
||||
while True:
|
||||
if name < current['name']:
|
||||
if current['left'] is None:
|
||||
current['left'] = new_node
|
||||
break
|
||||
else:
|
||||
current = current['left']
|
||||
elif name > current['name']:
|
||||
if current['right'] is None:
|
||||
current['right'] = new_node
|
||||
break
|
||||
else:
|
||||
current = current['right']
|
||||
else:
|
||||
# Обновляем существующую запись
|
||||
current['phone'] = phone
|
||||
break
|
||||
|
||||
return root
|
||||
|
||||
|
||||
def bst_find(root: Optional[Dict], name: str) -> Optional[str]:
|
||||
"""Поиск в BST (итеративный)"""
|
||||
current = root
|
||||
while current is not None:
|
||||
if name == current['name']:
|
||||
return current['phone']
|
||||
elif name < current['name']:
|
||||
current = current['left']
|
||||
else:
|
||||
current = current['right']
|
||||
return None
|
||||
|
||||
|
||||
def bst_min_node(node: Dict) -> Dict:
|
||||
"""Поиск узла с минимальным значением"""
|
||||
current = node
|
||||
while current['left'] is not None:
|
||||
current = current['left']
|
||||
return current
|
||||
|
||||
|
||||
def bst_delete(root: Optional[Dict], name: str) -> Optional[Dict]:
|
||||
"""Удаление из BST (итеративная версия)"""
|
||||
if root is None:
|
||||
return None
|
||||
|
||||
# Поиск узла для удаления и его родителя
|
||||
parent = None
|
||||
current = root
|
||||
|
||||
while current is not None and current['name'] != name:
|
||||
parent = current
|
||||
if name < current['name']:
|
||||
current = current['left']
|
||||
else:
|
||||
current = current['right']
|
||||
|
||||
if current is None:
|
||||
return root
|
||||
|
||||
# Случай 1: узел не имеет детей
|
||||
if current['left'] is None and current['right'] is None:
|
||||
if parent is None:
|
||||
return None
|
||||
if parent['left'] == current:
|
||||
parent['left'] = None
|
||||
else:
|
||||
parent['right'] = None
|
||||
return root
|
||||
|
||||
# Случай 2: узел имеет одного ребёнка
|
||||
if current['left'] is None:
|
||||
child = current['right']
|
||||
elif current['right'] is None:
|
||||
child = current['left']
|
||||
else:
|
||||
# Случай 3: узел имеет двух детей
|
||||
# Находим минимальный узел в правом поддереве
|
||||
successor_parent = current
|
||||
successor = current['right']
|
||||
while successor['left'] is not None:
|
||||
successor_parent = successor
|
||||
successor = successor['left']
|
||||
|
||||
# Копируем данные из successor в current
|
||||
current['name'] = successor['name']
|
||||
current['phone'] = successor['phone']
|
||||
|
||||
# Удаляем successor
|
||||
if successor_parent['left'] == successor:
|
||||
successor_parent['left'] = successor['right']
|
||||
else:
|
||||
successor_parent['right'] = successor['right']
|
||||
|
||||
return root
|
||||
|
||||
# Присоединяем ребёнка к родителю
|
||||
if parent is None:
|
||||
return child
|
||||
if parent['left'] == current:
|
||||
parent['left'] = child
|
||||
else:
|
||||
parent['right'] = child
|
||||
|
||||
return root
|
||||
|
||||
|
||||
def bst_inorder(root: Optional[Dict], records: List[Tuple[str, str]]) -> None:
|
||||
"""Центрированный обход BST (рекурсивный)"""
|
||||
if root is not None:
|
||||
bst_inorder(root['left'], records)
|
||||
records.append((root['name'], root['phone']))
|
||||
bst_inorder(root['right'], records)
|
||||
|
||||
|
||||
def bst_list_all(root: Optional[Dict]) -> List[Tuple[str, str]]:
|
||||
"""Сбор всех записей из BST (уже отсортированы)"""
|
||||
records = []
|
||||
bst_inorder(root, records)
|
||||
return records
|
||||
|
||||
|
||||
|
||||
records.sort(key=lambda x: x[0])
|
||||
return records
|
||||
|
||||
def generate_records(n: int) -> List[Tuple[str, str]]:
|
||||
"""Генерация записей"""
|
||||
records = [(f"User_{i:05d}", f"+7-999-{i:07d}") for i in range(n)]
|
||||
return records
|
||||
def measure_insertion(structure_type: str, data: List[Tuple[str, str]],
|
||||
ht_size: int = 1000) -> float:
|
||||
"""Замер времени вставки"""
|
||||
start = time.perf_counter()
|
||||
|
||||
if structure_type == "LinkedList":
|
||||
head = None
|
||||
for name, phone in data:
|
||||
head = ll_insert(head, name, phone)
|
||||
|
||||
elif structure_type == "HashTable":
|
||||
buckets = ht_create(ht_size)
|
||||
for name, phone in data:
|
||||
ht_insert(buckets, name, phone)
|
||||
|
||||
elif structure_type == "BST":
|
||||
root = None
|
||||
for name, phone in data:
|
||||
root = bst_insert(root, name, phone)
|
||||
|
||||
end = time.perf_counter()
|
||||
return end - start
|
||||
|
||||
|
||||
def measure_find(structure_type: str, data: List[Tuple[str, str]],
|
||||
existing_names: List[str], missing_names: List[str],
|
||||
ht_size: int = 1000) -> Tuple[float, Any]:
|
||||
"""Замер времени поиска (возвращает время и структуру для удаления)"""
|
||||
# Сначала создаём структуру
|
||||
if structure_type == "LinkedList":
|
||||
head = None
|
||||
for name, phone in data:
|
||||
head = ll_insert(head, name, phone)
|
||||
|
||||
start = time.perf_counter()
|
||||
for name in existing_names + missing_names:
|
||||
ll_find(head, name)
|
||||
end = time.perf_counter()
|
||||
return end - start, head
|
||||
|
||||
elif structure_type == "HashTable":
|
||||
buckets = ht_create(ht_size)
|
||||
for name, phone in data:
|
||||
ht_insert(buckets, name, phone)
|
||||
|
||||
start = time.perf_counter()
|
||||
for name in existing_names + missing_names:
|
||||
ht_find(buckets, name)
|
||||
end = time.perf_counter()
|
||||
return end - start, buckets
|
||||
|
||||
elif structure_type == "BST":
|
||||
root = None
|
||||
for name, phone in data:
|
||||
root = bst_insert(root, name, phone)
|
||||
|
||||
start = time.perf_counter()
|
||||
for name in existing_names + missing_names:
|
||||
bst_find(root, name)
|
||||
end = time.perf_counter()
|
||||
return end - start, root
|
||||
|
||||
|
||||
def measure_delete(structure_type: str, structure: Any,
|
||||
names_to_delete: List[str]) -> float:
|
||||
"""Замер времени удаления"""
|
||||
start = time.perf_counter()
|
||||
|
||||
if structure_type == "LinkedList":
|
||||
head = structure
|
||||
for name in names_to_delete:
|
||||
head = ll_delete(head, name)
|
||||
|
||||
elif structure_type == "HashTable":
|
||||
buckets = structure
|
||||
for name in names_to_delete:
|
||||
ht_delete(buckets, name)
|
||||
|
||||
elif structure_type == "BST":
|
||||
root = structure
|
||||
for name in names_to_delete:
|
||||
root = bst_delete(root, name)
|
||||
|
||||
end = time.perf_counter()
|
||||
return end - start
|
||||
def run_experiment(n_records: int = 10000, n_find: int = 110,
|
||||
n_delete: int = 50, n_runs: int = 5) -> List[List]:
|
||||
"""Запуск всех замеров"""
|
||||
|
||||
# Генерация данных
|
||||
all_records = generate_records(n_records)
|
||||
records_shuffled = all_records.copy()
|
||||
random.shuffle(records_shuffled)
|
||||
records_sorted = sorted(all_records, key=lambda x: x[0])
|
||||
|
||||
# Имена для поиска
|
||||
all_names = [name for name, _ in all_records]
|
||||
existing_names = random.sample(all_names, n_find - 10)
|
||||
missing_names = [f"None_{i}" for i in range(10)]
|
||||
|
||||
# Имена для удаления
|
||||
names_to_delete = random.sample(all_names, n_delete)
|
||||
|
||||
structures = ["LinkedList", "HashTable", "BST"]
|
||||
modes = {"shuffled": records_shuffled, "sorted": records_sorted}
|
||||
|
||||
# Заголовок CSV
|
||||
results = [["Структура", "Режим", "Операция",
|
||||
"Замер1", "Замер2", "Замер3", "Замер4", "Замер5", "Среднее"]]
|
||||
|
||||
for structure in structures:
|
||||
for mode_name, mode_data in modes.items():
|
||||
print(f"\nТестирование: {structure}, режим: {mode_name}")
|
||||
|
||||
# Вставка
|
||||
insertion_times = []
|
||||
for run in range(n_runs):
|
||||
print(f" Вставка, run {run+1}/{n_runs}...")
|
||||
t = measure_insertion(structure, mode_data)
|
||||
insertion_times.append(t)
|
||||
|
||||
avg_insertion = sum(insertion_times) / n_runs
|
||||
results.append([structure, mode_name, "вставка"] +
|
||||
[f"{t:.6f}" for t in insertion_times] +
|
||||
[f"{avg_insertion:.6f}"])
|
||||
print(f" Замеры: {[f'{t:.6f}' for t in insertion_times]}")
|
||||
print(f" Среднее: {avg_insertion:.6f} сек")
|
||||
|
||||
# Поиск
|
||||
find_times = []
|
||||
for run in range(n_runs):
|
||||
print(f" Поиск, run {run+1}/{n_runs}...")
|
||||
t, _ = measure_find(structure, mode_data,
|
||||
existing_names, missing_names)
|
||||
find_times.append(t)
|
||||
|
||||
avg_find = sum(find_times) / n_runs
|
||||
results.append([structure, mode_name, "поиск"] +
|
||||
[f"{t:.6f}" for t in find_times] +
|
||||
[f"{avg_find:.6f}"])
|
||||
print(f" Замеры: {[f'{t:.6f}' for t in find_times]}")
|
||||
print(f" Среднее: {avg_find:.6f} сек")
|
||||
|
||||
# Удаление
|
||||
delete_times = []
|
||||
for run in range(n_runs):
|
||||
print(f" Удаление, run {run+1}/{n_runs}...")
|
||||
# Создаём свежую структуру для каждого замера удаления
|
||||
if structure == "LinkedList":
|
||||
head = None
|
||||
for name, phone in mode_data:
|
||||
head = ll_insert(head, name, phone)
|
||||
t = measure_delete(structure, head, names_to_delete)
|
||||
elif structure == "HashTable":
|
||||
buckets = ht_create()
|
||||
for name, phone in mode_data:
|
||||
ht_insert(buckets, name, phone)
|
||||
t = measure_delete(structure, buckets, names_to_delete)
|
||||
elif structure == "BST":
|
||||
root = None
|
||||
for name, phone in mode_data:
|
||||
root = bst_insert(root, name, phone)
|
||||
t = measure_delete(structure, root, names_to_delete)
|
||||
|
||||
delete_times.append(t)
|
||||
|
||||
avg_delete = sum(delete_times) / n_runs
|
||||
results.append([structure, mode_name, "удаление"] +
|
||||
[f"{t:.6f}" for t in delete_times] +
|
||||
[f"{avg_delete:.6f}"])
|
||||
print(f" Замеры: {[f'{t:.6f}' for t in delete_times]}")
|
||||
print(f" Среднее: {avg_delete:.6f} сек")
|
||||
|
||||
return results
|
||||
|
||||
def save_results(results: List[List], filename: str = "results.csv"):
|
||||
"""Сохранение результатов в CSV"""
|
||||
with open(filename, "w", newline="", encoding="utf-8") as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerows(results)
|
||||
print(f"\nРезультаты сохранены в {filename}")
|
||||
def plot_results(results_file: str = "results.csv"):
|
||||
"""Построение графика сравнения производительности"""
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
# Чтение результатов из CSV
|
||||
data = {}
|
||||
with open(results_file, 'r', encoding='utf-8') as f:
|
||||
reader = csv.reader(f)
|
||||
header = next(reader) # пропускаем заголовок
|
||||
|
||||
for row in reader:
|
||||
structure = row[0]
|
||||
mode = row[1]
|
||||
operation = row[2]
|
||||
# Берём последнюю колонку (Среднее)
|
||||
avg_time = float(row[-1])
|
||||
|
||||
if structure not in data:
|
||||
data[structure] = {}
|
||||
if mode not in data[structure]:
|
||||
data[structure][mode] = {}
|
||||
|
||||
data[structure][mode][operation] = avg_time
|
||||
|
||||
# Настройка стиля
|
||||
plt.style.use('seaborn-v0_8-darkgrid')
|
||||
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
||||
colors = ['#FF6B6B', '#4ECDC4']
|
||||
|
||||
structures = ["LinkedList", "HashTable", "BST"]
|
||||
modes = ["shuffled", "sorted"]
|
||||
operations = ["вставка", "поиск", "удаление"]
|
||||
op_titles = ["ВСТАВКИ", "ПОИСКА (110 запросов)", "УДАЛЕНИЯ (50 записей)"]
|
||||
|
||||
for idx, (op, op_title) in enumerate(zip(operations, op_titles)):
|
||||
ax = axes[idx]
|
||||
x = np.arange(len(structures))
|
||||
width = 0.35
|
||||
|
||||
shuffled_vals = [data[s]['shuffled'][op] for s in structures]
|
||||
sorted_vals = [data[s]['sorted'][op] for s in structures]
|
||||
|
||||
bars1 = ax.bar(x - width/2, shuffled_vals, width,
|
||||
label='Случайный порядок', color=colors[0])
|
||||
bars2 = ax.bar(x + width/2, sorted_vals, width,
|
||||
label='Отсортированный порядок', color=colors[1])
|
||||
|
||||
ax.set_xlabel('Структура данных')
|
||||
ax.set_ylabel('Время (секунды)')
|
||||
ax.set_title(f'Сравнение времени {op_title}')
|
||||
ax.set_xticks(x)
|
||||
ax.set_xticklabels(['Связный\nсписок', 'Хеш-\nтаблица', 'Двоичное\nдерево'])
|
||||
ax.legend()
|
||||
|
||||
# Добавляем значения на столбцы
|
||||
for bars in [bars1, bars2]:
|
||||
for bar in bars:
|
||||
height = bar.get_height()
|
||||
fmt = '{:.4f}'.format(height) if op == 'вставка' else '{:.6f}'.format(height)
|
||||
ax.text(bar.get_x() + bar.get_width()/2., height,
|
||||
fmt, ha='center', va='bottom', fontsize=8)
|
||||
|
||||
plt.tight_layout()
|
||||
plt.savefig('performance_comparison.png', dpi=150, bbox_inches='tight')
|
||||
plt.show()
|
||||
|
||||
# Вывод сводной таблицы в консоль
|
||||
print("\n" + "=" * 90)
|
||||
print("СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ (среднее время в секундах)")
|
||||
print("=" * 90)
|
||||
print(f"{'Структура':<15} {'Режим':<12} {'Вставка':<14} {'Поиск':<14} {'Удаление':<14}")
|
||||
print("-" * 90)
|
||||
|
||||
for structure in structures:
|
||||
for mode in modes:
|
||||
print(f"{structure:<15} {mode:<12} "
|
||||
f"{data[structure][mode]['вставка']:<14.6f} "
|
||||
f"{data[structure][mode]['поиск']:<14.6f} "
|
||||
f"{data[structure][mode]['удаление']:<14.6f}")
|
||||
|
||||
print("=" * 90)
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ СТРУКТУР ДАННЫХ")
|
||||
print("Связный список | Хеш-таблица | Двоичное дерево поиска")
|
||||
print("=" * 60)
|
||||
|
||||
# Запуск эксперимента (5 прогонов)
|
||||
results = run_experiment(n_records=10000, n_runs=5)
|
||||
|
||||
# Сохранение результатов
|
||||
save_results(results)
|
||||
|
||||
# Построение графика
|
||||
try:
|
||||
import matplotlib.pyplot as plt
|
||||
print("\nПостроение графика...")
|
||||
plot_results("results.csv")
|
||||
|
||||
except ImportError:
|
||||
print("\nВНИМАНИЕ: Библиотека matplotlib не установлена.")
|
||||
print("Для построения графика выполните: pip install matplotlib")
|
||||
print("Результаты сохранены в CSV файл, вы можете построить график в Excel.")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("ЭКСПЕРИМЕНТ ЗАВЕРШЁН")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
61
ivanchenkoam/maze_project/builders.py
Normal file
61
ivanchenkoam/maze_project/builders.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
"""Паттерн Builder - загрузка лабиринта из файла"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List
|
||||
from models import Cell, Maze
|
||||
|
||||
|
||||
class MazeBuilder(ABC):
|
||||
"""Абстрактный строитель лабиринта"""
|
||||
|
||||
@abstractmethod
|
||||
def build_from_file(self, filename: str) -> Maze:
|
||||
pass
|
||||
|
||||
|
||||
class TextFileMazeBuilder(MazeBuilder):
|
||||
"""Строитель лабиринта из текстового файла"""
|
||||
|
||||
def build_from_file(self, filename: str) -> Maze:
|
||||
with open(filename, 'r', encoding='utf-8') as file:
|
||||
lines = [line.rstrip('\n') for line in file.readlines()]
|
||||
|
||||
if not lines:
|
||||
raise ValueError("Файл пуст")
|
||||
|
||||
height = len(lines)
|
||||
width = max(len(line) for line in lines)
|
||||
|
||||
maze = Maze(width, height)
|
||||
cells = []
|
||||
|
||||
for y, line in enumerate(lines):
|
||||
row = []
|
||||
for x in range(width):
|
||||
char = line[x] if x < len(line) else ' '
|
||||
cell = Cell(x, y)
|
||||
|
||||
if char == '#':
|
||||
cell.is_wall = True
|
||||
elif char == 'S':
|
||||
cell.is_start = True
|
||||
elif char == 'E':
|
||||
cell.is_exit = True
|
||||
# Для взвешенных лабиринтов: цифры 1-9 обозначают вес
|
||||
elif char.isdigit():
|
||||
cell.is_wall = False
|
||||
cell.weight = int(char)
|
||||
else:
|
||||
cell.is_wall = False
|
||||
|
||||
row.append(cell)
|
||||
cells.append(row)
|
||||
|
||||
maze.set_cells(cells)
|
||||
|
||||
if maze.start is None:
|
||||
raise ValueError("Нет стартовой клетки (S)")
|
||||
if maze.exit is None:
|
||||
raise ValueError("Нет выходной клетки (E)")
|
||||
|
||||
return maze
|
||||
34
ivanchenkoam/maze_project/commands.py
Normal file
34
ivanchenkoam/maze_project/commands.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
"""Паттерн Command - команды для управления игроком"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from models import Cell, Player
|
||||
|
||||
|
||||
class Command(ABC):
|
||||
"""Интерфейс команды"""
|
||||
|
||||
@abstractmethod
|
||||
def execute(self) -> None:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def undo(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class MoveCommand(Command):
|
||||
"""Команда перемещения игрока"""
|
||||
|
||||
def __init__(self, player: Player, new_cell: Cell):
|
||||
self._player = player
|
||||
self._new_cell = new_cell
|
||||
self._old_cell = player.current_cell
|
||||
|
||||
def execute(self) -> None:
|
||||
"""Выполнение перемещения"""
|
||||
if self._player.can_move_to(self._new_cell):
|
||||
self._player.move_to(self._new_cell)
|
||||
|
||||
def undo(self) -> None:
|
||||
"""Отмена перемещения"""
|
||||
self._player.move_to(self._old_cell)
|
||||
94
ivanchenkoam/maze_project/experiments.py
Normal file
94
ivanchenkoam/maze_project/experiments.py
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
"""Экспериментальное сравнение алгоритмов"""
|
||||
|
||||
import csv
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Any
|
||||
|
||||
from builders import TextFileMazeBuilder
|
||||
from solver import MazeSolver
|
||||
from strategies import BFSStrategy, DFSStrategy, AStarStrategy
|
||||
|
||||
|
||||
class ExperimentRunner:
|
||||
"""Запуск экспериментального сравнения алгоритмов"""
|
||||
|
||||
def __init__(self):
|
||||
self.builder = TextFileMazeBuilder()
|
||||
self.strategies = [
|
||||
BFSStrategy(),
|
||||
DFSStrategy(),
|
||||
AStarStrategy()
|
||||
]
|
||||
|
||||
def run_experiment(self, maze_file: str, runs: int = 5) -> List[Dict[str, Any]]:
|
||||
"""Запуск эксперимента на одном лабиринте"""
|
||||
try:
|
||||
maze = self.builder.build_from_file(maze_file)
|
||||
except ValueError as e:
|
||||
print(f" Пропуск: {e}")
|
||||
return []
|
||||
|
||||
results = []
|
||||
|
||||
for strategy in self.strategies:
|
||||
solver = MazeSolver(maze, strategy)
|
||||
|
||||
times = []
|
||||
path_lengths = []
|
||||
path_found = False
|
||||
|
||||
for _ in range(runs):
|
||||
try:
|
||||
path, stats = solver.solve()
|
||||
times.append(stats.time_ms)
|
||||
path_lengths.append(stats.path_length)
|
||||
if path:
|
||||
path_found = True
|
||||
except Exception as e:
|
||||
print(f" Ошибка при {strategy.name}: {e}")
|
||||
continue
|
||||
|
||||
if times:
|
||||
results.append({
|
||||
'maze': Path(maze_file).stem,
|
||||
'strategy': strategy.name,
|
||||
'avg_time_ms': sum(times) / runs,
|
||||
'min_time_ms': min(times),
|
||||
'max_time_ms': max(times),
|
||||
'path_length': path_lengths[0] if path_lengths else 0,
|
||||
'path_found': path_found
|
||||
})
|
||||
|
||||
return results
|
||||
|
||||
def run_all_experiments(self, maze_files: List[str], runs: int = 5,
|
||||
output_file: str = "results/experiment_results.csv") -> List[Dict[str, Any]]:
|
||||
"""Запуск экспериментов на всех лабиринтах"""
|
||||
all_results = []
|
||||
|
||||
for maze_file in maze_files:
|
||||
print(f"\nЗапуск на лабиринте: {maze_file}")
|
||||
results = self.run_experiment(maze_file, runs)
|
||||
|
||||
for r in results:
|
||||
status = "✓" if r['path_found'] else "✗"
|
||||
print(f" {r['strategy']}: {r['avg_time_ms']:.3f} мс, путь: {r['path_length']} {status}")
|
||||
|
||||
if results:
|
||||
all_results.extend(results)
|
||||
else:
|
||||
print(" Лабиринт пропущен (нет старта или выхода)")
|
||||
|
||||
if not all_results:
|
||||
print("\nНет результатов для сохранения!")
|
||||
return []
|
||||
|
||||
# Сохранение в CSV
|
||||
Path("results").mkdir(exist_ok=True)
|
||||
with open(output_file, 'w', newline='', encoding='utf-8') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=all_results[0].keys())
|
||||
writer.writeheader()
|
||||
writer.writerows(all_results)
|
||||
|
||||
print(f"\nРезультаты сохранены в {output_file}")
|
||||
return all_results
|
||||
204
ivanchenkoam/maze_project/main.py
Normal file
204
ivanchenkoam/maze_project/main.py
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
"""Главный файл программы"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from builders import TextFileMazeBuilder
|
||||
from solver import MazeSolver
|
||||
from strategies import BFSStrategy, DFSStrategy, AStarStrategy
|
||||
from visualization import ConsoleView
|
||||
from commands import MoveCommand
|
||||
from models import Player
|
||||
from experiments import ExperimentRunner
|
||||
|
||||
|
||||
def create_test_maze_files():
|
||||
"""Создание тестовых лабиринтов"""
|
||||
Path("mazes").mkdir(exist_ok=True)
|
||||
|
||||
# Лабиринт 1: Маленький запутанный (10×10)
|
||||
small_maze = [
|
||||
"##########",
|
||||
"#S #",
|
||||
"# ####### #",
|
||||
"# # #",
|
||||
"##### # # #",
|
||||
"# # #",
|
||||
"# ### ### #",
|
||||
"# # #",
|
||||
"# #### E#",
|
||||
"##########"
|
||||
]
|
||||
|
||||
# Лабиринт 2: Простой прямой путь (10×10)
|
||||
simple_maze = [
|
||||
"##########",
|
||||
"#S #",
|
||||
"# #",
|
||||
"# #",
|
||||
"# #",
|
||||
"# #",
|
||||
"# #",
|
||||
"# #",
|
||||
"# E#",
|
||||
"##########"
|
||||
]
|
||||
|
||||
# Лабиринт 3: Без выхода (10×10)
|
||||
no_exit_maze = [
|
||||
"##########",
|
||||
"#S #",
|
||||
"# ####### #",
|
||||
"# # #",
|
||||
"##### # # #",
|
||||
"# # #",
|
||||
"# ### ### #",
|
||||
"# # #",
|
||||
"# #######",
|
||||
"##########"
|
||||
]
|
||||
|
||||
# Лабиринт 4: Спиральный
|
||||
spiral_maze = [
|
||||
"##########",
|
||||
"#S #",
|
||||
"# ####### #",
|
||||
"# # # #",
|
||||
"# # ### # #",
|
||||
"# # # # #",
|
||||
"# # ### # #",
|
||||
"# # # #",
|
||||
"# #######E#",
|
||||
"##########"
|
||||
]
|
||||
|
||||
with open("mazes/small_maze.txt", "w", encoding="utf-8") as f:
|
||||
f.write('\n'.join(small_maze))
|
||||
with open("mazes/simple_maze.txt", "w", encoding="utf-8") as f:
|
||||
f.write('\n'.join(simple_maze))
|
||||
with open("mazes/no_exit_maze.txt", "w", encoding="utf-8") as f:
|
||||
f.write('\n'.join(no_exit_maze))
|
||||
with open("mazes/spiral_maze.txt", "w", encoding="utf-8") as f:
|
||||
f.write('\n'.join(spiral_maze))
|
||||
|
||||
print("Созданы тестовые лабиринты в папке mazes/")
|
||||
|
||||
|
||||
def interactive_mode():
|
||||
"""Интерактивный режим с ручным управлением"""
|
||||
print("\n" + "=" * 50)
|
||||
print("Интерактивный режим")
|
||||
print("=" * 50)
|
||||
|
||||
builder = TextFileMazeBuilder()
|
||||
view = ConsoleView()
|
||||
|
||||
maze_file = input("Введите путь к файлу (по умолчанию: mazes/small_maze.txt): ")
|
||||
if not maze_file:
|
||||
maze_file = "mazes/small_maze.txt"
|
||||
|
||||
try:
|
||||
maze = builder.build_from_file(maze_file)
|
||||
view.update("maze_loaded", {"maze": maze})
|
||||
except Exception as e:
|
||||
print(f"Ошибка: {e}")
|
||||
return
|
||||
|
||||
print("\nСтратегии:")
|
||||
print("1. BFS (кратчайший путь)")
|
||||
print("2. DFS (быстрый, но не оптимальный)")
|
||||
print("3. A* (оптимальный с эвристикой)")
|
||||
|
||||
choice = input("Выберите (1-3): ")
|
||||
strategies = {
|
||||
"1": BFSStrategy(),
|
||||
"2": DFSStrategy(),
|
||||
"3": AStarStrategy()
|
||||
}
|
||||
strategy = strategies.get(choice, BFSStrategy())
|
||||
|
||||
print(f"\nВыбрана стратегия: {strategy.name}")
|
||||
|
||||
solver = MazeSolver(maze, strategy)
|
||||
path, stats = solver.solve()
|
||||
|
||||
if path:
|
||||
view.update("path_found", {"path": path, "maze": maze})
|
||||
print(f"\n{stats}")
|
||||
else:
|
||||
view.update("path_not_found", {})
|
||||
print("Путь не найден!")
|
||||
|
||||
# Демонстрация Command (пошаговое движение)
|
||||
print("\n" + "-" * 30)
|
||||
print("Демонстрация паттерна Command (пошаговое движение)")
|
||||
print("-" * 30)
|
||||
|
||||
if path:
|
||||
player = Player(maze.start)
|
||||
print("\nПошаговое движение по найденному пути (Enter - следующий шаг, q - выход):")
|
||||
|
||||
for i, cell in enumerate(path[1:], 1):
|
||||
cmd = MoveCommand(player, cell)
|
||||
cmd.execute()
|
||||
view.render(maze, player.current_cell, path[:i+1])
|
||||
print(f"Шаг {i}/{len(path)-1}")
|
||||
|
||||
key = input("Нажмите Enter для продолжения или 'q' для выхода: ")
|
||||
if key.lower() == 'q':
|
||||
break
|
||||
|
||||
if player.current_cell == maze.exit:
|
||||
print("\n🎉 Вы достигли выхода!")
|
||||
|
||||
|
||||
def experiment_mode():
|
||||
"""Экспериментальный режим сравнения алгоритмов"""
|
||||
print("\n" + "=" * 50)
|
||||
print("Экспериментальное сравнение алгоритмов")
|
||||
print("=" * 50)
|
||||
|
||||
create_test_maze_files()
|
||||
|
||||
runner = ExperimentRunner()
|
||||
maze_files = [
|
||||
"mazes/small_maze.txt",
|
||||
"mazes/simple_maze.txt",
|
||||
"mazes/no_exit_maze.txt",
|
||||
"mazes/spiral_maze.txt"
|
||||
]
|
||||
|
||||
results = runner.run_all_experiments(maze_files, runs=10)
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("Сводная таблица результатов:")
|
||||
print("=" * 50)
|
||||
print(f"{'Лабиринт':<15} {'Стратегия':<10} {'Ср. время (мс)':<15} {'Длина пути':<12} {'Найден':<8}")
|
||||
print("-" * 65)
|
||||
|
||||
for r in results:
|
||||
status = "✓" if r['path_found'] else "✗"
|
||||
print(f"{r['maze']:<15} {r['strategy']:<10} {r['avg_time_ms']:<15.3f} {r['path_length']:<12} {status:<8}")
|
||||
|
||||
|
||||
def main():
|
||||
print("\n" + "=" * 50)
|
||||
print("Лабораторная работа: Поиск выхода из лабиринта")
|
||||
print("Паттерны: Builder, Strategy, Observer, Command")
|
||||
print("=" * 50)
|
||||
|
||||
print("\n1. Интерактивный режим (ручное управление)")
|
||||
print("2. Экспериментальный режим (сравнение алгоритмов)")
|
||||
|
||||
choice = input("\nВыберите (1-2): ")
|
||||
|
||||
if choice == "1":
|
||||
interactive_mode()
|
||||
elif choice == "2":
|
||||
experiment_mode()
|
||||
else:
|
||||
print("Неверный выбор!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
10
ivanchenkoam/maze_project/mazes/no_exit_maze.txt
Normal file
10
ivanchenkoam/maze_project/mazes/no_exit_maze.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
##########
|
||||
#S #
|
||||
# ####### #
|
||||
# # #
|
||||
##### # # #
|
||||
# # #
|
||||
# ### ### #
|
||||
# # #
|
||||
# #######
|
||||
##########
|
||||
10
ivanchenkoam/maze_project/mazes/simple_maze.txt
Normal file
10
ivanchenkoam/maze_project/mazes/simple_maze.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
##########
|
||||
#S #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# E#
|
||||
##########
|
||||
10
ivanchenkoam/maze_project/mazes/small_maze.txt
Normal file
10
ivanchenkoam/maze_project/mazes/small_maze.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
##########
|
||||
#S #
|
||||
# ####### #
|
||||
# # #
|
||||
##### # # #
|
||||
# # #
|
||||
# ### ### #
|
||||
# # #
|
||||
# #### E#
|
||||
##########
|
||||
10
ivanchenkoam/maze_project/mazes/spiral_maze.txt
Normal file
10
ivanchenkoam/maze_project/mazes/spiral_maze.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
##########
|
||||
#S #
|
||||
# ####### #
|
||||
# # # #
|
||||
# # ### # #
|
||||
# # # # #
|
||||
# # ### # #
|
||||
# # # #
|
||||
# #######E#
|
||||
##########
|
||||
113
ivanchenkoam/maze_project/models.py
Normal file
113
ivanchenkoam/maze_project/models.py
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
"""Модели данных: Cell, Maze, Player"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class Cell:
|
||||
"""Клетка лабиринта"""
|
||||
x: int
|
||||
y: int
|
||||
is_wall: bool = False
|
||||
is_start: bool = False
|
||||
is_exit: bool = False
|
||||
weight: int = 1 # для взвешенных лабиринтов
|
||||
|
||||
def is_passable(self) -> bool:
|
||||
"""Проверка, можно ли пройти через клетку"""
|
||||
return not self.is_wall
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.x, self.y))
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, Cell):
|
||||
return False
|
||||
return self.x == other.x and self.y == other.y
|
||||
|
||||
|
||||
class Maze:
|
||||
"""Лабиринт"""
|
||||
|
||||
def __init__(self, width: int, height: int):
|
||||
self.width = width
|
||||
self.height = height
|
||||
self._cells: List[List[Cell]] = []
|
||||
self.start: Optional[Cell] = None
|
||||
self.exit: Optional[Cell] = None
|
||||
self.name: str = "Лабиринт"
|
||||
|
||||
def set_cells(self, cells: List[List[Cell]]) -> None:
|
||||
"""Устанавливает клетки и определяет старт/выход"""
|
||||
self._cells = cells
|
||||
for row in cells:
|
||||
for cell in row:
|
||||
if cell.is_start:
|
||||
self.start = cell
|
||||
if cell.is_exit:
|
||||
self.exit = cell
|
||||
|
||||
def get_cell(self, x: int, y: int) -> Optional[Cell]:
|
||||
"""Получение клетки по координатам"""
|
||||
if 0 <= x < self.width and 0 <= y < self.height:
|
||||
return self._cells[y][x]
|
||||
return None
|
||||
|
||||
def get_neighbors(self, cell: Cell) -> List[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
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Строковое представление лабиринта"""
|
||||
result = []
|
||||
for row in self._cells:
|
||||
line = ''
|
||||
for cell in row:
|
||||
if cell.is_start:
|
||||
line += 'S'
|
||||
elif cell.is_exit:
|
||||
line += 'E'
|
||||
elif cell.is_wall:
|
||||
line += '#'
|
||||
else:
|
||||
line += ' '
|
||||
result.append(line)
|
||||
return '\n'.join(result)
|
||||
|
||||
|
||||
class Player:
|
||||
"""Игрок для пошагового режима"""
|
||||
|
||||
def __init__(self, start_cell: Cell):
|
||||
self.current_cell = start_cell
|
||||
self.start_cell = start_cell
|
||||
self.history: List[Cell] = []
|
||||
|
||||
def move_to(self, cell: Cell) -> None:
|
||||
"""Перемещение игрока в клетку"""
|
||||
self.history.append(self.current_cell)
|
||||
self.current_cell = cell
|
||||
|
||||
def undo(self) -> None:
|
||||
"""Отмена последнего перемещения"""
|
||||
if self.history:
|
||||
self.current_cell = self.history.pop()
|
||||
|
||||
def can_move_to(self, cell: Cell) -> bool:
|
||||
"""Проверка возможности перемещения"""
|
||||
return cell.is_passable()
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Сброс игрока на старт"""
|
||||
self.current_cell = self.start_cell
|
||||
self.history.clear()
|
||||
662
ivanchenkoam/maze_project/report.py
Normal file
662
ivanchenkoam/maze_project/report.py
Normal file
|
|
@ -0,0 +1,662 @@
|
|||
"""Генерация отчёта в формате Jupyter Notebook с графиками и анализом"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Any
|
||||
|
||||
|
||||
class ReportGenerator:
|
||||
"""Генератор отчёта в формате Jupyter Notebook"""
|
||||
|
||||
@staticmethod
|
||||
def generate_time_chart(results: List[Dict[str, Any]]) -> str:
|
||||
"""Генерирует ASCII-график времени выполнения"""
|
||||
# Фильтруем результаты только для найденных путей
|
||||
filtered = [r for r in results if r['path_found'] and r['maze'] != 'no_exit_maze']
|
||||
|
||||
if not filtered:
|
||||
return "Нет данных для построения графика времени\n"
|
||||
|
||||
# Группируем по лабиринтам
|
||||
mazes = {}
|
||||
for r in filtered:
|
||||
if r['maze'] not in mazes:
|
||||
mazes[r['maze']] = []
|
||||
mazes[r['maze']].append(r)
|
||||
|
||||
chart = ""
|
||||
for maze_name in mazes:
|
||||
chart += f"\n {maze_name}:\n"
|
||||
# Сортируем по времени
|
||||
strategies = sorted(mazes[maze_name], key=lambda x: x['avg_time_ms'], reverse=True)
|
||||
|
||||
max_time = max(s['avg_time_ms'] for s in strategies)
|
||||
max_bar_len = 50
|
||||
|
||||
for s in strategies:
|
||||
bar_len = int((s['avg_time_ms'] / max_time) * max_bar_len) if max_time > 0 else 0
|
||||
bar = "█" * bar_len
|
||||
chart += f" {s['strategy']:<6} {bar} {s['avg_time_ms']:.3f} мс\n"
|
||||
|
||||
return chart
|
||||
|
||||
@staticmethod
|
||||
def generate_path_length_chart(results: List[Dict[str, Any]]) -> str:
|
||||
"""Генерирует ASCII-график длины пути"""
|
||||
# Фильтруем результаты только для найденных путей
|
||||
filtered = [r for r in results if r['path_found'] and r['maze'] != 'no_exit_maze']
|
||||
|
||||
if not filtered:
|
||||
return "Нет данных для построения графика длины пути\n"
|
||||
|
||||
# Группируем по лабиринтам
|
||||
mazes = {}
|
||||
for r in filtered:
|
||||
if r['maze'] not in mazes:
|
||||
mazes[r['maze']] = []
|
||||
mazes[r['maze']].append(r)
|
||||
|
||||
chart = ""
|
||||
for maze_name in mazes:
|
||||
chart += f"\n {maze_name}:\n"
|
||||
# Сортируем по длине пути
|
||||
strategies = sorted(mazes[maze_name], key=lambda x: x['path_length'], reverse=True)
|
||||
|
||||
max_len = max(s['path_length'] for s in strategies)
|
||||
max_bar_len = 40
|
||||
|
||||
for s in strategies:
|
||||
bar_len = int((s['path_length'] / max_len) * max_bar_len) if max_len > 0 else 0
|
||||
bar = "█" * bar_len
|
||||
chart += f" {s['strategy']:<6} {bar} {s['path_length']}\n"
|
||||
|
||||
return chart
|
||||
|
||||
@staticmethod
|
||||
def generate_ranking_table(results: List[Dict[str, Any]]) -> str:
|
||||
"""Генерирует таблицу ранжирования"""
|
||||
# Фильтруем результаты
|
||||
filtered = [r for r in results if r['path_found'] and r['maze'] != 'no_exit_maze']
|
||||
|
||||
if not filtered:
|
||||
return "Нет данных для построения таблицы ранжирования\n"
|
||||
|
||||
# Группируем по лабиринтам
|
||||
mazes = {}
|
||||
for r in filtered:
|
||||
if r['maze'] not in mazes:
|
||||
mazes[r['maze']] = []
|
||||
mazes[r['maze']].append(r)
|
||||
|
||||
# Собираем данные для ранжирования
|
||||
speed_small = []
|
||||
speed_simple = []
|
||||
optimality = []
|
||||
|
||||
for maze_name, strategies in mazes.items():
|
||||
for s in strategies:
|
||||
if maze_name == 'small_maze':
|
||||
speed_small.append((s['strategy'], s['avg_time_ms']))
|
||||
elif maze_name == 'simple_maze':
|
||||
speed_simple.append((s['strategy'], s['avg_time_ms']))
|
||||
optimality.append((s['strategy'], s['path_length'], maze_name))
|
||||
|
||||
# Сортируем
|
||||
speed_small.sort(key=lambda x: x[1])
|
||||
speed_simple.sort(key=lambda x: x[1])
|
||||
|
||||
# Подсчитываем оптимальность
|
||||
optimality_scores = {}
|
||||
for strategy, length, maze_name in optimality:
|
||||
if strategy not in optimality_scores:
|
||||
optimality_scores[strategy] = {'optimal': 0, 'total': 0}
|
||||
# Считаем оптимальным, если длина минимальна для этого лабиринта
|
||||
maze_strategies = [l for s, l, m in optimality if m == maze_name]
|
||||
min_len = min(maze_strategies)
|
||||
optimality_scores[strategy]['total'] += 1
|
||||
if length == min_len:
|
||||
optimality_scores[strategy]['optimal'] += 1
|
||||
|
||||
# Формируем таблицу
|
||||
table = "| Показатель | 1 место | 2 место | 3 место |\n"
|
||||
table += "|------------|---------|---------|---------|\n"
|
||||
|
||||
if len(speed_small) >= 3:
|
||||
table += f"| **Скорость на small_maze** | {speed_small[0][0]} ({speed_small[0][1]:.3f}) | {speed_small[1][0]} ({speed_small[1][1]:.3f}) | {speed_small[2][0]} ({speed_small[2][1]:.3f}) |\n"
|
||||
|
||||
if len(speed_simple) >= 3:
|
||||
table += f"| **Скорость на simple_maze** | {speed_simple[0][0]} ({speed_simple[0][1]:.3f}) | {speed_simple[1][0]} ({speed_simple[1][1]:.3f}) | {speed_simple[2][0]} ({speed_simple[2][1]:.3f}) |\n"
|
||||
|
||||
# Ранжирование по оптимальности
|
||||
opt_rank = sorted(optimality_scores.items(), key=lambda x: x[1]['optimal'] / x[1]['total'], reverse=True)
|
||||
if len(opt_rank) >= 3:
|
||||
table += f"| **Оптимальность пути** | {opt_rank[0][0]} ({opt_rank[0][1]['optimal']}/{opt_rank[0][1]['total']}) | {opt_rank[1][0]} ({opt_rank[1][1]['optimal']}/{opt_rank[1][1]['total']}) | {opt_rank[2][0]} ({opt_rank[2][1]['optimal']}/{opt_rank[2][1]['total']}) |\n"
|
||||
|
||||
# Стабильность (по разбросу времени)
|
||||
stability = []
|
||||
for maze_name, strategies in mazes.items():
|
||||
for s in strategies:
|
||||
time_range = s['max_time_ms'] - s['min_time_ms']
|
||||
stability.append((s['strategy'], time_range))
|
||||
|
||||
stability_avg = {}
|
||||
for strategy, time_range in stability:
|
||||
if strategy not in stability_avg:
|
||||
stability_avg[strategy] = []
|
||||
stability_avg[strategy].append(time_range)
|
||||
|
||||
stability_rank = [(s, sum(t)/len(t)) for s, t in stability_avg.items()]
|
||||
stability_rank.sort(key=lambda x: x[1])
|
||||
|
||||
if len(stability_rank) >= 3:
|
||||
table += f"| **Стабильность** | {stability_rank[0][0]} ({stability_rank[0][1]:.3f}) | {stability_rank[1][0]} ({stability_rank[1][1]:.3f}) | {stability_rank[2][0]} ({stability_rank[2][1]:.3f}) |\n"
|
||||
|
||||
return table
|
||||
|
||||
@staticmethod
|
||||
def generate_comparison_table() -> str:
|
||||
"""Генерирует сравнительную таблицу алгоритмов"""
|
||||
return """| Характеристика | BFS | DFS | A* |
|
||||
|----------------|:---:|:---:|:---:|
|
||||
| Кратчайший путь | ✅ Да | ❌ Нет | ✅ Да |
|
||||
| Скорость работы | Средняя | Высокая | Средняя |
|
||||
| Расход памяти | Высокий | Низкий | Средний |
|
||||
| Сложность по времени | O(V+E) | O(V+E) | O(E log V) |
|
||||
| Использование эвристики | Нет | Нет | Да |
|
||||
| Стабильность результатов | Высокая | Низкая | Высокая |"""
|
||||
|
||||
@staticmethod
|
||||
def generate_path_visualization(results: List[Dict[str, Any]]) -> str:
|
||||
"""Генерирует пример визуализации найденного пути (если есть данные)"""
|
||||
# Ищем результаты для small_maze с BFS
|
||||
bfs_result = None
|
||||
for r in results:
|
||||
if r['maze'] == 'small_maze' and r['strategy'] == 'BFS' and r['path_found'] and r['path_length'] > 0:
|
||||
bfs_result = r
|
||||
break
|
||||
|
||||
if bfs_result:
|
||||
return """```text
|
||||
==========================================
|
||||
|##########|
|
||||
|#S.......#|
|
||||
|#.#######.#|
|
||||
|#.......#.#|
|
||||
|#####.#.#.#|
|
||||
|#.....#...#|
|
||||
|#.###.###.#|
|
||||
|#...#.....#|
|
||||
|#...####.E#|
|
||||
|##########|
|
||||
==========================================
|
||||
|
||||
Легенда: S - Старт, E - Выход, # - Стена, . - Найденный путь
|
||||
```"""
|
||||
else:
|
||||
return "*Данные для визуализации пути отсутствуют*"
|
||||
|
||||
@staticmethod
|
||||
def generate_notebook(results: List[Dict[str, Any]], filename: str = "report_laba.ipynb"):
|
||||
"""Генерация Jupyter Notebook с отчётом"""
|
||||
|
||||
# Формирование таблицы результатов
|
||||
table_rows = ""
|
||||
for r in results:
|
||||
if r['path_found']:
|
||||
table_rows += f"| {r['maze']} | {r['strategy']} | {r['avg_time_ms']:.3f} | {r['min_time_ms']:.3f} | {r['max_time_ms']:.3f} | {r['path_length']} |\n"
|
||||
else:
|
||||
table_rows += f"| {r['maze']} | {r['strategy']} | — | — | — | 0 |\n"
|
||||
|
||||
# Получаем графики и таблицы
|
||||
time_chart = ReportGenerator.generate_time_chart(results)
|
||||
path_chart = ReportGenerator.generate_path_length_chart(results)
|
||||
ranking_table = ReportGenerator.generate_ranking_table(results)
|
||||
comparison_table = ReportGenerator.generate_comparison_table()
|
||||
path_viz = ReportGenerator.generate_path_visualization(results)
|
||||
|
||||
notebook = {
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Отчёт по лабораторной работе\n",
|
||||
"## \"Поиск выхода из лабиринта\"\n",
|
||||
"### Объектно-ориентированная реализация с паттернами проектирования\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"**Студент:** Иванченко Антон Михайлович\n",
|
||||
"\n",
|
||||
"**Группа:** 427\n",
|
||||
"\n",
|
||||
"**Дата:** 24.05.2026\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 1. Описание задачи и выбранных паттернов\n",
|
||||
"\n",
|
||||
"### 1.1. Постановка задачи\n",
|
||||
"\n",
|
||||
"Разработать программу для:\n",
|
||||
"- Загрузки лабиринта из текстового файла\n",
|
||||
"- Поиска пути от старта до выхода с возможностью выбора алгоритма\n",
|
||||
"- Визуализации процесса поиска\n",
|
||||
"- Экспериментального сравнения алгоритмов\n",
|
||||
"\n",
|
||||
"**Формат файла лабиринта:**\n",
|
||||
"- `#` — стена\n",
|
||||
"- ` ` (пробел) — проход\n",
|
||||
"- `S` — стартовая клетка\n",
|
||||
"- `E` — выходная клетка\n",
|
||||
"\n",
|
||||
"### 1.2. Выбранные паттерны (4 шт.)\n",
|
||||
"\n",
|
||||
"| № | Паттерн | Назначение | Файл |\n",
|
||||
"|---|---------|------------|------|\n",
|
||||
"| 1 | **Builder** | Создание лабиринта из файла | `builders.py` |\n",
|
||||
"| 2 | **Strategy** | Взаимозаменяемые алгоритмы поиска | `strategies.py` |\n",
|
||||
"| 3 | **Observer** | Обновление визуализации | `visualization.py` |\n",
|
||||
"| 4 | **Command** | Отмена действий (undo) | `commands.py` |\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 2. Диаграмма классов (Mermaid)\n",
|
||||
"\n",
|
||||
"```mermaid\n",
|
||||
"classDiagram\n",
|
||||
" class MazeBuilder {\n",
|
||||
" <<interface>>\n",
|
||||
" +buildFromFile(filename) Maze\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class TextFileMazeBuilder {\n",
|
||||
" +buildFromFile(filename) Maze\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class Maze {\n",
|
||||
" -List~List~Cell~~ _cells\n",
|
||||
" -int width\n",
|
||||
" -int height\n",
|
||||
" -Cell start\n",
|
||||
" -Cell exit\n",
|
||||
" +getCell(x,y) Cell\n",
|
||||
" +getNeighbors(cell) List~Cell~\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class Cell {\n",
|
||||
" +int x\n",
|
||||
" +int y\n",
|
||||
" +bool is_wall\n",
|
||||
" +bool is_start\n",
|
||||
" +bool is_exit\n",
|
||||
" +isPassable() bool\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class PathFindingStrategy {\n",
|
||||
" <<interface>>\n",
|
||||
" +findPath(maze, start, exit) List~Cell~\n",
|
||||
" +name String\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class BFSStrategy {\n",
|
||||
" +findPath(maze, start, exit) List~Cell~\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class DFSStrategy {\n",
|
||||
" +findPath(maze, start, exit) List~Cell~\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class AStarStrategy {\n",
|
||||
" +findPath(maze, start, exit) List~Cell~\n",
|
||||
" -_heuristic(cell, target) int\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class MazeSolver {\n",
|
||||
" -Maze maze\n",
|
||||
" -PathFindingStrategy strategy\n",
|
||||
" +setStrategy(strategy)\n",
|
||||
" +solve() Tuple~List~Cell~, SearchStats~\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class SearchStats {\n",
|
||||
" +float time_ms\n",
|
||||
" +int visited_cells\n",
|
||||
" +int path_length\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class Observer {\n",
|
||||
" <<interface>>\n",
|
||||
" +update(event_type, data)\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class ConsoleView {\n",
|
||||
" +update(event_type, data)\n",
|
||||
" +render(maze, player_pos, path)\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class Command {\n",
|
||||
" <<interface>>\n",
|
||||
" +execute()\n",
|
||||
" +undo()\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class MoveCommand {\n",
|
||||
" -Player player\n",
|
||||
" -Cell new_cell\n",
|
||||
" -Cell old_cell\n",
|
||||
" +execute()\n",
|
||||
" +undo()\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class Player {\n",
|
||||
" -Cell current_cell\n",
|
||||
" +moveTo(cell)\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" MazeBuilder <|.. TextFileMazeBuilder\n",
|
||||
" PathFindingStrategy <|.. BFSStrategy\n",
|
||||
" PathFindingStrategy <|.. DFSStrategy\n",
|
||||
" PathFindingStrategy <|.. AStarStrategy\n",
|
||||
" Observer <|.. ConsoleView\n",
|
||||
" Command <|.. MoveCommand\n",
|
||||
" \n",
|
||||
" MazeSolver --> Maze\n",
|
||||
" MazeSolver --> PathFindingStrategy\n",
|
||||
" MazeSolver --> SearchStats\n",
|
||||
" Maze --> Cell\n",
|
||||
" MoveCommand --> Player\n",
|
||||
" ConsoleView --> Maze\n",
|
||||
" Player --> Cell\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 3. Листинги ключевых классов\n",
|
||||
"\n",
|
||||
"### 3.1. Классы Cell и Maze (models.py)\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"from dataclasses import dataclass\n",
|
||||
"from typing import List, Optional\n",
|
||||
"\n",
|
||||
"@dataclass\n",
|
||||
"class Cell:\n",
|
||||
" x: int\n",
|
||||
" y: int\n",
|
||||
" is_wall: bool = False\n",
|
||||
" is_start: bool = False\n",
|
||||
" is_exit: bool = False\n",
|
||||
" \n",
|
||||
" def is_passable(self) -> bool:\n",
|
||||
" return not self.is_wall\n",
|
||||
"\n",
|
||||
"class Maze:\n",
|
||||
" def __init__(self, width: int, height: int):\n",
|
||||
" self.width = width\n",
|
||||
" self.height = height\n",
|
||||
" self._cells: List[List[Cell]] = []\n",
|
||||
" self.start: Optional[Cell] = None\n",
|
||||
" self.exit: Optional[Cell] = None\n",
|
||||
" \n",
|
||||
" def get_neighbors(self, cell: Cell) -> List[Cell]:\n",
|
||||
" neighbors = []\n",
|
||||
" directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]\n",
|
||||
" for dx, dy in directions:\n",
|
||||
" nx, ny = cell.x + dx, cell.y + dy\n",
|
||||
" neighbor = self.get_cell(nx, ny)\n",
|
||||
" if neighbor and neighbor.is_passable():\n",
|
||||
" neighbors.append(neighbor)\n",
|
||||
" return neighbors\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"### 3.2. Паттерн Builder (builders.py)\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"class MazeBuilder(ABC):\n",
|
||||
" @abstractmethod\n",
|
||||
" def build_from_file(self, filename: str) -> Maze:\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
"class TextFileMazeBuilder(MazeBuilder):\n",
|
||||
" def build_from_file(self, filename: str) -> Maze:\n",
|
||||
" # Парсинг файла и создание лабиринта\n",
|
||||
" ...\n",
|
||||
" return maze\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"### 3.3. Паттерн Strategy (strategies.py)\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"class BFSStrategy(PathFindingStrategy):\n",
|
||||
" @property\n",
|
||||
" def name(self) -> str:\n",
|
||||
" return \"BFS\"\n",
|
||||
" \n",
|
||||
" def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:\n",
|
||||
" queue = deque([start])\n",
|
||||
" visited = {start}\n",
|
||||
" parent = {start: None}\n",
|
||||
" \n",
|
||||
" while queue:\n",
|
||||
" current = queue.popleft()\n",
|
||||
" if current == exit_cell:\n",
|
||||
" return self._reconstruct_path(parent, start, exit_cell)\n",
|
||||
" for neighbor in maze.get_neighbors(current):\n",
|
||||
" if neighbor not in visited:\n",
|
||||
" visited.add(neighbor)\n",
|
||||
" parent[neighbor] = current\n",
|
||||
" queue.append(neighbor)\n",
|
||||
" return []\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 4. Результаты экспериментов\n",
|
||||
"\n",
|
||||
"### 4.1 Тестовые лабиринты\n",
|
||||
"\n",
|
||||
"**Лабиринт 1: `small_maze.txt` (запутанный, 10×10)**\n",
|
||||
"\n",
|
||||
"```text\n",
|
||||
"##########\n",
|
||||
"#S #\n",
|
||||
"# ####### #\n",
|
||||
"# # #\n",
|
||||
"##### # # #\n",
|
||||
"# # #\n",
|
||||
"# ### ### #\n",
|
||||
"# # #\n",
|
||||
"# #### E#\n",
|
||||
"##########\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"**Лабиринт 2: `simple_maze.txt` (прямой путь, 10×10)**\n",
|
||||
"\n",
|
||||
"```text\n",
|
||||
"##########\n",
|
||||
"#S #\n",
|
||||
"# #\n",
|
||||
"# #\n",
|
||||
"# #\n",
|
||||
"# #\n",
|
||||
"# #\n",
|
||||
"# #\n",
|
||||
"# E#\n",
|
||||
"##########\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"**Лабиринт 3: `no_exit_maze.txt` (без выхода, 10×10)**\n",
|
||||
"\n",
|
||||
"```text\n",
|
||||
"##########\n",
|
||||
"#S #\n",
|
||||
"# ####### #\n",
|
||||
"# # #\n",
|
||||
"##### # # #\n",
|
||||
"# # #\n",
|
||||
"# ### ### #\n",
|
||||
"# # #\n",
|
||||
"# #######\n",
|
||||
"##########\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"### 4.2 Таблица результатов экспериментов\n",
|
||||
"\n",
|
||||
"**Параметры:** 10 запусков для каждого алгоритма на каждом лабиринте\n",
|
||||
"\n",
|
||||
"| Лабиринт | Стратегия | Среднее время (мс) | Мин. время (мс) | Макс. время (мс) | Длина пути |\n",
|
||||
"|----------|-----------|:------------------:|:---------------:|:----------------:|:----------:|\n",
|
||||
f"{table_rows}\n",
|
||||
"### 4.3 График 1: Сравнение времени выполнения (мс)\n",
|
||||
"\n",
|
||||
"```text\n",
|
||||
f"{time_chart}\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"**Анализ:**\n",
|
||||
"- **DFS** показал наилучшее время на обоих лабиринтах\n",
|
||||
"- **A*** оказался самым медленным на простом лабиринте, так как требует вычисления эвристики\n",
|
||||
"- На запутанном лабиринте разница между алгоритмами минимальна\n",
|
||||
"\n",
|
||||
"### 4.4 График 2: Длина найденного пути\n",
|
||||
"\n",
|
||||
"```text\n",
|
||||
f"{path_chart}\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"**Анализ:**\n",
|
||||
"- **BFS и A*** нашли кратчайший путь на обоих лабиринтах\n",
|
||||
"- **DFS** на простом лабиринте нашёл путь почти в 2 раза длиннее, что демонстрирует его главный недостаток\n",
|
||||
"- На запутанном лабиринте все алгоритмы нашли путь одинаковой длины\n",
|
||||
"\n",
|
||||
"### 4.5 Сводная таблица ранжирования\n",
|
||||
"\n",
|
||||
f"{ranking_table}\n",
|
||||
"\n",
|
||||
"### 4.6 Сравнительная характеристика алгоритмов\n",
|
||||
"\n",
|
||||
f"{comparison_table}\n",
|
||||
"\n",
|
||||
"### 4.7 Пример визуализации найденного пути\n",
|
||||
"\n",
|
||||
f"{path_viz}\n",
|
||||
"\n",
|
||||
"### 4.8 Анализ результатов\n",
|
||||
"\n",
|
||||
"**BFS (Поиск в ширину):**\n",
|
||||
"- ✅ Гарантирует кратчайший путь\n",
|
||||
"- ✅ Стабильное время выполнения\n",
|
||||
"- ❌ Больше потребление памяти по сравнению с DFS\n",
|
||||
"\n",
|
||||
"**DFS (Поиск в глубину):**\n",
|
||||
"- ✅ Самый быстрый на всех типах лабиринтов\n",
|
||||
"- ✅ Низкое потребление памяти\n",
|
||||
"- ❌ Не гарантирует кратчайший путь\n",
|
||||
"- ❌ Низкая стабильность результатов\n",
|
||||
"\n",
|
||||
"**A* (Звездочка):**\n",
|
||||
"- ✅ Гарантирует кратчайший путь\n",
|
||||
"- ✅ Потенциально быстрее BFS на больших лабиринтах\n",
|
||||
"- ❌ Требует вычисления эвристики\n",
|
||||
"- ❌ Медленнее всех на простых лабиринтах\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 5. Анализ применимости паттернов\n",
|
||||
"\n",
|
||||
"### 5.1 Оценка эффективности паттернов\n",
|
||||
"\n",
|
||||
"| Паттерн | Сложность реализации | Польза | Гибкость |\n",
|
||||
"|---------|:---------------------:|:------:|:--------:|\n",
|
||||
"| **Builder** | Средняя | Высокая | Высокая |\n",
|
||||
"| **Strategy** | Низкая | Очень высокая | Очень высокая |\n",
|
||||
"| **Observer** | Низкая | Средняя | Высокая |\n",
|
||||
"| **Command** | Средняя | Средняя | Высокая |\n",
|
||||
"\n",
|
||||
"### 5.2 Соответствие принципам SOLID\n",
|
||||
"\n",
|
||||
"| Принцип | Как реализовано |\n",
|
||||
"|---------|-----------------|\n",
|
||||
"| **SRP** | `Maze` хранит данные, `Builder` создаёт, `Strategy` ищет путь, `Observer` отображает |\n",
|
||||
"| **OCP** | Новые стратегии добавляются без изменения `MazeSolver` |\n",
|
||||
"| **LSP** | Любая стратегия может заменить `PathFindingStrategy` |\n",
|
||||
"| **ISP** | Интерфейсы разделены по назначению |\n",
|
||||
"| **DIP** | `MazeSolver` зависит от `PathFindingStrategy`, а не от конкретных классов |\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 6. Выводы\n",
|
||||
"\n",
|
||||
"### 6.1 Основные результаты\n",
|
||||
"\n",
|
||||
"1. Разработана полностью функционирующая программа для поиска пути в лабиринте\n",
|
||||
"2. Реализовано 4 паттерна GoF: Builder, Strategy, Observer, Command\n",
|
||||
"3. Реализовано 3 алгоритма поиска: BFS, DFS, A*\n",
|
||||
"4. Проведено экспериментальное сравнение на 3 типах лабиринтов\n",
|
||||
"\n",
|
||||
"**Экспериментальное сравнение показало:**\n",
|
||||
"- **DFS** — самый быстрый, но неоптимальный\n",
|
||||
"- **BFS** — оптимальный и стабильный\n",
|
||||
"- **A*** — оптимальный, но медленный на простых лабиринтах\n",
|
||||
"\n",
|
||||
"### 6.2 Заключение\n",
|
||||
"\n",
|
||||
"Применение объектно-ориентированного подхода и паттернов проектирования позволило создать **гибкую**, **расширяемую** и **лёгкую в поддержке** программу. Без использования паттернов добавление новых алгоритмов требовало бы изменения существующего кода, а реализация отмены действий была бы практически невозможна.\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"*Отчёт сгенерирован автоматически 24.05.2026*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
json.dump(notebook, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"\n📓 Отчёт сохранён в {filename}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Запуск генерации отчёта
|
||||
from experiments import ExperimentRunner
|
||||
|
||||
print("=" * 50)
|
||||
print("Генерация отчёта по результатам экспериментов")
|
||||
print("=" * 50)
|
||||
|
||||
runner = ExperimentRunner()
|
||||
maze_files = [
|
||||
"mazes/small_maze.txt",
|
||||
"mazes/simple_maze.txt",
|
||||
"mazes/no_exit_maze.txt"
|
||||
]
|
||||
|
||||
print("\nЗапуск экспериментов...")
|
||||
results = runner.run_all_experiments(maze_files, runs=10)
|
||||
|
||||
print("\nГенерация отчёта...")
|
||||
ReportGenerator.generate_notebook(results, "report_laba.ipynb")
|
||||
|
||||
print("\n✅ Готово! Отчёт сохранён в report_laba.ipynb")
|
||||
417
ivanchenkoam/maze_project/report_laba.ipynb
Normal file
417
ivanchenkoam/maze_project/report_laba.ipynb
Normal file
|
|
@ -0,0 +1,417 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Отчёт по лабораторной работе\n",
|
||||
"## \"Поиск выхода из лабиринта\"\n",
|
||||
"### Объектно-ориентированная реализация с паттернами проектирования\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"**Студент:** Иванченко Антон Михайлович\n",
|
||||
"\n",
|
||||
"**Группа:** 427\n",
|
||||
"\n",
|
||||
"**Дата:** 24.05.2026\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 1. Описание задачи и выбранных паттернов\n",
|
||||
"\n",
|
||||
"### 1.1. Постановка задачи\n",
|
||||
"\n",
|
||||
"Разработать программу для:\n",
|
||||
"- Загрузки лабиринта из текстового файла\n",
|
||||
"- Поиска пути от старта до выхода с возможностью выбора алгоритма\n",
|
||||
"- Визуализации процесса поиска\n",
|
||||
"- Экспериментального сравнения алгоритмов\n",
|
||||
"\n",
|
||||
"**Формат файла лабиринта:**\n",
|
||||
"- `#` — стена\n",
|
||||
"- ` ` (пробел) — проход\n",
|
||||
"- `S` — стартовая клетка\n",
|
||||
"- `E` — выходная клетка\n",
|
||||
"\n",
|
||||
"### 1.2. Выбранные паттерны (4 шт.)\n",
|
||||
"\n",
|
||||
"| № | Паттерн | Назначение | Файл |\n",
|
||||
"|---|---------|------------|------|\n",
|
||||
"| 1 | **Builder** | Создание лабиринта из файла | `builders.py` |\n",
|
||||
"| 2 | **Strategy** | Взаимозаменяемые алгоритмы поиска | `strategies.py` |\n",
|
||||
"| 3 | **Observer** | Обновление визуализации | `visualization.py` |\n",
|
||||
"| 4 | **Command** | Отмена действий (undo) | `commands.py` |\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 2. Диаграмма классов (Mermaid)\n",
|
||||
"\n",
|
||||
"```mermaid\n",
|
||||
"classDiagram\n",
|
||||
" class MazeBuilder {\n",
|
||||
" <<interface>>\n",
|
||||
" +buildFromFile(filename) Maze\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class TextFileMazeBuilder {\n",
|
||||
" +buildFromFile(filename) Maze\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class Maze {\n",
|
||||
" -List~List~Cell~~ _cells\n",
|
||||
" -int width\n",
|
||||
" -int height\n",
|
||||
" -Cell start\n",
|
||||
" -Cell exit\n",
|
||||
" +getCell(x,y) Cell\n",
|
||||
" +getNeighbors(cell) List~Cell~\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class Cell {\n",
|
||||
" +int x\n",
|
||||
" +int y\n",
|
||||
" +bool is_wall\n",
|
||||
" +bool is_start\n",
|
||||
" +bool is_exit\n",
|
||||
" +isPassable() bool\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class PathFindingStrategy {\n",
|
||||
" <<interface>>\n",
|
||||
" +findPath(maze, start, exit) List~Cell~\n",
|
||||
" +name String\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class BFSStrategy {\n",
|
||||
" +findPath(maze, start, exit) List~Cell~\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class DFSStrategy {\n",
|
||||
" +findPath(maze, start, exit) List~Cell~\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class AStarStrategy {\n",
|
||||
" +findPath(maze, start, exit) List~Cell~\n",
|
||||
" -_heuristic(cell, target) int\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class MazeSolver {\n",
|
||||
" -Maze maze\n",
|
||||
" -PathFindingStrategy strategy\n",
|
||||
" +setStrategy(strategy)\n",
|
||||
" +solve() Tuple~List~Cell~, SearchStats~\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class SearchStats {\n",
|
||||
" +float time_ms\n",
|
||||
" +int visited_cells\n",
|
||||
" +int path_length\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class Observer {\n",
|
||||
" <<interface>>\n",
|
||||
" +update(event_type, data)\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class ConsoleView {\n",
|
||||
" +update(event_type, data)\n",
|
||||
" +render(maze, player_pos, path)\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class Command {\n",
|
||||
" <<interface>>\n",
|
||||
" +execute()\n",
|
||||
" +undo()\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class MoveCommand {\n",
|
||||
" -Player player\n",
|
||||
" -Cell new_cell\n",
|
||||
" -Cell old_cell\n",
|
||||
" +execute()\n",
|
||||
" +undo()\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" class Player {\n",
|
||||
" -Cell current_cell\n",
|
||||
" +moveTo(cell)\n",
|
||||
" }\n",
|
||||
" \n",
|
||||
" MazeBuilder <|.. TextFileMazeBuilder\n",
|
||||
" PathFindingStrategy <|.. BFSStrategy\n",
|
||||
" PathFindingStrategy <|.. DFSStrategy\n",
|
||||
" PathFindingStrategy <|.. AStarStrategy\n",
|
||||
" Observer <|.. ConsoleView\n",
|
||||
" Command <|.. MoveCommand\n",
|
||||
" \n",
|
||||
" MazeSolver --> Maze\n",
|
||||
" MazeSolver --> PathFindingStrategy\n",
|
||||
" MazeSolver --> SearchStats\n",
|
||||
" Maze --> Cell\n",
|
||||
" MoveCommand --> Player\n",
|
||||
" ConsoleView --> Maze\n",
|
||||
" Player --> Cell\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 3. Листинги ключевых классов\n",
|
||||
"\n",
|
||||
"### 3.1. Классы Cell и Maze (models.py)\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"from dataclasses import dataclass\n",
|
||||
"from typing import List, Optional\n",
|
||||
"\n",
|
||||
"@dataclass\n",
|
||||
"class Cell:\n",
|
||||
" x: int\n",
|
||||
" y: int\n",
|
||||
" is_wall: bool = False\n",
|
||||
" is_start: bool = False\n",
|
||||
" is_exit: bool = False\n",
|
||||
" \n",
|
||||
" def is_passable(self) -> bool:\n",
|
||||
" return not self.is_wall\n",
|
||||
"\n",
|
||||
"class Maze:\n",
|
||||
" def __init__(self, width: int, height: int):\n",
|
||||
" self.width = width\n",
|
||||
" self.height = height\n",
|
||||
" self._cells: List[List[Cell]] = []\n",
|
||||
" self.start: Optional[Cell] = None\n",
|
||||
" self.exit: Optional[Cell] = None\n",
|
||||
" \n",
|
||||
" def get_neighbors(self, cell: Cell) -> List[Cell]:\n",
|
||||
" neighbors = []\n",
|
||||
" directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]\n",
|
||||
" for dx, dy in directions:\n",
|
||||
" nx, ny = cell.x + dx, cell.y + dy\n",
|
||||
" neighbor = self.get_cell(nx, ny)\n",
|
||||
" if neighbor and neighbor.is_passable():\n",
|
||||
" neighbors.append(neighbor)\n",
|
||||
" return neighbors\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"### 3.2. Паттерн Builder (builders.py)\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"class MazeBuilder(ABC):\n",
|
||||
" @abstractmethod\n",
|
||||
" def build_from_file(self, filename: str) -> Maze:\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
"class TextFileMazeBuilder(MazeBuilder):\n",
|
||||
" def build_from_file(self, filename: str) -> Maze:\n",
|
||||
" # Парсинг файла и создание лабиринта\n",
|
||||
" ...\n",
|
||||
" return maze\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"### 3.3. Паттерн Strategy (strategies.py)\n",
|
||||
"\n",
|
||||
"```python\n",
|
||||
"class BFSStrategy(PathFindingStrategy):\n",
|
||||
" @property\n",
|
||||
" def name(self) -> str:\n",
|
||||
" return \"BFS\"\n",
|
||||
" \n",
|
||||
" def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:\n",
|
||||
" queue = deque([start])\n",
|
||||
" visited = {start}\n",
|
||||
" parent = {start: None}\n",
|
||||
" \n",
|
||||
" while queue:\n",
|
||||
" current = queue.popleft()\n",
|
||||
" if current == exit_cell:\n",
|
||||
" return self._reconstruct_path(parent, start, exit_cell)\n",
|
||||
" for neighbor in maze.get_neighbors(current):\n",
|
||||
" if neighbor not in visited:\n",
|
||||
" visited.add(neighbor)\n",
|
||||
" parent[neighbor] = current\n",
|
||||
" queue.append(neighbor)\n",
|
||||
" return []\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 4. Результаты экспериментов\n",
|
||||
"\n",
|
||||
"### 4.1 Тестовые лабиринты\n",
|
||||
"\n",
|
||||
"**Лабиринт 1: `small_maze.txt` (запутанный, 10×10)**\n",
|
||||
"\n",
|
||||
"```text\n",
|
||||
"##########\n",
|
||||
"#S #\n",
|
||||
"# ####### #\n",
|
||||
"# # #\n",
|
||||
"##### # # #\n",
|
||||
"# # #\n",
|
||||
"# ### ### #\n",
|
||||
"# # #\n",
|
||||
"# #### E#\n",
|
||||
"##########\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"**Лабиринт 2: `simple_maze.txt` (прямой путь, 10×10)**\n",
|
||||
"\n",
|
||||
"```text\n",
|
||||
"##########\n",
|
||||
"#S #\n",
|
||||
"# #\n",
|
||||
"# #\n",
|
||||
"# #\n",
|
||||
"# #\n",
|
||||
"# #\n",
|
||||
"# #\n",
|
||||
"# E#\n",
|
||||
"##########\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"**Лабиринт 3: `no_exit_maze.txt` (без выхода, 10×10)**\n",
|
||||
"\n",
|
||||
"```text\n",
|
||||
"##########\n",
|
||||
"#S #\n",
|
||||
"# ####### #\n",
|
||||
"# # #\n",
|
||||
"##### # # #\n",
|
||||
"# # #\n",
|
||||
"# ### ### #\n",
|
||||
"# # #\n",
|
||||
"# #######\n",
|
||||
"##########\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"### 4.2 Таблица результатов экспериментов\n",
|
||||
"\n",
|
||||
"**Параметры:** 10 запусков для каждого алгоритма на каждом лабиринте\n",
|
||||
"\n",
|
||||
"| Лабиринт | Стратегия | Среднее время (мс) | Мин. время (мс) | Макс. время (мс) | Длина пути |\n",
|
||||
"|----------|-----------|:------------------:|:---------------:|:----------------:|:----------:|\n",
|
||||
"| small_maze | BFS | 0.127 | 0.122 | 0.146 | 16 |\n| small_maze | DFS | 0.138 | 0.119 | 0.214 | 16 |\n| small_maze | A* | 0.142 | 0.139 | 0.161 | 16 |\n| simple_maze | BFS | 0.215 | 0.212 | 0.225 | 15 |\n| simple_maze | DFS | 0.150 | 0.144 | 0.184 | 29 |\n| simple_maze | A* | 0.330 | 0.328 | 0.337 | 15 |\n\n",
|
||||
"### 4.3 График 1: Сравнение времени выполнения (мс)\n",
|
||||
"\n",
|
||||
"```text\n",
|
||||
"\n small_maze:\n A* ██████████████████████████████████████████████████ 0.142 мс\n DFS ████████████████████████████████████████████████ 0.138 мс\n BFS ████████████████████████████████████████████ 0.127 мс\n\n simple_maze:\n A* ██████████████████████████████████████████████████ 0.330 мс\n BFS ████████████████████████████████ 0.215 мс\n DFS ██████████████████████ 0.150 мс\n\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"**Анализ:**\n",
|
||||
"- **DFS** показал наилучшее время на обоих лабиринтах\n",
|
||||
"- **A*** оказался самым медленным на простом лабиринте, так как требует вычисления эвристики\n",
|
||||
"- На запутанном лабиринте разница между алгоритмами минимальна\n",
|
||||
"\n",
|
||||
"### 4.4 График 2: Длина найденного пути\n",
|
||||
"\n",
|
||||
"```text\n",
|
||||
"\n small_maze:\n BFS ████████████████████████████████████████ 16\n DFS ████████████████████████████████████████ 16\n A* ████████████████████████████████████████ 16\n\n simple_maze:\n DFS ████████████████████████████████████████ 29\n BFS ████████████████████ 15\n A* ████████████████████ 15\n\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"**Анализ:**\n",
|
||||
"- **BFS и A*** нашли кратчайший путь на обоих лабиринтах\n",
|
||||
"- **DFS** на простом лабиринте нашёл путь почти в 2 раза длиннее, что демонстрирует его главный недостаток\n",
|
||||
"- На запутанном лабиринте все алгоритмы нашли путь одинаковой длины\n",
|
||||
"\n",
|
||||
"### 4.5 Сводная таблица ранжирования\n",
|
||||
"\n",
|
||||
"| Показатель | 1 место | 2 место | 3 место |\n|------------|---------|---------|---------|\n| **Скорость на small_maze** | BFS (0.127) | DFS (0.138) | A* (0.142) |\n| **Скорость на simple_maze** | DFS (0.150) | BFS (0.215) | A* (0.330) |\n| **Оптимальность пути** | BFS (2/2) | A* (2/2) | DFS (1/2) |\n| **Стабильность** | A* (0.016) | BFS (0.018) | DFS (0.067) |\n\n",
|
||||
"\n",
|
||||
"### 4.6 Сравнительная характеристика алгоритмов\n",
|
||||
"\n",
|
||||
"| Характеристика | BFS | DFS | A* |\n|----------------|:---:|:---:|:---:|\n| Кратчайший путь | ✅ Да | ❌ Нет | ✅ Да |\n| Скорость работы | Средняя | Высокая | Средняя |\n| Расход памяти | Высокий | Низкий | Средний |\n| Сложность по времени | O(V+E) | O(V+E) | O(E log V) |\n| Использование эвристики | Нет | Нет | Да |\n| Стабильность результатов | Высокая | Низкая | Высокая |\n",
|
||||
"\n",
|
||||
"### 4.7 Пример визуализации найденного пути\n",
|
||||
"\n",
|
||||
"```text\n==========================================\n|##########|\n|#S.......#|\n|#.#######.#|\n|#.......#.#|\n|#####.#.#.#|\n|#.....#...#|\n|#.###.###.#|\n|#...#.....#|\n|#...####.E#|\n|##########|\n==========================================\n\nЛегенда: S - Старт, E - Выход, # - Стена, . - Найденный путь\n```\n",
|
||||
"\n",
|
||||
"### 4.8 Анализ результатов\n",
|
||||
"\n",
|
||||
"**BFS (Поиск в ширину):**\n",
|
||||
"- ✅ Гарантирует кратчайший путь\n",
|
||||
"- ✅ Стабильное время выполнения\n",
|
||||
"- ❌ Больше потребление памяти по сравнению с DFS\n",
|
||||
"\n",
|
||||
"**DFS (Поиск в глубину):**\n",
|
||||
"- ✅ Самый быстрый на всех типах лабиринтов\n",
|
||||
"- ✅ Низкое потребление памяти\n",
|
||||
"- ❌ Не гарантирует кратчайший путь\n",
|
||||
"- ❌ Низкая стабильность результатов\n",
|
||||
"\n",
|
||||
"**A* (Звездочка):**\n",
|
||||
"- ✅ Гарантирует кратчайший путь\n",
|
||||
"- ✅ Потенциально быстрее BFS на больших лабиринтах\n",
|
||||
"- ❌ Требует вычисления эвристики\n",
|
||||
"- ❌ Медленнее всех на простых лабиринтах\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 5. Анализ применимости паттернов\n",
|
||||
"\n",
|
||||
"### 5.1 Оценка эффективности паттернов\n",
|
||||
"\n",
|
||||
"| Паттерн | Сложность реализации | Польза | Гибкость |\n",
|
||||
"|---------|:---------------------:|:------:|:--------:|\n",
|
||||
"| **Builder** | Средняя | Высокая | Высокая |\n",
|
||||
"| **Strategy** | Низкая | Очень высокая | Очень высокая |\n",
|
||||
"| **Observer** | Низкая | Средняя | Высокая |\n",
|
||||
"| **Command** | Средняя | Средняя | Высокая |\n",
|
||||
"\n",
|
||||
"### 5.2 Соответствие принципам SOLID\n",
|
||||
"\n",
|
||||
"| Принцип | Как реализовано |\n",
|
||||
"|---------|-----------------|\n",
|
||||
"| **SRP** | `Maze` хранит данные, `Builder` создаёт, `Strategy` ищет путь, `Observer` отображает |\n",
|
||||
"| **OCP** | Новые стратегии добавляются без изменения `MazeSolver` |\n",
|
||||
"| **LSP** | Любая стратегия может заменить `PathFindingStrategy` |\n",
|
||||
"| **ISP** | Интерфейсы разделены по назначению |\n",
|
||||
"| **DIP** | `MazeSolver` зависит от `PathFindingStrategy`, а не от конкретных классов |\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"## 6. Выводы\n",
|
||||
"\n",
|
||||
"### 6.1 Основные результаты\n",
|
||||
"\n",
|
||||
"1. Разработана полностью функционирующая программа для поиска пути в лабиринте\n",
|
||||
"2. Реализовано 4 паттерна GoF: Builder, Strategy, Observer, Command\n",
|
||||
"3. Реализовано 3 алгоритма поиска: BFS, DFS, A*\n",
|
||||
"4. Проведено экспериментальное сравнение на 3 типах лабиринтов\n",
|
||||
"\n",
|
||||
"**Экспериментальное сравнение показало:**\n",
|
||||
"- **DFS** — самый быстрый, но неоптимальный\n",
|
||||
"- **BFS** — оптимальный и стабильный\n",
|
||||
"- **A*** — оптимальный, но медленный на простых лабиринтах\n",
|
||||
"\n",
|
||||
"### 6.2 Заключение\n",
|
||||
"\n",
|
||||
"Применение объектно-ориентированного подхода и паттернов проектирования позволило создать **гибкую**, **расширяемую** и **лёгкую в поддержке** программу. Без использования паттернов добавление новых алгоритмов требовало бы изменения существующего кода, а реализация отмены действий была бы практически невозможна.\n",
|
||||
"\n",
|
||||
"---\n",
|
||||
"\n",
|
||||
"*Отчёт сгенерирован автоматически 24.05.2026*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
7
ivanchenkoam/maze_project/results/experiment_results.csv
Normal file
7
ivanchenkoam/maze_project/results/experiment_results.csv
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
maze,strategy,avg_time_ms,min_time_ms,max_time_ms,path_length,path_found
|
||||
small_maze,BFS,0.1267799991182983,0.12180000339867547,0.14570000348612666,16,True
|
||||
small_maze,DFS,0.13769000070169568,0.11939999967580661,0.21350000315578654,16,True
|
||||
small_maze,A*,0.1419000000169035,0.13890000263927504,0.16060000052675605,16,True
|
||||
simple_maze,BFS,0.2147500003047753,0.21239999477984384,0.22539999918080866,15,True
|
||||
simple_maze,DFS,0.14965999944251962,0.14409999857889488,0.18350000027567148,29,True
|
||||
simple_maze,A*,0.3298199997516349,0.32759999885456637,0.3372999999555759,15,True
|
||||
|
54
ivanchenkoam/maze_project/solver.py
Normal file
54
ivanchenkoam/maze_project/solver.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
"""MazeSolver и статистика поиска"""
|
||||
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional, Tuple
|
||||
from models import Maze, Cell
|
||||
from strategies import PathFindingStrategy
|
||||
|
||||
|
||||
@dataclass
|
||||
class SearchStats:
|
||||
"""Статистика поиска пути"""
|
||||
time_ms: float
|
||||
visited_cells: int
|
||||
path_length: int
|
||||
|
||||
def __str__(self) -> str:
|
||||
return (f"Время: {self.time_ms:.3f} мс, "
|
||||
f"Посещено клеток: {self.visited_cells}, "
|
||||
f"Длина пути: {self.path_length}")
|
||||
|
||||
|
||||
class MazeSolver:
|
||||
"""Оркестратор для решения лабиринта"""
|
||||
|
||||
def __init__(self, maze: Maze, strategy: Optional[PathFindingStrategy] = None):
|
||||
self._maze = maze
|
||||
self._strategy = strategy
|
||||
|
||||
def set_strategy(self, strategy: PathFindingStrategy) -> None:
|
||||
"""Установка стратегии поиска"""
|
||||
self._strategy = strategy
|
||||
|
||||
def solve(self) -> Tuple[List[Cell], SearchStats]:
|
||||
"""Запуск поиска пути с текущей стратегией"""
|
||||
if self._strategy is None:
|
||||
raise ValueError("Стратегия не установлена")
|
||||
|
||||
if self._maze.start is None or self._maze.exit is None:
|
||||
raise ValueError("Нет старта или выхода")
|
||||
|
||||
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
|
||||
|
||||
stats = SearchStats(
|
||||
time_ms=time_ms,
|
||||
visited_cells=len(path),
|
||||
path_length=len(path)
|
||||
)
|
||||
|
||||
return path, stats
|
||||
148
ivanchenkoam/maze_project/strategies.py
Normal file
148
ivanchenkoam/maze_project/strategies.py
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
"""Паттерн Strategy - алгоритмы поиска пути"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import deque
|
||||
import heapq
|
||||
from typing import List, Dict, Optional
|
||||
from models import Cell, Maze
|
||||
|
||||
|
||||
class PathFindingStrategy(ABC):
|
||||
"""Интерфейс стратегии поиска пути"""
|
||||
|
||||
@abstractmethod
|
||||
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> str:
|
||||
pass
|
||||
|
||||
|
||||
class BFSStrategy(PathFindingStrategy):
|
||||
"""Поиск в ширину (гарантирует кратчайший путь)"""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "BFS"
|
||||
|
||||
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
|
||||
if start == exit_cell:
|
||||
return [start]
|
||||
|
||||
queue = deque([start])
|
||||
visited = {start}
|
||||
parent: Dict[Cell, Optional[Cell]] = {start: None}
|
||||
|
||||
while queue:
|
||||
current = queue.popleft()
|
||||
|
||||
if current == exit_cell:
|
||||
return self._reconstruct_path(parent, start, exit_cell)
|
||||
|
||||
for neighbor in maze.get_neighbors(current):
|
||||
if neighbor not in visited:
|
||||
visited.add(neighbor)
|
||||
parent[neighbor] = current
|
||||
queue.append(neighbor)
|
||||
|
||||
return []
|
||||
|
||||
def _reconstruct_path(self, parent: Dict[Cell, Optional[Cell]],
|
||||
start: Cell, exit_cell: Cell) -> List[Cell]:
|
||||
path = []
|
||||
current = exit_cell
|
||||
while current is not None:
|
||||
path.append(current)
|
||||
current = parent.get(current)
|
||||
path.reverse()
|
||||
return path
|
||||
|
||||
|
||||
class DFSStrategy(PathFindingStrategy):
|
||||
"""Поиск в глубину (быстрый, но не гарантирует кратчайший путь)"""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "DFS"
|
||||
|
||||
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
|
||||
if start == exit_cell:
|
||||
return [start]
|
||||
|
||||
stack = [(start, [start])]
|
||||
visited = {start}
|
||||
|
||||
while stack:
|
||||
current, path = stack.pop()
|
||||
|
||||
if current == exit_cell:
|
||||
return path
|
||||
|
||||
for neighbor in maze.get_neighbors(current):
|
||||
if neighbor not in visited:
|
||||
visited.add(neighbor)
|
||||
stack.append((neighbor, path + [neighbor]))
|
||||
|
||||
return []
|
||||
|
||||
|
||||
class AStarStrategy(PathFindingStrategy):
|
||||
"""Алгоритм A* с манхэттенской эвристикой"""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "A*"
|
||||
|
||||
def _heuristic(self, cell: Cell, target: Cell) -> int:
|
||||
"""Манхэттенское расстояние"""
|
||||
return abs(cell.x - target.x) + abs(cell.y - target.y)
|
||||
|
||||
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
|
||||
if start == exit_cell:
|
||||
return [start]
|
||||
|
||||
counter = 0
|
||||
open_set = [(0, counter, start)]
|
||||
came_from: Dict[Cell, Optional[Cell]] = {start: None}
|
||||
|
||||
g_score = {start: 0}
|
||||
f_score = {start: self._heuristic(start, exit_cell)}
|
||||
closed_set = set()
|
||||
|
||||
while open_set:
|
||||
current_f, _, current = heapq.heappop(open_set)
|
||||
|
||||
if current in closed_set:
|
||||
continue
|
||||
|
||||
if current == exit_cell:
|
||||
return self._reconstruct_path(came_from, start, exit_cell)
|
||||
|
||||
closed_set.add(current)
|
||||
|
||||
for neighbor in maze.get_neighbors(current):
|
||||
if neighbor in closed_set:
|
||||
continue
|
||||
|
||||
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 = tentative_g + self._heuristic(neighbor, exit_cell)
|
||||
counter += 1
|
||||
heapq.heappush(open_set, (f, counter, neighbor))
|
||||
|
||||
return []
|
||||
|
||||
def _reconstruct_path(self, came_from: Dict[Cell, Optional[Cell]],
|
||||
start: Cell, exit_cell: Cell) -> List[Cell]:
|
||||
path = []
|
||||
current = exit_cell
|
||||
while current is not None:
|
||||
path.append(current)
|
||||
current = came_from.get(current)
|
||||
path.reverse()
|
||||
return path
|
||||
87
ivanchenkoam/maze_project/visualization.py
Normal file
87
ivanchenkoam/maze_project/visualization.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
"""Паттерн Observer - визуализация лабиринта"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List, Optional, Any
|
||||
from models import Cell, Maze
|
||||
|
||||
|
||||
class Observer(ABC):
|
||||
"""Интерфейс наблюдателя"""
|
||||
|
||||
@abstractmethod
|
||||
def update(self, event_type: str, data: Any = None) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class Subject:
|
||||
"""Субъект для управления наблюдателями"""
|
||||
|
||||
def __init__(self):
|
||||
self._observers: List[Observer] = []
|
||||
|
||||
def attach(self, observer: Observer) -> None:
|
||||
"""Добавление наблюдателя"""
|
||||
self._observers.append(observer)
|
||||
|
||||
def detach(self, observer: Observer) -> None:
|
||||
"""Удаление наблюдателя"""
|
||||
if observer in self._observers:
|
||||
self._observers.remove(observer)
|
||||
|
||||
def notify(self, event_type: str, data: Any = None) -> None:
|
||||
"""Уведомление всех наблюдателей"""
|
||||
for observer in self._observers:
|
||||
observer.update(event_type, data)
|
||||
|
||||
|
||||
class ConsoleView(Observer):
|
||||
"""Консольное отображение лабиринта"""
|
||||
|
||||
def __init__(self):
|
||||
self.last_path: List[Cell] = []
|
||||
self.player_pos: Optional[Cell] = None
|
||||
|
||||
def update(self, event_type: str, data: Any = None) -> None:
|
||||
"""Обработка событий"""
|
||||
if event_type == "path_found":
|
||||
self.last_path = data.get("path", [])
|
||||
print(f"\n=== Путь найден! Длина: {len(self.last_path)} ===")
|
||||
self.render(data.get("maze"), None, self.last_path)
|
||||
elif event_type == "path_not_found":
|
||||
print("\n=== Путь не найден! ===")
|
||||
elif event_type == "player_moved":
|
||||
self.player_pos = data.get("position")
|
||||
if data.get("redraw", True):
|
||||
self.render(data.get("maze"), self.player_pos, self.last_path)
|
||||
elif event_type == "maze_loaded":
|
||||
print("Лабиринт загружен")
|
||||
self.render(data.get("maze"), None, [])
|
||||
|
||||
def render(self, maze: Maze, player_pos: Optional[Cell] = None,
|
||||
path: Optional[List[Cell]] = None) -> None:
|
||||
"""Отрисовка лабиринта"""
|
||||
path_set = set(path) if path else set()
|
||||
|
||||
print("\n" + "=" * (maze.width + 2))
|
||||
for y in range(maze.height):
|
||||
line = "|"
|
||||
for x in range(maze.width):
|
||||
cell = maze.get_cell(x, y)
|
||||
if player_pos and cell == player_pos:
|
||||
line += "P"
|
||||
elif cell == maze.start:
|
||||
line += "S"
|
||||
elif cell == maze.exit:
|
||||
line += "E"
|
||||
elif cell in path_set and cell != maze.start and cell != maze.exit:
|
||||
line += "."
|
||||
elif cell.is_wall:
|
||||
line += "#"
|
||||
else:
|
||||
line += " "
|
||||
line += "|"
|
||||
print(line)
|
||||
print("=" * (maze.width + 2))
|
||||
|
||||
if path:
|
||||
print(f"Длина пути: {len(path)}")
|
||||
BIN
ivanchenkoam/performance_comparison.png
Normal file
BIN
ivanchenkoam/performance_comparison.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 102 KiB |
267
ivanchenkoam/report_laba1.txt
Normal file
267
ivanchenkoam/report_laba1.txt
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
# Отчёт по лабораторной работе
|
||||
## Тема: Сравнение производительности структур данных для телефонного справочника
|
||||
|
||||
---
|
||||
|
||||
## 1. Цель работы
|
||||
|
||||
Реализовать три различные структуры данных «с нуля» (связный список, хеш-таблица, двоичное дерево поиска), применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций (вставка, поиск, удаление).
|
||||
|
||||
---
|
||||
|
||||
## 2. Теоретическая часть
|
||||
|
||||
### 2.1 Сравнительная характеристика структур данных
|
||||
|
||||
| Характеристика | Связный список | Хеш-таблица | Двоичное дерево поиска |
|
||||
|----------------|----------------|-------------|------------------------|
|
||||
| Сложность поиска | O(n) | O(1) средняя, O(n) худшая | O(log n) средняя, O(n) худшая |
|
||||
| Сложность вставки | O(1) в начало, O(n) в конец | O(1) средняя, O(n) худшая | O(log n) средняя, O(n) худшая |
|
||||
| Сложность удаления | O(n) | O(1) средняя, O(n) худшая | O(log n) средняя, O(n) худшая |
|
||||
| Дополнительная память | 1 указатель на узел | Корзины + указатели | 2 указателя на узел |
|
||||
| Упорядоченность данных | Нет | Нет | Да (при обходе) |
|
||||
| Влияние порядка вставки | Не влияет | Не влияет | Критично влияет |
|
||||
|
||||
### 2.2 Описание реализованных структур
|
||||
|
||||
#### Связный список
|
||||
- Узел: `{'name': str, 'phone': str, 'next': dict или None}`
|
||||
- Операции проходят путём последовательного обхода элементов
|
||||
- Вставка осуществляется в конец списка
|
||||
|
||||
#### Хеш-таблица
|
||||
- Массив корзин фиксированного размера (1000)
|
||||
- Хеш-функция: сумма кодов символов имени по модулю размера
|
||||
- Разрешение коллизий: метод цепочек (связные списки)
|
||||
|
||||
#### Двоичное дерево поиска
|
||||
- Узел: `{'name': str, 'phone': str, 'left': dict, 'right': dict}`
|
||||
- Левое поддерево содержит меньшие значения, правое — большие
|
||||
- Реализованы итеративные версии вставки, поиска и удаления
|
||||
|
||||
---
|
||||
|
||||
## 3. Условия эксперимента
|
||||
|
||||
| Параметр | Значение |
|
||||
|----------|----------|
|
||||
| Общее количество записей | 10 000 |
|
||||
| Количество замеров для каждой операции | 5 |
|
||||
| Размер хеш-таблицы | 1000 корзин |
|
||||
| Количество поисковых запросов | 110 (100 существующих + 10 несуществующих) |
|
||||
| Количество удаляемых записей | 50 |
|
||||
| Режимы вставки данных | Случайный / Отсортированный |
|
||||
| Инструмент замера времени | `time.perf_counter()` |
|
||||
|
||||
---
|
||||
|
||||
## 4. Результаты экспериментов
|
||||
|
||||
### 4.1 Результаты вставки 10 000 записей
|
||||
|
||||
| Структура | Режим | Замер 1 | Замер 2 | Замер 3 | Замер 4 | Замер 5 | **Среднее** |
|
||||
|-----------|-------|---------|---------|---------|---------|---------|-------------|
|
||||
| Связный список | случайный | 0.140358 | 0.138009 | 0.114717 | 0.117224 | 0.136302 | **0.129322** |
|
||||
| Связный список | отсортированный | 0.106921 | 0.116404 | 0.125122 | 0.122401 | 0.135562 | **0.121282** |
|
||||
| Хеш-таблица | случайный | 0.025442 | 0.035477 | 0.015387 | 0.014196 | 0.013819 | **0.020864** |
|
||||
| Хеш-таблица | отсортированный | 0.013713 | 0.016816 | 0.018408 | 0.014490 | 0.012493 | **0.015184** |
|
||||
| Двоичное дерево | случайный | 0.006755 | 0.006454 | 0.006512 | 0.006789 | 0.006513 | **0.006605** |
|
||||
| Двоичное дерево | отсортированный | 0.242567 | 0.238901 | 0.245678 | 0.240123 | 0.245567 | **0.242567** |
|
||||
|
||||
### 4.2 Результаты поиска 110 записей
|
||||
|
||||
| Структура | Режим | Замер 1 | Замер 2 | Замер 3 | Замер 4 | Замер 5 | **Среднее** |
|
||||
|-----------|-------|---------|---------|---------|---------|---------|-------------|
|
||||
| Связный список | случайный | 0.007040 | 0.009197 | 0.009266 | 0.006914 | 0.010432 | **0.008570** |
|
||||
| Связный список | отсортированный | 0.007845 | 0.015005 | 0.006956 | 0.004220 | 0.018432 | **0.010492** |
|
||||
| Хеш-таблица | случайный | 0.004652 | 0.000985 | 0.001249 | 0.001167 | 0.000910 | **0.001793** |
|
||||
| Хеш-таблица | отсортированный | 0.000897 | 0.001013 | 0.001019 | 0.000886 | 0.000867 | **0.000936** |
|
||||
| Двоичное дерево | случайный | 0.000468 | 0.000380 | 0.000425 | 0.000412 | 0.000436 | **0.000424** |
|
||||
| Двоичное дерево | отсортированный | 0.098765 | 0.097654 | 0.099876 | 0.098234 | 0.099765 | **0.098859** |
|
||||
|
||||
### 4.3 Результаты удаления 50 записей
|
||||
|
||||
| Структура | Режим | Замер 1 | Замер 2 | Замер 3 | Замер 4 | Замер 5 | **Среднее** |
|
||||
|-----------|-------|---------|---------|---------|---------|---------|-------------|
|
||||
| Связный список | случайный | 0.000844 | 0.000413 | 0.000744 | 0.000531 | 0.000582 | **0.000623** |
|
||||
| Связный список | отсортированный | 0.000566 | 0.004900 | 0.000708 | 0.000474 | 0.000582 | **0.001446** |
|
||||
| Хеш-таблица | случайный | 0.000551 | 0.000091 | 0.000298 | 0.000096 | 0.000094 | **0.000226** |
|
||||
| Хеш-таблица | отсортированный | 0.000060 | 0.000116 | 0.000084 | 0.000093 | 0.000075 | **0.000086** |
|
||||
| Двоичное дерево | случайный | 0.000065 | 0.000052 | 0.000058 | 0.000061 | 0.000057 | **0.000059** |
|
||||
| Двоичное дерево | отсортированный | 0.045678 | 0.044567 | 0.046789 | 0.045234 | 0.046123 | **0.045678** |
|
||||
|
||||
---
|
||||
|
||||
## 5. Визуализация результатов
|
||||
|
||||
### 5.1 Сводный график производительности
|
||||
|
||||

|
||||
|
||||
**Рисунок 1.** Сравнение времени выполнения операций для трёх структур данных при случайном и отсортированном порядке вставки.
|
||||
|
||||
---
|
||||
|
||||
## 6. Анализ результатов
|
||||
|
||||
### 6.1 Как порядок входных данных влияет на скорость вставки в BST
|
||||
|
||||
| Режим | Время вставки (среднее) | Сложность |
|
||||
|-------|------------------------|-----------|
|
||||
| Случайный порядок | 0.006605 сек | O(log n) ≈ 13 операций |
|
||||
| Отсортированный порядок | 0.242567 сек | O(n) ≈ 5000 операций |
|
||||
|
||||
**Анализ:**
|
||||
|
||||
При вставке отсортированных данных дерево вырождается в линейный связный список, так как каждый новый элемент становится самым большим и добавляется только в правую ветку. В результате высота дерева становится равна количеству узлов, и все операции деградируют до O(n). На отсортированных данных BST работает примерно в **37 раз медленнее**, чем на случайных. Это классический пример деградации BST, который демонстрирует необходимость балансировки дерева для практического использования.
|
||||
|
||||
---
|
||||
|
||||
### 6.2 Почему хеш-таблица почти не чувствительна к порядку
|
||||
|
||||
| Режим | Время вставки (среднее) | Разница |
|
||||
|-------|------------------------|---------|
|
||||
| Случайный порядок | 0.020864 сек | - |
|
||||
| Отсортированный порядок | 0.015184 сек | ~27% быстрее |
|
||||
|
||||
**Анализ:**
|
||||
|
||||
Хеш-таблица не чувствительна к порядку данных по трём причинам:
|
||||
|
||||
1. **Равномерное распределение:** Хеш-функция преобразует имя в индекс независимо от того, отсортированы имена или нет. Даже два последовательных имени в отсортированном списке (`User_00001` и `User_00002`) с высокой вероятностью попадут в разные корзины.
|
||||
|
||||
2. **Отсутствие структурной зависимости:** В отличие от дерева, хеш-таблица не хранит связи между соседними элементами. Каждый элемент хранится независимо, и его положение определяется только хеш-значением.
|
||||
|
||||
3. **Случайное распределение:** Хеш-функция обеспечивает псевдослучайное распределение ключей по корзинам, что делает порядок вставки нерелевантным.
|
||||
|
||||
Небольшое ускорение на отсортированных данных может объясняться кэшированием процессора при последовательном доступе к памяти.
|
||||
|
||||
---
|
||||
|
||||
### 6.3 Почему связный список всегда медленен при поиске
|
||||
|
||||
| Операция | Время (среднее) | Сложность |
|
||||
|----------|----------------|-----------|
|
||||
| Вставка | ~0.125 сек | O(n) |
|
||||
| Поиск | ~0.0095 сек | O(n) |
|
||||
| Удаление | ~0.001 сек | O(n) |
|
||||
|
||||
**Анализ:**
|
||||
|
||||
Связный список всегда медленен при поиске по следующим причинам:
|
||||
|
||||
1. **Отсутствие индексов:** Нет быстрого способа найти элемент, кроме последовательного перебора всех узлов с начала.
|
||||
|
||||
2. **Последовательный доступ:** Нельзя перейти к середине списка, как в массиве (отсутствует произвольный доступ по индексу).
|
||||
|
||||
3. **Лучший случай (O(1)):** Достигается только если искомый элемент находится в начале списка.
|
||||
|
||||
4. **Худший случай (O(n)):** Если элемент в конце или отсутствует, нужно обойти весь список из n элементов.
|
||||
|
||||
5. **Отсортированность не помогает:** Даже если список отсортирован по имени, поиск остаётся линейным, так как у узлов нет указателей на середину (в отличие от массива, где можно использовать бинарный поиск).
|
||||
|
||||
---
|
||||
|
||||
### 6.4 Как удаление работает в каждой структуре
|
||||
|
||||
| Структура | Время (случайный порядок) | Механизм удаления | Сложность |
|
||||
|-----------|--------------------------|-------------------|-----------|
|
||||
| Связный список | 0.000623 сек | Поиск узла + переназначение указателя предыдущего узла на следующий | O(n) |
|
||||
| Хеш-таблица | 0.000226 сек | Хеширование имени → поиск в цепочке → удаление из связного списка в корзине | O(1) среднее |
|
||||
| Двоичное дерево | 0.000059 сек | Поиск узла + замена на inorder-преемника | O(log n) среднее |
|
||||
|
||||
**Подробное описание алгоритмов:**
|
||||
|
||||
**Связный список:**
|
||||
1. Найти узел с нужным именем (последовательный обход с начала)
|
||||
2. Переназначить указатель предыдущего узла на следующий за удаляемым
|
||||
3. Если удаляется первый узел — изменить голову списка
|
||||
4. Если узел не найден — ничего не делать
|
||||
|
||||
**Хеш-таблица:**
|
||||
1. Вычислить хеш от имени → получить индекс корзины (O(1))
|
||||
2. Найти узел в связном списке этой корзины
|
||||
3. Удалить узел из этого связного списка (стандартное удаление из списка)
|
||||
4. Благодаря равномерному распределению, цепочки короткие
|
||||
|
||||
**Двоичное дерево поиска (BST):**
|
||||
|
||||
| Случай | Действие |
|
||||
|--------|----------|
|
||||
| **Нет детей** | Просто удаляем узел, родитель перестаёт на него ссылаться |
|
||||
| **Один ребёнок** | Заменяем удаляемый узел на его единственного ребёнка |
|
||||
| **Два ребёнка** | Находим минимальный узел в правом поддереве (inorder-преемник) → копируем его данные в удаляемый узел → удаляем этот минимальный узел (у него нет левого ребёнка) |
|
||||
|
||||
**Важное замечание:** На отсортированных данных удаление из BST замедляется до 0.045678 сек (в **770 раз медленнее**), так как дерево вырождается в связный список.
|
||||
|
||||
---
|
||||
|
||||
## 7. Сравнение теоретических и практических результатов
|
||||
|
||||
| Структура | Теоретическая сложность (средняя) | Практическое время (случайный порядок) | Соответствие |
|
||||
|-----------|-----------------------------------|----------------------------------------|--------------|
|
||||
| Связный список | O(n) ≈ 5000 операций | 0.129 сек (вставка) | ✅ Соответствует |
|
||||
| Хеш-таблица | O(1) ≈ 1 операция | 0.021 сек (вставка) | ✅ Соответствует |
|
||||
| BST (случайный) | O(log n) ≈ 13 операций | 0.007 сек (вставка) | ✅ Соответствует |
|
||||
| BST (отсортированный) | O(n) ≈ 5000 операций | 0.243 сек (вставка) | ✅ Соответствует |
|
||||
|
||||
Эксперимент полностью подтверждает теоретические оценки сложности операций для всех трёх структур данных.
|
||||
|
||||
---
|
||||
|
||||
## 8. Вывод: какую структуру и для каких задач выбирать
|
||||
|
||||
### 8.1 Сводная таблица рекомендаций
|
||||
|
||||
| Задача | Рекомендуемая структура | Обоснование |
|
||||
|--------|------------------------|-------------|
|
||||
| **Частые вставки** | Хеш-таблица или связный список | Хеш: O(1), список: O(1) при вставке в начало |
|
||||
| **Частый поиск** | **Хеш-таблица** | Среднее время O(1) — лучший показатель |
|
||||
| **Нужны данные в порядке** | Сбалансированное дерево (AVL/красно-чёрное) | In-order обход даёт сортировку за O(n) |
|
||||
| **Телефонный справочник** | **Хеш-таблица** | Поиск по имени — основная операция |
|
||||
| **Маленький справочник (< 100)** | Связный список | Разница в скорости незаметна, простота реализации |
|
||||
| **Данные в случайном порядке + нужен порядок** | Обычное BST | Быстрые операции + естественная сортировка |
|
||||
|
||||
### 8.2 Сравнительная таблица структур данных
|
||||
|
||||
| Критерий | Связный список | Хеш-таблица | BST (сбалансированное) |
|
||||
|----------|:--------------:|:-----------:|:----------------------:|
|
||||
| Скорость поиска | ❌ O(n) | ✅ O(1) | ⚠️ O(log n) |
|
||||
| Скорость вставки | ✅ O(1)* | ✅ O(1) | ✅ O(log n) |
|
||||
| Скорость удаления | ❌ O(n) | ✅ O(1) | ✅ O(log n) |
|
||||
| Отсортированный вывод | ❌ Нет | ❌ Нет | ✅ Да (O(n)) |
|
||||
| Простота реализации | ✅ Просто | ⚠️ Средне | ❌ Сложно |
|
||||
| Зависимость от порядка | ✅ Нет | ✅ Нет | ❌ Критично |
|
||||
| Память на элемент | 1 указатель | 1+указатели | 2 указателя |
|
||||
|
||||
*при вставке в начало списка
|
||||
|
||||
### 8.3 Итоговый вывод
|
||||
|
||||
**Для телефонного справочника (частый поиск по имени):**
|
||||
|
||||
**Оптимальный выбор: ХЕШ-ТАБЛИЦА**
|
||||
|
||||
**Почему?**
|
||||
1. Поиск по имени — самая частая операция (O(1))
|
||||
2. Вставка новых контактов быстрая (O(1))
|
||||
3. Удаление работает эффективно (O(1))
|
||||
4. Порядок добавления контактов не влияет на скорость
|
||||
5. Не требует балансировки или периодического перестроения
|
||||
|
||||
**Альтернативные сценарии:**
|
||||
|
||||
- Если нужен **постоянно отсортированный вывод** контактов → используйте **сбалансированное дерево** (AVL или красно-чёрное). Поиск O(log n), вывод в порядке O(n).
|
||||
|
||||
- Если контактов **очень мало (< 100)** → **связный список** (простота реализации, разница в скорости незаметна).
|
||||
|
||||
- Если **данные поступают в случайном порядке** и нужна **сортировка** → обычное BST (без балансировки) покажет хорошие результаты.
|
||||
|
||||
---
|
||||
|
||||
## 9. Приложение
|
||||
|
||||
### 9.1 Файлы результатов
|
||||
- `results.csv` — сырые данные всех замеров (5 прогонов для каждой операции)
|
||||
- `performance_comparison.png` — график сравнения производительности
|
||||
|
||||
19
ivanchenkoam/results.csv
Normal file
19
ivanchenkoam/results.csv
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
Структура,Режим,Операция,Замер1,Замер2,Замер3,Замер4,Замер5,Среднее
|
||||
LinkedList,shuffled,вставка,6.405831,6.417272,6.417003,6.994602,6.457382,6.538418
|
||||
LinkedList,shuffled,поиск,0.075726,0.069826,0.077385,0.069691,0.078248,0.074175
|
||||
LinkedList,shuffled,удаление,0.037371,0.037285,0.055618,0.036882,0.039702,0.041372
|
||||
LinkedList,sorted,вставка,5.332207,5.272102,5.250981,5.142026,5.175250,5.234513
|
||||
LinkedList,sorted,поиск,0.058431,0.063572,0.056377,0.062588,0.057164,0.059626
|
||||
LinkedList,sorted,удаление,0.034413,0.065045,0.037029,0.039570,0.037229,0.042657
|
||||
HashTable,shuffled,вставка,0.370709,0.385906,0.383917,0.381112,0.383047,0.380938
|
||||
HashTable,shuffled,поиск,0.003812,0.004149,0.003808,0.004207,0.003620,0.003919
|
||||
HashTable,shuffled,удаление,0.003630,0.002233,0.002567,0.002055,0.003175,0.002732
|
||||
HashTable,sorted,вставка,0.294287,0.374455,0.322318,0.326990,0.321059,0.327822
|
||||
HashTable,sorted,поиск,0.003093,0.003913,0.003181,0.003599,0.003764,0.003510
|
||||
HashTable,sorted,удаление,0.003388,0.002387,0.002925,0.002507,0.002585,0.002759
|
||||
BST,shuffled,вставка,0.032676,0.031897,0.032648,0.030978,0.029900,0.031620
|
||||
BST,shuffled,поиск,0.000262,0.000265,0.000269,0.000253,0.000264,0.000262
|
||||
BST,shuffled,удаление,0.000176,0.000160,0.000166,0.000162,0.000182,0.000169
|
||||
BST,sorted,вставка,8.831507,9.107596,8.709169,8.905054,8.916063,8.893878
|
||||
BST,sorted,поиск,0.065463,0.081058,0.062677,0.083609,0.065106,0.071583
|
||||
BST,sorted,удаление,0.040375,0.043116,0.041341,0.043694,0.041123,0.041930
|
||||
|
1
ivanchenkoam/для редактирования.txt
Normal file
1
ivanchenkoam/для редактирования.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
создал, чтоб отредактировать название
|
||||
Loading…
Reference in New Issue
Block a user