diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c9ebf2d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python-envs.defaultEnvManager": "ms-python.python:system" +} \ No newline at end of file diff --git a/benchmark_chart.png b/benchmark_chart.png new file mode 100644 index 0000000..19c11ba Binary files /dev/null and b/benchmark_chart.png differ diff --git a/results.csv b/results.csv new file mode 100644 index 0000000..fedd423 --- /dev/null +++ b/results.csv @@ -0,0 +1,91 @@ +структура,режим,операция,повторение,время (сек) +LinkedList,случайный,вставка,1,0.11277499999999918 +LinkedList,случайный,поиск,1,0.006451900000001842 +LinkedList,случайный,удаление,1,0.003666800000019066 +LinkedList,случайный,вставка,2,0.10957519999999477 +LinkedList,случайный,поиск,2,0.0064522000000124535 +LinkedList,случайный,удаление,2,0.003728399999999965 +LinkedList,случайный,вставка,3,0.109341000000029 +LinkedList,случайный,поиск,3,0.006500899999991816 +LinkedList,случайный,удаление,3,0.0038011999999980617 +LinkedList,случайный,вставка,4,0.1110247999999956 +LinkedList,случайный,поиск,4,0.006476099999986218 +LinkedList,случайный,удаление,4,0.0036711999999852196 +LinkedList,случайный,вставка,5,0.11070879999999761 +LinkedList,случайный,поиск,5,0.0064365000000066175 +LinkedList,случайный,удаление,5,0.003677400000015041 +LinkedList,отсортированный,вставка,1,0.11145989999999983 +LinkedList,отсортированный,поиск,1,0.005919100000028266 +LinkedList,отсортированный,удаление,1,0.004607999999961976 +LinkedList,отсортированный,вставка,2,0.11035959999998113 +LinkedList,отсортированный,поиск,2,0.005787800000007337 +LinkedList,отсортированный,удаление,2,0.004521399999987352 +LinkedList,отсортированный,вставка,3,0.11167460000001483 +LinkedList,отсортированный,поиск,3,0.006033900000034009 +LinkedList,отсортированный,удаление,3,0.004642099999955462 +LinkedList,отсортированный,вставка,4,0.11137540000004265 +LinkedList,отсортированный,поиск,4,0.0057623999999805164 +LinkedList,отсортированный,удаление,4,0.004488399999956982 +LinkedList,отсортированный,вставка,5,0.11472939999998744 +LinkedList,отсортированный,поиск,5,0.005918899999983296 +LinkedList,отсортированный,удаление,5,0.004527400000029047 +HashTable,случайный,вставка,1,0.0012038000000416105 +HashTable,случайный,поиск,1,3.46000000490676e-05 +HashTable,случайный,удаление,1,2.0000000006348273e-05 +HashTable,случайный,вставка,2,0.0010017999999831773 +HashTable,случайный,поиск,2,3.1499999977313564e-05 +HashTable,случайный,удаление,2,1.720000000204891e-05 +HashTable,случайный,вставка,3,0.0010786999999936597 +HashTable,случайный,поиск,3,3.4999999968476914e-05 +HashTable,случайный,удаление,3,1.9199999996999395e-05 +HashTable,случайный,вставка,4,0.0009729999999876782 +HashTable,случайный,поиск,4,3.090000001293447e-05 +HashTable,случайный,удаление,4,1.7399999990175274e-05 +HashTable,случайный,вставка,5,0.0009918999999740663 +HashTable,случайный,поиск,5,3.049999997983832e-05 +HashTable,случайный,удаление,5,1.6399999992700032e-05 +HashTable,отсортированный,вставка,1,0.0009690999999634187 +HashTable,отсортированный,поиск,1,3.5900000000310683e-05 +HashTable,отсортированный,удаление,1,1.8599999975776882e-05 +HashTable,отсортированный,вставка,2,0.0009684999999990396 +HashTable,отсортированный,поиск,2,3.0100000003585592e-05 +HashTable,отсортированный,удаление,2,1.759999997830164e-05 +HashTable,отсортированный,вставка,3,0.001049399999999423 +HashTable,отсортированный,поиск,3,3.1100000001060835e-05 +HashTable,отсортированный,удаление,3,1.8299999965165625e-05 +HashTable,отсортированный,вставка,4,0.000957400000004327 +HashTable,отсортированный,поиск,4,2.9900000015459227e-05 +HashTable,отсортированный,удаление,4,1.7500000012660166e-05 +HashTable,отсортированный,вставка,5,0.0009610000000179753 +HashTable,отсортированный,поиск,5,2.9500000039206498e-05 +HashTable,отсортированный,удаление,5,1.770000000078653e-05 +BST,случайный,вставка,1,0.004438699999980145 +BST,случайный,поиск,1,0.00013129999996408515 +BST,случайный,удаление,1,8.220000000846994e-05 +BST,случайный,вставка,2,0.004387300000018968 +BST,случайный,поиск,2,0.00012960000003658934 +BST,случайный,удаление,2,7.93000000385291e-05 +BST,случайный,вставка,3,0.004303200000038032 +BST,случайный,поиск,3,0.0001283999999941443 +BST,случайный,удаление,3,8.050000002413071e-05 +BST,случайный,вставка,4,0.004263700000024073 +BST,случайный,поиск,4,0.00012770000000728032 +BST,случайный,удаление,4,7.650000003422974e-05 +BST,случайный,вставка,5,0.004260499999986678 +BST,случайный,поиск,5,0.00012939999999161955 +BST,случайный,удаление,5,7.969999995793842e-05 +BST,отсортированный,вставка,1,0.7347394000000236 +BST,отсортированный,поиск,1,0.018492599999945014 +BST,отсортированный,удаление,1,0.013188500000012482 +BST,отсортированный,вставка,2,0.7407673999999815 +BST,отсортированный,поиск,2,0.018303899999978057 +BST,отсортированный,удаление,2,0.012820200000021487 +BST,отсортированный,вставка,3,0.7382302000000323 +BST,отсортированный,поиск,3,0.01834120000000894 +BST,отсортированный,удаление,3,0.012760500000013053 +BST,отсортированный,вставка,4,0.7377933000000212 +BST,отсортированный,поиск,4,0.01829400000002579 +BST,отсортированный,удаление,4,0.012722399999972822 +BST,отсортированный,вставка,5,0.7360582999999679 +BST,отсортированный,поиск,5,0.01833809999999403 +BST,отсортированный,удаление,5,0.012751900000012029 diff --git a/sorokinfi/427.md b/sorokinfi/427.md deleted file mode 100644 index e69de29..0000000 diff --git a/sorokinfi/427.py b/sorokinfi/427.py new file mode 100644 index 0000000..8a00d89 --- /dev/null +++ b/sorokinfi/427.py @@ -0,0 +1,572 @@ +import csv +import random +import sys +import time +from collections import defaultdict, deque +import pandas as pd +import matplotlib.pyplot as plt +from abc import ABC, abstractmethod +import os + +current_dir = os.path.dirname(os.path.abspath(__file__)) +os.chdir(current_dir) + +# увеличиваем лимит рекурсии +sys.setrecursionlimit(25000) + +# ЗАДАНИЕ 1: СТРУКТУРЫ ДАННЫХ + +# 1. связный список, узел: {'name': 'Имя', 'phone': '123', 'next': None} + +# проходит до конца и добавляет в конец +def ll_insert(head, name, phone): + new_node = {'name': name, 'phone': phone, 'next': None} + if head is None: + return new_node + current = head + while current['next'] is not None: + current = current['next'] + current['next'] = new_node + return head + +# ищет узел, возвращает телефон или None +def ll_find(head, name): + current = head + while current is not None: + if current['name'] == name: + return current['phone'] + current = current['next'] + return None + +# удаляет узел, возвращает новую голову +def ll_delete(head, name): + if head is None: + return None + if head['name'] == name: + return head['next'] + + current = head + while current['next'] is not None: + if current['next']['name'] == name: + current['next'] = current['next']['next'] + return head + current = current['next'] + return head + +# собирает все записи в список и сортирует +def ll_list_all(head): + records = [] + current = head + while current is not None: + records.append((current['name'], current['phone'])) + current = current['next'] + records.sort(key=lambda x: x[0]) + return records + + +# 2. хеш-таблица + +# хеш-функция для вычисления бекета +def ht_hash(name, size): + return hash(name) % size + +# вычисляет индекс, вызывает ll_insert для соответствующего бакета +def ht_insert(buckets, name, phone): + size = len(buckets) + idx = ht_hash(name, size) + buckets[idx] = ll_insert(buckets[idx], name, phone) + +# поиск по хеш-таблице +def ht_find(buckets, name): + size = len(buckets) + idx = ht_hash(name, size) + return ll_find(buckets[idx], name) + +# удаление из хеш-таблицы +def ht_delete(buckets, name): + size = len(buckets) + idx = ht_hash(name, size) + buckets[idx] = ll_delete(buckets[idx], name) + +# собирает все записи из всех бакетов и сортирует +def ht_list_all(buckets): + all_records = [] + for head in buckets: + current = head + while current is not None: + all_records.append((current['name'], current['phone'])) + current = current['next'] + all_records.sort(key=lambda x: x[0]) + return all_records + + +# 3. двоичное дерево поиска +# узел — словарь: {'name': 'Имя', 'phone': '123', 'left': None, 'right': None} + +# рекурсивно или итеративно вставляет, возвращает новый корень (если корень меняется) +def bst_insert(root, name, phone): + if root is None: + return {'name': name, 'phone': phone, 'left': None, 'right': None} + + if name < root['name']: + root['left'] = bst_insert(root['left'], name, phone) + elif name > root['name']: + root['right'] = bst_insert(root['right'], name, phone) + else: + root['phone'] = phone + return root + +# поиск +def bst_find(root, name): + if root is None: + return None + if name == root['name']: + return root['phone'] + elif name < root['name']: + return bst_find(root['left'], name) + else: + return bst_find(root['right'], name) + +# удаление, возвращает новый корень +def bst_delete(root, name): + if root is None: + return None + + if name < root['name']: + root['left'] = bst_delete(root['left'], name) + elif name > root['name']: + root['right'] = bst_delete(root['right'], name) + else: + # одна ветвь или её отсутствие + if root['left'] is None: + return root['right'] + if root['right'] is None: + return root['left'] + # две ветви + successor = root['right'] + while successor['left'] is not None: + successor = successor['left'] + + root['name'] = successor['name'] + root['phone'] = successor['phone'] + root['right'] = bst_delete(root['right'], successor['name']) + + return root + +# центрированный обход (рекурсивно собирает записи в отсортированном порядке) +def bst_list_all(root): + records = [] + def _inorder(node): + if node is not None: + _inorder(node['left']) + records.append((node['name'], node['phone'])) + _inorder(node['right']) + _inorder(root) + return records + + +# 4. ЭКСПЕРИМЕНТАЛЬНАЯ ЧАСТЬ + +def run_experiments(): + N = 3000 + HASH_SIZE = 1007 + + print(f"генерация тестовых данных для N = {N}...") + records_sorted = [(f"User_{i:05d}", f"+7999123{i:04d}") for i in range(N)] + records_shuffled = records_sorted.copy() + random.seed(42) + random.shuffle(records_shuffled) + + # подготовка выборок + existing_sample = [r[0] for r in random.sample(records_sorted, min(100, N))] + non_existing_sample = [f"None_{i}" for i in range(10)] + search_names = existing_sample + non_existing_sample + + delete_names = [r[0] for r in random.sample(records_sorted, min(50, N))] + csv_rows = [["структура", "режим", "операция", "повторение", "время (сек)"]] + modes = [("случайный", records_shuffled), ("отсортированный", records_sorted)] + + print("запуск экспериментов (5 повторений для каждого режима)") + +# ТЕСТ: СВЯЗНЫЙ СПИСОК + + for mode_name, data in modes: + for rep in range(1, 6): + head = None + t_start = time.perf_counter() + for name, phone in data: + head = ll_insert(head, name, phone) + t_end = time.perf_counter() + csv_rows.append(["LinkedList", mode_name, "вставка", rep, t_end - t_start]) + + t_start = time.perf_counter() + for name in search_names: + ll_find(head, name) + t_end = time.perf_counter() + csv_rows.append(["LinkedList", mode_name, "поиск", rep, t_end - t_start]) + + t_start = time.perf_counter() + for name in delete_names: + head = ll_delete(head, name) + t_end = time.perf_counter() + csv_rows.append(["LinkedList", mode_name, "удаление", rep, t_end - t_start]) + +# ТЕСТ: ХЕШ-ТАБЛИЦА + + for mode_name, data in modes: + for rep in range(1, 6): + buckets = [None] * HASH_SIZE + t_start = time.perf_counter() + for name, phone in data: + ht_insert(buckets, name, phone) + t_end = time.perf_counter() + csv_rows.append(["HashTable", mode_name, "вставка", rep, t_end - t_start]) + + t_start = time.perf_counter() + for name in search_names: + ht_find(buckets, name) + t_end = time.perf_counter() + csv_rows.append(["HashTable", mode_name, "поиск", rep, t_end - t_start]) + + t_start = time.perf_counter() + for name in delete_names: + ht_delete(buckets, name) + t_end = time.perf_counter() + csv_rows.append(["HashTable", mode_name, "удаление", rep, t_end - t_start]) + +# ТЕСТ: ДЕРЕВО ПОИСКА (BST) + + for mode_name, data in modes: + for rep in range(1, 6): + root = None + t_start = time.perf_counter() + for name, phone in data: + root = bst_insert(root, name, phone) + t_end = time.perf_counter() + csv_rows.append(["BST", mode_name, "вставка", rep, t_end - t_start]) + + t_start = time.perf_counter() + for name in search_names: + bst_find(root, name) + t_end = time.perf_counter() + csv_rows.append(["BST", mode_name, "поиск", rep, t_end - t_start]) + + t_start = time.perf_counter() + for name in delete_names: + root = bst_delete(root, name) + t_end = time.perf_counter() + csv_rows.append(["BST", mode_name, "удаление", rep, t_end - t_start]) + +# сохранение в csv + with open("results.csv", "w", newline="", encoding="utf-8") as f: + writer = csv.writer(f) + writer.writerows(csv_rows) + print("\nвсе замеры сохранены в файл 'results.csv'.") + + show_summary(csv_rows) + +# функция для подсчета и вывода среднего времени +def show_summary(rows): + summary = defaultdict(list) + for row in rows[1:]: + struct, mode, op, rep, elapsed = row + summary[(struct, mode, op)].append(elapsed) + + print("\nСВОДНЫЕ РЕЗУЛЬТАТЫ (СРЕДНЕЕ ВРЕМЯ ИЗ 5 ЗАПУСКОВ)") + print(f"{'структура':<12} | {'режим данных':<15} | {'операция':<10} | {'время (сек)':<12}") + print("-" * 59) + for (struct, mode, op), times in sorted(summary.items()): + avg_time = sum(times) / len(times) + print(f"{struct:<12} | {mode:<15} | {op:<10} | {avg_time:.6f}") + + + +# 5. АНАЛИЗ РЕЗУЛЬТАТОВ + +def plot_results(csv_filename="results.csv"): + print("построение графика") + try: + df = pd.read_csv(csv_filename) + + df_insert = df[df["операция"] == "вставка"] + + pivot_df = df_insert.pivot_table( + index="структура", + columns="режим", + values="время (сек)", + aggfunc="mean" + ) + + pivot_df.plot(kind="bar", figsize=(10, 6), color=['#1f77b4', '#ff7f0e']) + + plt.title("сравнение времени вставки (N=3000)") + plt.ylabel("среднее время выполнения (сек)") + plt.xlabel("структура данных") + plt.xticks(rotation=0) + plt.grid(axis='y', linestyle='--', alpha=0.7) + plt.savefig("benchmark_chart.png") + print("график сохранен как benchmark_chart.png") + plt.show() + + except FileNotFoundError: + print(f"файл {csv_filename} не найден. сначала запустите тесты.") + + +def print_report(): + report = """ + + 5. АНАЛИЗ РЕЗУЛЬТАТОВ ЭКСПЕРИМЕНТОВ + +1. Влияние порядка входных данных на скорость вставки в BST: + - На случайных данных BST строится сбалансированным. Высота дерева составляет + примерно O(log N), поэтому вставка происходит почти мгновенно. + - На отсортированных данных происходит ДЕГРАДАЦИЯ дерева. Каждый элемент больше + предыдущего и вставляется строго вправо. Дерево вырождается в связный список. + Сложность возрастает до O(N), что отчетливо видно по гигантскому пику на графике. + +2. Чувствительность Хеш-таблицы к порядку: + - Хеш-таблица НЕ ЧУВСТВИТЕЛЬНА к порядку данных. Математическая хеш-функция + превращает любое имя в хаотичный индекс и равномерно распределяет записи + по бакетам. В обоих режимах операции выполняются за константное время O(1). + +3. Почему связный список всегда медленен при поиске: + - У связного списка нет индексов для прямого доступа. Поиск всегда линейный O(N) + — алгоритм вынужден последовательно перебирать элементы от головы к хвосту. + +4. Как удаление работает в каждой структуре: + - Связный список: O(N) затрачивается на линейный поиск узла, само удаление — O(1). + - Хеш-таблица: O(1) нахождение бакета по хешу, удаление из цепочки коллизий мгновенно. + - BST: В среднем O(log N), в худшем O(N). Требует поиска узла и перестройки связей + (замена удаляемого узла на его потомка или минимальный элемент правого поддерева). + +ВЫВОД: + - ДЛЯ ЧАСТЫХ ВСТАВОК И ПОИСКА: Идеально подходит Хеш-таблица благодаря скорости O(1). + - ДЛЯ ПОЛУЧЕНИЯ ДАННЫХ В ПОРЯДКЕ (АЛФАВИТНОМ): Стоит выбирать Двоичное дерево поиска + (BST), так как обход дерева (In-order traversal) сразу выдает отсортированные данные. + """ + print(report) + +def run_task_1(): + run_experiments() + plot_results() + print_report() + +# ЗАДАНИЕ 2: ПОИСК ВЫХОДА ИЗ ЛАБИРИНТА + +# поиск выхода из лабиринта +class Maze: + def __init__(self, grid): + self.grid = grid + self.rows = len(grid) + self.cols = len(grid[0]) if self.rows > 0 else 0 + self.start = self._find_point('S') + self.end = self._find_point('E') + + def _find_point(self, char): + for r in range(self.rows): + for c in range(self.cols): + if self.grid[r][c] == char: return (r, c) + return None + + def is_walkable(self, r, c): + return 0 <= r < self.rows and 0 <= c < self.cols and self.grid[r][c] != '#' + +class PathfindingStrategy(ABC): + @abstractmethod + def solve(self, maze: Maze): pass + + +# стратегия №1: BFS +class BFSStrategy(PathfindingStrategy): + def solve(self, maze: Maze): + + # проверка входных данных + if not maze.start or not maze.end: + return None, 0 + + # инициализация структур данных + queue = deque([(maze.start, [maze.start])]) + visited = set([maze.start]) + + # направление для шагов + directions = [(-1, 0), (1, 0), (0, -1), (0, 1)] + + # основной цикл обхода лабиринта + while queue: + current, path = queue.popleft() + if current == maze.end: + return path, len(visited) # возвращаем путь и размер посещенных клеток + r, c = current + for dr, dc in directions: + nr, nc = r + dr, c + dc + if maze.is_walkable(nr, nc) and (nr, nc) not in visited: + visited.add((nr, nc)) + queue.append(((nr, nc), path + [(nr, nc)])) + return None, len(visited) + + +# стратегия №2: DFS +class DFSStrategy(PathfindingStrategy): + def solve(self, maze: Maze): + if not maze.start or not maze.end: + return None, 0 + + # инициализация структур данных + stack = [(maze.start, [maze.start])] + visited = set() + directions = [(-1, 0), (1, 0), (0, -1), (0, 1)] + + # основной цикл обхода лабиринта + while stack: + current, path = stack.pop() + if current in visited: + continue + visited.add(current) + if current == maze.end: + return path, len(visited) + r, c = current + for dr, dc in directions: + nr, nc = r + dr, c + dc + if maze.is_walkable(nr, nc): + stack.append(((nr, nc), path + [(nr, nc)])) + return None, len(visited) + +class MazeSolver: + def __init__(self, strategy: PathfindingStrategy): self._strategy = strategy + def set_strategy(self, strategy: PathfindingStrategy): self._strategy = strategy + def solve_maze(self, maze: Maze): return self._strategy.solve(maze) + +# подготовка тестовых лабиринтов +class MazeFactory: + @staticmethod + def _generate_random_grid(width, height, wall_chance=0.25, has_exit=True): + # вспомогательный метод генерации сетки лабиринта заданной сложности + grid = [[" " for _ in range(width)] for _ in range(height)] + for r in range(height): + for c in range(width): + if random.random() < wall_chance: grid[r][c] = "#" + grid[0][0] = "S" + if has_exit: + grid[height-1][width-1] = "E" + else: + grid[height-1][width-1] = "E" + if height > 1: grid[height-1][width-2] = "#" + if width > 1: grid[height-2][width-1] = "#" + return grid + + @staticmethod + def create_maze(maze_type): + random.seed(42) + if maze_type == "маленький (10x10)": + return Maze(MazeFactory._generate_random_grid(10, 10, wall_chance=0.15)) + elif maze_type == "средний (50x50)": + return Maze(MazeFactory._generate_random_grid(50, 50, wall_chance=0.25)) + elif maze_type == "большой (100x100)": + return Maze(MazeFactory._generate_random_grid(100, 100, wall_chance=0.3)) + elif maze_type == "пустой": + grid = [[" " for _ in range(30)] for _ in range(30)] + grid[0][0], grid[29][29] = "S", "E" + return Maze(grid) + elif maze_type == "без выхода": + return Maze(MazeFactory._generate_random_grid(20, 20, wall_chance=0.2, has_exit=False)) + return None + +# экспериментальная часть лабиринтов +def run_maze_experiments(): + maze_types = ["маленький (10x10)", "средний (50x50)", "большой (100x100)", "пустой", "без выхода"] + strategies = [("BFS", BFSStrategy()), ("DFS", DFSStrategy())] + csv_rows = [["лабиринт", "стратегия", "время_мс", "посещено_клеток", "длина_пути"]] + + print("\nзапуск экспериментов(5 повторений)") + + for m_type in maze_types: + maze_obj = MazeFactory.create_maze(m_type) + for strat_name, strategy in strategies: + solver = MazeSolver(strategy) + + times = [] + visited_cells = 0 + path_len = 0 + + # запускаем по 5 раз для усреднения времени + for rep in range(5): + t_start = time.perf_counter() + path, visited = solver.solve_maze(maze_obj) + t_end = time.perf_counter() + + times.append((t_end - t_start) * 1000) # переводим в миллисекунды + visited_cells = visited + path_len = len(path) if path else 0 + + avg_time_ms = sum(times) / len(times) + csv_rows.append([m_type, strat_name, round(avg_time_ms, 4), visited_cells, path_len]) + + # сохраняем в CSV + with open("maze_results.csv", "w", newline="", encoding="utf-8") as f: + writer = csv.writer(f) + writer.writerows(csv_rows) + print("Результаты сохранены в 'maze_results.csv'") + + df = pd.DataFrame(csv_rows[1:], columns=csv_rows[0]) + print("\nТАБЛИЦА СРАВНЕНИЯ АЛГОРИТМОВ ПОИСКА:") + print(df.to_string(index=False)) + + # визуализация графиков + print("\nпостроение графиков эффективности") + for m_type in maze_types: + sub_df = df[df["лабиринт"] == m_type] + + fig, ax1 = plt.subplots(figsize=(7, 4)) + ax2 = ax1.twinx() + + sub_df.plot(kind="bar", x="стратегия", y="время_мс", ax=ax1, position=0, width=0.2, color="blue", legend=False) + sub_df.plot(kind="bar", x="стратегия", y="посещено_клеток", ax=ax2, position=1, width=0.2, color="orange", legend=False) + + ax1.set_ylabel("время выполнения (мс)", color="blue") + ax2.set_ylabel("посещено клеток (ед)", color="orange") + plt.title(f"эффективность на лабиринте: {m_type}") + ax1.set_xticklabels(sub_df["стратегия"], rotation=0) + + lines1, labels1 = ax1.get_legend_handles_labels() + lines2, labels2 = ax2.get_legend_handles_labels() + ax1.legend(lines1 + lines2, ["время (мс)", "посещено клеток"], loc="upper center") + + plt.tight_layout() + filename = f"maze_chart_{m_type.replace(' ', '_').replace('(', '').replace(')', '')}.png" + plt.savefig(filename) + plt.close() + print("графики для каждого типа лабиринта сохранены в текущую папку") + +def print_maze_report(): + print(""" + ВЫВОДЫ ПО ИТОГАМ АНАЛИЗА ЛАБИРИНТОВ: + 1. Маленький и Пустой лабиринты: Оба алгоритма работают мгновенно. Однако в пустом + пространстве BFS проверяет почти все клетки «волной» до достижения цели, в то время + как DFS может случайно угадать прямую траекторию быстрее, но выдать неоптимальный путь. + 2. Средний и Большой лабиринты (с тупиками): BFS стабильно находит самый КОРОТКИЙ путь, + однако тратит много памяти и времени на посещение клеток. DFS работает непредсказуемо, + его путь часто длиннее в разы, так как он «плутает» по тупикам. + 3. Лабиринт без выхода: Оба алгоритма вынуждены совершить полный перебор графа. + Количество посещенных клеток у них совпадает и равняется общему числу доступных клеток. + """) + +def run_task_2(): + run_maze_experiments() + print_maze_report() + +# главное меню + +def main(): + while True: + print("МЕНЮ ЛАБОРАТОРНЫХ РАБОТ") + print("1. Задание 1: структуры данных") + print("2. Задание 2: эксперименты с Лабиринтами") + print("0. Выход") + choice = input("Введите номер задания: ") + if choice == '1': run_task_1() + elif choice == '2': run_task_2() + elif choice == '0': break + else: print("Ошибка ввода.") + +if __name__ == "__main__": + main() \ No newline at end of file