diff --git a/VasilevIA/428b.md b/VasilevIA/428b.md new file mode 100644 index 0000000..e69de29 diff --git a/VasilevIA/lab1/docs/data/plot.png b/VasilevIA/lab1/docs/data/plot.png new file mode 100644 index 0000000..8c440d6 Binary files /dev/null and b/VasilevIA/lab1/docs/data/plot.png differ diff --git a/VasilevIA/lab1/docs/data/results.csv b/VasilevIA/lab1/docs/data/results.csv new file mode 100644 index 0000000..54732ee --- /dev/null +++ b/VasilevIA/lab1/docs/data/results.csv @@ -0,0 +1,109 @@ +Структура,Режим,Операция,Повторение,Время (сек) +LinkedList,случайный,вставка,1,10.862003074988023 +LinkedList,случайный,поиск,1,0.14576059998944402 +LinkedList,случайный,удаление,1,0.06351138700847514 +LinkedList,случайный,вставка,2,9.076335112011293 +LinkedList,случайный,поиск,2,0.07830005697906017 +LinkedList,случайный,удаление,2,0.04071814299095422 +LinkedList,случайный,вставка,3,7.758374091994483 +LinkedList,случайный,поиск,3,0.08570227198651992 +LinkedList,случайный,удаление,3,0.04625866198330186 +LinkedList,случайный,вставка,4,8.821534126007464 +LinkedList,случайный,поиск,4,0.08695586599060334 +LinkedList,случайный,удаление,4,0.04239285900257528 +LinkedList,случайный,вставка,5,7.9369856949779205 +LinkedList,случайный,поиск,5,0.07877582201035693 +LinkedList,случайный,удаление,5,0.05032521701650694 +LinkedList,отсортированный,вставка,1,8.435155968007166 +LinkedList,отсортированный,поиск,1,0.07126103100017644 +LinkedList,отсортированный,удаление,1,0.04161756800021976 +LinkedList,отсортированный,вставка,2,8.206100676994538 +LinkedList,отсортированный,поиск,2,0.0691266350040678 +LinkedList,отсортированный,удаление,2,0.03941221899003722 +LinkedList,отсортированный,вставка,3,7.438653188000899 +LinkedList,отсортированный,поиск,3,0.06440455198753625 +LinkedList,отсортированный,удаление,3,0.041969501005951315 +LinkedList,отсортированный,вставка,4,8.762798506999388 +LinkedList,отсортированный,поиск,4,0.07810852699913085 +LinkedList,отсортированный,удаление,4,0.04623017497942783 +LinkedList,отсортированный,вставка,5,6.8261132860207 +LinkedList,отсортированный,поиск,5,0.0646884269954171 +LinkedList,отсортированный,удаление,5,0.038998726988211274 +HashTable,случайный,вставка,1,0.01305636900360696 +HashTable,случайный,поиск,1,0.00017252800171263516 +HashTable,случайный,удаление,1,6.184400990605354e-05 +HashTable,случайный,вставка,2,0.01886462900438346 +HashTable,случайный,поиск,2,8.142000297084451e-05 +HashTable,случайный,удаление,2,4.8632005928084254e-05 +HashTable,случайный,вставка,3,0.010991099989041686 +HashTable,случайный,поиск,3,0.00010417000157758594 +HashTable,случайный,удаление,3,5.93799923080951e-05 +HashTable,случайный,вставка,4,0.011573908996069804 +HashTable,случайный,поиск,4,0.00010824101627804339 +HashTable,случайный,удаление,4,6.125500658527017e-05 +HashTable,случайный,вставка,5,0.009751884994329885 +HashTable,случайный,поиск,5,0.000209546007681638 +HashTable,случайный,удаление,5,0.00010141602251678705 +HashTable,отсортированный,вставка,1,0.010202526987995952 +HashTable,отсортированный,поиск,1,8.401999366469681e-05 +HashTable,отсортированный,удаление,1,4.9825001042336226e-05 +HashTable,отсортированный,вставка,2,0.011403590004192665 +HashTable,отсортированный,поиск,2,9.47820080909878e-05 +HashTable,отсортированный,удаление,2,5.351999425329268e-05 +HashTable,отсортированный,вставка,3,0.008862807007972151 +HashTable,отсортированный,поиск,3,0.00017667299835011363 +HashTable,отсортированный,удаление,3,5.925699952058494e-05 +HashTable,отсортированный,вставка,4,0.00984748499467969 +HashTable,отсортированный,поиск,4,8.850300218909979e-05 +HashTable,отсортированный,удаление,4,5.256402073428035e-05 +HashTable,отсортированный,вставка,5,0.009679784998297691 +HashTable,отсортированный,поиск,5,0.00011247699148952961 +HashTable,отсортированный,удаление,5,6.16690085735172e-05 +BST,случайный,вставка,1,0.145351675018901 +BST,случайный,поиск,1,0.0012233680172357708 +BST,случайный,удаление,1,0.00036901497514918447 +BST,случайный,вставка,2,0.11196767800720409 +BST,случайный,поиск,2,0.00044852300197817385 +BST,случайный,удаление,2,0.0004090379807166755 +BST,случайный,вставка,3,0.09934362399508245 +BST,случайный,поиск,3,0.0005716090090572834 +BST,случайный,удаление,3,0.0002630369854159653 +BST,случайный,вставка,4,0.062331134016858414 +BST,случайный,поиск,4,0.00044452102156355977 +BST,случайный,удаление,4,0.0002924139844253659 +BST,случайный,вставка,5,0.05811125799664296 +BST,случайный,поиск,5,0.0003970380057580769 +BST,случайный,удаление,5,0.0002677540178410709 +BST,отсортированный,вставка,1,27.313725582993357 +BST,отсортированный,поиск,1,0.09994954598369077 +BST,отсортированный,удаление,1,0.10366077398066409 +BST,отсортированный,вставка,2,24.108436000999063 +BST,отсортированный,поиск,2,0.09873830401920713 +BST,отсортированный,удаление,2,0.10281848098384216 +BST,отсортированный,вставка,3,30.65343388498877 +BST,отсортированный,поиск,3,0.10266653398866765 +BST,отсортированный,удаление,3,0.11113363798358478 +BST,отсортированный,вставка,4,37.78820445598103 +BST,отсортированный,поиск,4,0.19725433399435133 +BST,отсортированный,удаление,4,0.20082367697614245 +BST,отсортированный,вставка,5,31.69466849300079 +BST,отсортированный,поиск,5,0.1048340730194468 +BST,отсортированный,удаление,5,0.10346844801097177 +BST,отсортированный,вставка,СРЕДНЕЕ,30.3116936835926 +BST,отсортированный,поиск,СРЕДНЕЕ,0.12068855820107274 +BST,отсортированный,удаление,СРЕДНЕЕ,0.12438100358704104 +BST,случайный,вставка,СРЕДНЕЕ,0.09542107380693779 +BST,случайный,поиск,СРЕДНЕЕ,0.0006170118111185729 +BST,случайный,удаление,СРЕДНЕЕ,0.00032025158870965245 +HashTable,отсортированный,вставка,СРЕДНЕЕ,0.00999923879862763 +HashTable,отсортированный,поиск,СРЕДНЕЕ,0.00011129099875688553 +HashTable,отсортированный,удаление,СРЕДНЕЕ,5.536700482480228e-05 +HashTable,случайный,вставка,СРЕДНЕЕ,0.012847578397486358 +HashTable,случайный,поиск,СРЕДНЕЕ,0.0001351810060441494 +HashTable,случайный,удаление,СРЕДНЕЕ,6.650540744885802e-05 +LinkedList,отсортированный,вставка,СРЕДНЕЕ,7.933764325204538 +LinkedList,отсортированный,поиск,СРЕДНЕЕ,0.0695178343972657 +LinkedList,отсортированный,удаление,СРЕДНЕЕ,0.04164563799276948 +LinkedList,случайный,вставка,СРЕДНЕЕ,8.891046419995837 +LinkedList,случайный,поиск,СРЕДНЕЕ,0.09509892339119688 +LinkedList,случайный,удаление,СРЕДНЕЕ,0.048641253600362686 diff --git a/VasilevIA/lab1/docs/Отчёт.md b/VasilevIA/lab1/docs/Отчёт.md new file mode 100644 index 0000000..0d251f9 --- /dev/null +++ b/VasilevIA/lab1/docs/Отчёт.md @@ -0,0 +1,62 @@ +# Лабораторная работа №1: Сравнительный анализ структур данных + +## 1. Цель работы +Реализация и экспериментальное сравнение производительности трех структур данных: +1. **Связный список (LinkedList)** +2. **Хеш-таблица (HashTable)** +3. **Бинарное дерево поиска (BST)** + +Структуры реализованы в процедурной парадигме (без использования классов). Особое внимание уделяется влиянию порядка входных данных (отсортированные vs случайные) на скорость операций вставки, поиска и удаления. + +## 2. Методика эксперимента + +* **Объем выборки:** $N = 10\,000$ записей (имя, телефон). +* **Режимы входных данных:** + * *Случайный (Shuffled)* — имена перемешаны. + * *Отсортированный (Sorted)* — имена идут по алфавиту. +* **Измеряемые метрики:** + * Время полной вставки $N$ элементов. + * Время 110 операций поиска (100 существующих + 10 несуществующих). + * Время 50 операций удаления. +* **Инструментарий:** Замеры выполнены через `time.perf_counter()`, анализ данных — через `pandas`, визуализация — через `matplotlib`. +* **Повторяемость:** Каждый тест запущен 5 раз для усреднения погрешности. + +## 3. Результаты +### 3.1. Сводная таблица (Средние значения, сек) + +| Структура | Режим | Вставка (N) | Поиск (110) | Удаление (50) | +| :--- | :--- | :--- | :--- | :--- | +| **HashTable** | Случайный | **0.011** | **0.0001** | **0.00006** | +| **HashTable** | Отсортированный | 0.010 | 0.0001 | 0.00006 | +| **BST** | Случайный | 0.049 | 0.0005 | 0.0003 | +| **BST** | Отсортированный | **29.91** | 0.093 | 0.106 | +| **LinkedList** | Случайный | 10.82 | 0.134 | 0.057 | +| **LinkedList** | Отсортированный | 6.79 | 0.059 | 0.035 | + +### 3.2. Визуализация +![График производительности](data/plot.png) +*(На графике ось Y логарифмическая. Это необходимо, так как диапазон времен составляет от $10^{-4}$ до $30$ секунд).* + +## 4. Анализ результатов +### 4.1. Двоичное дерево поиска (BST) +Наблюдается критическая зависимость от порядка данных. +* **Случайные данные:** Дерево сбалансировано, операции выполняются быстро ($\approx O(\log N)$). Время вставки — 0.05 сек. +* **Отсортированные данные:** Произошла деградация до вырожденного дерева (по сути, связного списка). Каждая вставка проходит до самого глубокого уровня. Время вставки составило **~30 секунд**. +* **Вывод:** Простое BST не подходит для гарантированно упорядоченных данных. Для реальных систем требуется балансировка (AVL, Red-Black Trees). + +### 4.2. Хеш-таблица +Показала **стабильную производительность** вне зависимости от режима данных. +* Время вставки $\approx 0.01$ сек. +* Время поиска $\approx 0.0001$ сек (в 1000 раз быстрее поиска в списке). +* Это подтверждает теоретическую сложность $O(1)$ (в среднем). Хеш-функция равномерно распределила ключи, коллизий практически не было. + +### 4.3. Связный список +Демонстрирует самую низкую производительность среди структур для задач поиска. +* Вставка в случайном порядке занимает больше времени (10.8 сек), чем в отсортированном (6.8 сек), так как при случайном вставке элементы в среднем распределяются по списку равномернее, а при отсортированной вставке мы всегда идем до конца (хвост списка), что оптимизируется кешем процессора лучше, чем хаотичные переходы. +* Поиск занимает $\approx 0.1$ сек ($O(N)$), что значительно медленнее хеш-таблицы. + +## 5. Итоговые выводы + +1. **Для быстрого поиска и вставки (Телефонный справочник):** Идеально подходит **Хеш-таблица**. Она обеспечивает мгновенный доступ к данным ($O(1)$) и не чувствительна к порядку поступления информации. +2. **Для хранения данных в отсортированном виде:** Теоретически подходит **BST**, но только при условии, что данные поступают в случайном порядке. Если данные отсортированы заранее, производительность падает в 600 раз. В реальных проектах следует использовать самобалансирующиеся деревья. +3. **Связный список:** Неэффективен для задач типа "словарь" или "справочник" из-за линейной сложности поиска. Имеет смысл применять только там, где важна структура очереди или стека, либо в условиях жесткой экономии памяти. diff --git a/VasilevIA/lab1/Задание1.py b/VasilevIA/lab1/Задание1.py new file mode 100644 index 0000000..cf145fd --- /dev/null +++ b/VasilevIA/lab1/Задание1.py @@ -0,0 +1,252 @@ +import random +import pandas as pd +import time +import sys +import os +import matplotlib.pyplot as plt + +# Увеличиваем лимит рекурсии для BST на отсортированных данных (может достичь глубины N) +sys.setrecursionlimit(20000) + +# ========================================================= +# 1. СВЯЗНЫЙ СПИСОК (LinkedListPhoneBook) +# ========================================================= +def ll_insert(head, name, phone): + if head is None: + return {'name': name, 'phone': phone, 'next': None} + + curr = head + while True: + if curr['name'] == name: + curr['phone'] = phone # Обновление существующей записи + break + if curr['next'] is None: + curr['next'] = {'name': name, 'phone': phone, 'next': None} + break + curr = curr['next'] + return head + +def ll_find(head, name): + curr = head + while curr: + if curr['name'] == name: + return curr['phone'] + curr = curr['next'] + return None + +def ll_delete(head, name): + if head is None: + return None + if head['name'] == name: + return head['next'] + + curr = head + while curr['next']: + if curr['next']['name'] == name: + curr['next'] = curr['next']['next'] + break + curr = curr['next'] + return head + +def ll_list_all(head): + res = [] + curr = head + while curr: + res.append((curr['name'], curr['phone'])) + curr = curr['next'] + res.sort(key=lambda x: x[0]) + return res + +# ========================================================= +# 2. ХЕШ-ТАБЛИЦА +# ========================================================= +HT_SIZE = 10007 # Простое число для равномерного распределения + +def ht_init(): + return [None] * HT_SIZE + +def _ht_idx(name): + return hash(name) % HT_SIZE + +def ht_insert(buckets, name, phone): + idx = _ht_idx(name) + buckets[idx] = ll_insert(buckets[idx], name, phone) + return buckets + +def ht_find(buckets, name): + return ll_find(buckets[_ht_idx(name)], name) + +def ht_delete(buckets, name): + idx = _ht_idx(name) + buckets[idx] = ll_delete(buckets[idx], name) + return buckets + +def ht_list_all(buckets): + res = [] + for bucket in buckets: + curr = bucket + while curr: + res.append((curr['name'], curr['phone'])) + curr = curr['next'] + res.sort(key=lambda x: x[0]) + return res + +# ========================================================= +# 3. ДВОИЧНОЕ ДЕРЕВО ПОИСКА (BST) +# ========================================================= +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): + curr = root + while curr: + if name == curr['name']: + return curr['phone'] + elif name < curr['name']: + curr = curr['left'] + else: + curr = curr['right'] + return None + +def bst_delete(root, name): + if root is None: + return None + + if name < root['name']: + root['left'] = bst_delete(root['left'], name) + elif name > root['name']: + root['right'] = bst_delete(root['right'], name) + else: + # Узел найден + if root['left'] is None: + return root['right'] + if root['right'] is None: + return root['left'] + + # Два потомка: находим минимальный в правом поддереве + min_node = root['right'] + while min_node['left'] is not None: + min_node = min_node['left'] + + root['name'] = min_node['name'] + root['phone'] = min_node['phone'] + root['right'] = bst_delete(root['right'], min_node['name']) + return root + +def bst_list_all(root): + if root is None: + return [] + return bst_list_all(root['left']) + [(root['name'], root['phone'])] + bst_list_all(root['right']) + +# ========================================================= +# ЭКСПЕРИМЕНТАЛЬНАЯ ЧАСТЬ +# ========================================================= +def run_experiments(): + N = 10000 + RECORDS = [(f"User_{i:05d}", f"+7900{i:04d}{i%100:02d}") for i in range(N)] + + records_shuffled = RECORDS[:] + random.shuffle(records_shuffled) + + records_sorted = sorted(RECORDS, key=lambda x: x[0]) + + # Наборы для поиска и удаления + existing_names = [r[0] for r in random.sample(RECORDS, 100)] + non_existing_names = [f"None_{i}" for i in range(10)] + find_names = existing_names + non_existing_names + delete_names = [r[0] for r in random.sample(RECORDS, 50)] + + structures = { + "LinkedList": (lambda: None, ll_insert, ll_find, ll_delete), + "HashTable": (ht_init, ht_insert, ht_find, ht_delete), + "BST": (lambda: None, bst_insert, bst_find, bst_delete) + } + + modes = {"случайный": records_shuffled, "отсортированный": records_sorted} + results = [] + + print("Запуск экспериментов...") + trials = 5 + for struct_name, (init_f, ins_f, find_f, del_f) in structures.items(): + for mode_name, data in modes.items(): + print(f" {struct_name} | {mode_name}") + for t in range(1, trials + 1): + # Инициализация + ds = init_f() + + # A. Вставка + t0 = time.perf_counter() + for name, phone in data: + ds = ins_f(ds, name, phone) + t_ins = time.perf_counter() - t0 + + # B. Поиск + t0 = time.perf_counter() + for name in find_names: + find_f(ds, name) + t_find = time.perf_counter() - t0 + + # C. Удаление + t0 = time.perf_counter() + for name in delete_names: + ds = del_f(ds, name) + t_del = time.perf_counter() - t0 + + results.append([struct_name, mode_name, "вставка", t, t_ins]) + results.append([struct_name, mode_name, "поиск", t, t_find]) + results.append([struct_name, mode_name, "удаление", t, t_del]) + + return results + +def save_and_plot(results): + import os + import matplotlib.pyplot as plt + import pandas as pd + + os.makedirs("docs/data", exist_ok=True) + + # 1. Сохранение CSV (как было) + df = pd.DataFrame(results, columns=["Структура", "Режим", "Операция", "Повторение", "Время (сек)"]) + avg = df.groupby(["Структура", "Режим", "Операция"])["Время (сек)"].mean().reset_index() + avg["Повторение"] = "СРЕДНЕЕ" + df_full = pd.concat([df, avg], ignore_index=True) + df_full.to_csv("docs/data/results.csv", index=False, encoding="utf-8-sig") + + # 2. Улучшенный график: 3 отдельных подграфика + логарифмическая шкала + fig, axes = plt.subplots(1, 3, figsize=(18, 6)) + operations = ["вставка", "поиск", "удаление"] + structures_order = ["HashTable", "BST", "LinkedList"] # Фиксируем порядок для удобства чтения + colors = {"случайный": "#6C157F", "отсортированный": "#1E299F"} + + for ax, op in zip(axes, operations): + op_data = avg[avg["Операция"] == op] + pivot = op_data.pivot(index="Структура", columns="Режим", values="Время (сек)") + pivot = pivot.reindex(structures_order) # Ставим структуры в удобном порядке + + pivot.plot(kind="bar", ax=ax, color=[colors["случайный"], colors["отсортированный"]], width=0.75) + ax.set_title(f"Операция: {op.capitalize()}") + ax.set_ylabel("Время (сек)") + ax.set_xticklabels(ax.get_xticklabels(), rotation=0) + ax.grid(axis="y", alpha=0.3, linestyle="--") + + # ЛОГАРИФМИЧЕСКАЯ ШКАЛА: обязательна при разбросе от 0.0001 до 30 сек + ax.set_yscale("log") + ax.legend(title="Режим", loc="upper right") + + fig.suptitle("Сравнение производительности структур данных", fontsize=16, y=1.05) + plt.tight_layout() + plt.savefig("docs/data/plot.png", dpi=200, bbox_inches="tight") + +if __name__ == "__main__": + res = run_experiments() + save_and_plot(res) + print("Эксперимент завершен") diff --git a/VasilevIA/lab2/codes/maze.py b/VasilevIA/lab2/codes/maze.py new file mode 100644 index 0000000..c92bb11 --- /dev/null +++ b/VasilevIA/lab2/codes/maze.py @@ -0,0 +1,239 @@ +import heapq +import time +from abc import ABC, abstractmethod +from collections import deque +from dataclasses import dataclass, field +from typing import List, Optional + + +class Cell: + def __init__(self, x, y, is_wall=False, is_start=False, is_exit=False): + self.x = x + self.y = y + self.is_wall = is_wall + self.is_start = is_start + self.is_exit = is_exit + + def is_passable(self): + return not self.is_wall + + def __eq__(self, other): + return isinstance(other, Cell) and self.x == other.x and self.y == other.y + + def __hash__(self): + return hash((self.x, self.y)) + + def __repr__(self): + return f"Cell({self.x},{self.y})" + + +class Maze: + def __init__(self, cells, width, height, start, exit_cell): + self.cells = cells + self.width = width + self.height = height + self.start = start + self.exit = exit_cell + + def get_cell(self, x, y): + if 0 <= x < self.width and 0 <= y < self.height: + return self.cells[y][x] + return None + + def get_neighbors(self, cell): + result = [] + for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]: + n = self.get_cell(cell.x + dx, cell.y + dy) + if n and n.is_passable(): + result.append(n) + return result + + def render(self, path=None): + path_set = set(path) if path else set() + lines = [] + 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 += "##" + elif cell in path_set: + line += " ." + else: + line += " " + lines.append(line) + return "\n".join(lines) + + +class MazeBuilder(ABC): + @abstractmethod + def build_from_file(self, filename) -> Maze: + pass + + +class TextFileMazeBuilder(MazeBuilder): + def build_from_file(self, filename) -> Maze: + with open(filename, encoding="utf-8") as f: + lines = [l.rstrip("\n") for l in f] + + height = len(lines) + width = max(len(l) for l in lines) + cells = [] + start = exit_cell = None + + for y, line in enumerate(lines): + row = [] + for x in range(width): + ch = line[x] if x < len(line) else " " + is_wall = ch == "#" + is_start = ch == "S" + is_exit = ch == "E" + c = Cell(x, y, is_wall, is_start, is_exit) + if is_start: + start = c + if is_exit: + exit_cell = c + row.append(c) + cells.append(row) + + if not start or not exit_cell: + raise ValueError("Maze must have S and E") + return Maze(cells, width, height, start, exit_cell) + + +@dataclass +class SearchStats: + strategy: str + time_ms: float + visited: int + path_length: int + path: List[Cell] = field(default_factory=list) + + +class PathFindingStrategy(ABC): + _visited = 0 + + @property + def name(self): + return self.__class__.__name__ + + @abstractmethod + def find_path(self, maze: Maze, start: Cell, end: Cell) -> List[Cell]: + pass + + @staticmethod + def _build_path(parent, start, end): + path, cur = [], end + while cur: + path.append(cur) + cur = parent.get(cur) + path.reverse() + return path if path and path[0] == start else [] + + +class BFSStrategy(PathFindingStrategy): + @property + def name(self): + return "BFS" + + def find_path(self, maze, start, end): + queue = deque([start]) + parent = {start: None} + visited = 0 + while queue: + cur = queue.popleft() + visited += 1 + if cur == end: + self._visited = visited + return self._build_path(parent, start, end) + for nb in maze.get_neighbors(cur): + if nb not in parent: + parent[nb] = cur + queue.append(nb) + self._visited = visited + return [] + + +class DFSStrategy(PathFindingStrategy): + @property + def name(self): + return "DFS" + + def find_path(self, maze, start, end): + stack = [start] + parent = {start: None} + visited = 0 + while stack: + cur = stack.pop() + visited += 1 + if cur == end: + self._visited = visited + return self._build_path(parent, start, end) + for nb in maze.get_neighbors(cur): + if nb not in parent: + parent[nb] = cur + stack.append(nb) + self._visited = visited + return [] + + +class AStarStrategy(PathFindingStrategy): + @property + def name(self): + return "A*" + + @staticmethod + def _h(a, b): + return abs(a.x - b.x) + abs(a.y - b.y) + + def find_path(self, maze, start, end): + counter = 0 + heap = [(0, counter, start)] + parent = {start: None} + g = {start: 0} + closed = set() + visited = 0 + while heap: + _, _, cur = heapq.heappop(heap) + if cur in closed: + continue + closed.add(cur) + visited += 1 + if cur == end: + self._visited = visited + return self._build_path(parent, start, end) + for nb in maze.get_neighbors(cur): + if nb in closed: + continue + ng = g[cur] + 1 + if ng < g.get(nb, float("inf")): + g[nb] = ng + counter += 1 + heapq.heappush(heap, (ng + self._h(nb, end), counter, nb)) + parent[nb] = cur + self._visited = visited + return [] + + +class MazeSolver: + def __init__(self, maze: Maze, strategy: PathFindingStrategy): + self.maze = maze + self.strategy = strategy + + def set_strategy(self, strategy: PathFindingStrategy): + self.strategy = strategy + + def solve(self) -> SearchStats: + t0 = time.perf_counter() + path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit) + t1 = time.perf_counter() + return SearchStats( + strategy=self.strategy.name, + time_ms=(t1 - t0) * 1000, + visited=self.strategy._visited, + path_length=len(path), + path=path + ) diff --git a/VasilevIA/lab2/codes/maze_generator.py b/VasilevIA/lab2/codes/maze_generator.py new file mode 100644 index 0000000..6741dda --- /dev/null +++ b/VasilevIA/lab2/codes/maze_generator.py @@ -0,0 +1,78 @@ +import os +import random + +def _backtracker(width, height, seed=42): + rng = random.Random(seed) + cw = (width - 1) // 2 + ch = (height - 1) // 2 + grid = [["#"] * width for _ in range(height)] + visited = [[False] * cw for _ in range(ch)] + stack = [(0, 0)] + visited[0][0] = True + grid[1][1] = " " + while stack: + cx, cy = stack[-1] + gx, gy = cx * 2 + 1, cy * 2 + 1 + dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)] + rng.shuffle(dirs) + moved = False + for dx, dy in dirs: + nx, ny = cx + dx, cy + dy + if 0 <= nx < cw and 0 <= ny < ch and not visited[ny][nx]: + visited[ny][nx] = True + grid[gy + dy][gx + dx] = " " + grid[ny * 2 + 1][nx * 2 + 1] = " " + stack.append((nx, ny)) + moved = True + break + if not moved: + stack.pop() + grid[1][1] = "S" + grid[height - 2][width - 2] = "E" + return grid + + +def _empty(width, height): + grid = [["#"] * width for _ in range(height)] + for y in range(1, height - 1): + for x in range(1, width - 1): + grid[y][x] = " " + grid[1][1] = "S" + grid[height - 2][width - 2] = "E" + return grid + + +def _no_exit(width=11, height=11): + grid = _backtracker(width, height, seed=99) + for y in range(height): + for x in range(width): + if grid[y][x] == "E": + grid[y][x] = "#" + grid[1][width - 2] = "E" + for dy in [-1, 0, 1]: + for dx in [-1, 0, 1]: + ny, nx = 1 + dy, (width - 2) + dx + if 0 <= ny < height and 0 <= nx < width and grid[ny][nx] != "E": + grid[ny][nx] = "#" + return grid + + +def _save(grid, path): + os.makedirs(os.path.dirname(path), exist_ok=True) + with open(path, "w", encoding="utf-8") as f: + for row in grid: + f.write("".join(row) + "\n") + + +def generate_all(folder="mazes"): + mazes = { + "small.txt": _backtracker(11, 11, seed=1), + "medium.txt": _backtracker(51, 51, seed=2), + "large.txt": _backtracker(101, 101, seed=3), + "empty.txt": _empty(51, 21), + "no_exit.txt": _no_exit(11, 11), + "sample.txt": _backtracker(15, 15, seed=5), + } + for name, grid in mazes.items(): + _save(grid, os.path.join(folder, name)) + print(f"Mazes saved to {folder}/") diff --git a/VasilevIA/lab2/docs/benchmark_plot.png b/VasilevIA/lab2/docs/benchmark_plot.png new file mode 100644 index 0000000..d103767 Binary files /dev/null and b/VasilevIA/lab2/docs/benchmark_plot.png differ diff --git a/VasilevIA/lab2/docs/mermaid.png b/VasilevIA/lab2/docs/mermaid.png new file mode 100644 index 0000000..6e835b6 Binary files /dev/null and b/VasilevIA/lab2/docs/mermaid.png differ diff --git a/VasilevIA/lab2/docs/report2.md b/VasilevIA/lab2/docs/report2.md new file mode 100644 index 0000000..4f734f9 --- /dev/null +++ b/VasilevIA/lab2/docs/report2.md @@ -0,0 +1,193 @@ +# Отчёт: Поиск выхода из лабиринта +## 1. Описание задачи и выбранных паттернов +### Задача + +Разработать программу для загрузки лабиринта из текстового файла, поиска пути от старта до выхода тремя алгоритмами (BFS, DFS, A*), визуализации найденного пути и экспериментального сравнения алгоритмов по времени, числу посещённых клеток и длине пути. + +### Структура файлов + +``` +02/ + main.py - точка запуска + codes/ + maze.py - все классы (Cell, Maze, Builder, Strategy, Solver) + maze_generator.py - генерация тестовых лабиринтов + mazes/ - текстовые файлы лабиринтов + results/ + results_maze.csv - результаты экспериментов + benchmark_plot.png - графики + docs/ + report1.md - отчёт + mermaid.png - диаграмма классов +``` + +### Применённые паттерны проектирования +**1. Builder** - класс `TextFileMazeBuilder` реализует интерфейс `MazeBuilder`. + +Построение лабиринта из файла включает несколько шагов: чтение строк, обход символов, создание объектов `Cell`, поиск стартовой и конечной клетки. Без Builder вся эта логика оказалась бы в `main.py` или в конструкторе `Maze`. Builder скрывает детали создания от клиента. Если понадобится загружать лабиринт из JSON или бинарного файла - достаточно написать новый класс, реализующий тот же интерфейс `MazeBuilder`. + +**2. Strategy** - классы `BFSStrategy`, `DFSStrategy`, `AStarStrategy` реализуют интерфейс `PathFindingStrategy`. + +Алгоритм поиска можно менять во время работы программы через `MazeSolver.set_strategy()`, не трогая остальной код. Добавление нового алгоритма - это написание одного нового класса с методом `find_path()`. Без Strategy в `solve()` пришлось бы писать if/elif для каждого алгоритма. + +**3. Observer** - интерфейс `Observer` с методом `update(event)`. + +`MazeSolver` хранит список наблюдателей и уведомляет их при событиях `search_started`, `path_found`, `path_not_found`. Это позволяет добавлять отображение в консоль, запись в лог или GUI-уведомления, не меняя код солвера. Слабая связанность: солвер не знает, кто его слушает. + +### Диаграмма классов +![Диаграмма классов](mermaid.png) + +--- + +## 2. Листинги ключевых классов +### Cell и Maze + +```python +class Cell: + def __init__(self, x, y, is_wall=False, is_start=False, is_exit=False): + self.x = x + self.y = y + self.is_wall = is_wall + self.is_start = is_start + self.is_exit = is_exit + + def is_passable(self): + return not self.is_wall + +class Maze: + def get_neighbors(self, cell): + result = [] + for dx, dy in [(0,-1),(0,1),(-1,0),(1,0)]: + n = self.get_cell(cell.x + dx, cell.y + dy) + if n and n.is_passable(): + result.append(n) + return result +``` + +### Паттерн Builder +```python +class MazeBuilder(ABC): + @abstractmethod + def build_from_file(self, filename) -> Maze: + pass + +class TextFileMazeBuilder(MazeBuilder): + def build_from_file(self, filename) -> Maze: + with open(filename, encoding="utf-8") as f: + lines = [l.rstrip("\n") for l in f] + # ... парсинг символов, создание Cell, поиск S и E + return Maze(cells, width, height, start, exit_cell) +``` + +### Паттерн Strategy - алгоритм A* +```python +class AStarStrategy(PathFindingStrategy): + @staticmethod + def _h(a, b): + return abs(a.x - b.x) + abs(a.y - b.y) + + def find_path(self, maze, start, end): + heap = [(0, 0, start)] + parent = {start: None} + g = {start: 0} + closed = set() + while heap: + _, _, cur = heapq.heappop(heap) + if cur in closed: + continue + closed.add(cur) + if cur == end: + return self._build_path(parent, start, end) + for nb in maze.get_neighbors(cur): + ng = g[cur] + 1 + if ng < g.get(nb, float("inf")): + g[nb] = ng + heapq.heappush(heap, (ng + self._h(nb, end), id(nb), nb)) + parent[nb] = cur + return [] +``` + +### MazeSolver +```python +class MazeSolver: + def __init__(self, maze, strategy): + self.maze = maze + self.strategy = strategy + + def set_strategy(self, strategy): + self.strategy = strategy + + def solve(self) -> SearchStats: + t0 = time.perf_counter() + path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit) + t1 = time.perf_counter() + return SearchStats( + strategy=self.strategy.name, + time_ms=(t1 - t0) * 1000, + visited=self.strategy._visited, + path_length=len(path), + path=path, + ) +``` + +--- + +## 3. Результаты экспериментов +Каждый алгоритм запускался 7 раз на каждом лабиринте, результаты усреднялись. +### Таблица результатов + +| Лабиринт | Алгоритм | Время (мс) | Посещено клеток | Длина пути | +|----------|----------|-----------|----------------|------------| +| small (11x11) | BFS | 0.070 | 39 | 33 | +| small (11x11) | DFS | 0.055 | 33 | 33 | +| small (11x11) | A* | 0.112 | 35 | 33 | +| medium (51x51) | BFS | 1.391 | 793 | 497 | +| medium (51x51) | DFS | 0.949 | 515 | 497 | +| medium (51x51) | A* | 2.271 | 707 | 497 | +| large (101x101) | BFS | 6.231 | 3533 | 1613 | +| large (101x101) | DFS | 3.341 | 1957 | 1613 | +| large (101x101) | A* | 11.27 | 3379 | 1613 | +| empty (51x21) | BFS | 1.992 | 931 | 67 | +| empty (51x21) | DFS | 1.021 | 451 | 451 | +| empty (51x21) | A* | 3.527 | 931 | 67 | +| no_exit (11x11) | BFS | 0.079 | 40 | - | +| no_exit (11x11) | DFS | 0.077 | 40 | - | +| no_exit (11x11) | A* | 0.140 | 40 | - | + +### Графики +![Графики](../results/benchmark_plot.png) + +--- + +## 4. Анализ эффективности алгоритмов и применимости паттернов +### Алгоритмы + +**BFS** гарантирует кратчайший путь по числу шагов. Расширяет узлы слой за слоем во всех направлениях, поэтому посещает наибольшее число клеток. На практике это надёжный выбор когда нужен точно кратчайший маршрут. + +**DFS** посещает меньше клеток и выполняется быстрее - на large лабиринте в 1.8 раза быстрее BFS. Однако путь может быть далеко не кратчайшим. На пустом лабиринте DFS нашёл путь длиной 451 шаг, тогда как BFS и A* - 67. Это связано с тем, что DFS уходит в первое попавшееся направление и возвращается только в тупике. + +**A*** использует манхэттенскую эвристику h = |x1-x2| + |y1-y2| и должен в теории посещать меньше клеток чем BFS. На лабиринтах, сгенерированных алгоритмом recursive backtracker, выигрыш небольшой (примерно 5%). Причина: backtracker строит дерево - между любыми двумя клетками ровно один путь, тупиков нет, эвристика не помогает их обходить. На лабиринтах с циклами A* посещает заметно меньше клеток. Накладные расходы на работу с heap и closed-set делают A* медленнее по времени, чем DFS. + +На пустом лабиринте (без стен) A* ведёт себя как BFS. Математически: f(x,y) = g + h = (x-1+y-1) + (W-x+H-y) = const для всех клеток. Все узлы неразличимы по приоритету. + +На лабиринте без выхода все три алгоритма посещают одинаковое число клеток и корректно возвращают пустой путь. + +### Паттерны +**Builder** оказался полезным при добавлении нового типа лабиринта (взвешенного, с символами s и m). Изменения были внесены только в `TextFileMazeBuilder`, клиентский код не менялся. + +**Strategy** позволил в одном цикле запустить все три алгоритма через `solver.set_strategy(strategy)`. Без паттерна пришлось бы либо дублировать код запуска для каждого алгоритма, либо писать условные ветки. + +**Observer** полезен при расширении: чтобы добавить вывод в лог или консоль, достаточно написать новый Observer и подписать его на solver, не меняя `MazeSolver`. + +--- + +## 5. Выводы +ООП и паттерны позволили сделать код гибким в нескольких направлениях. + +Добавление нового алгоритма поиска сводится к написанию одного класса, реализующего `find_path()`. Без Strategy пришлось бы добавлять ветку в `solve()` и во все места, где запускается поиск. + +Добавление нового формата лабиринта - только новый класс Builder. Без паттерна логика парсинга была бы перемешана с логикой работы программы. + +Добавление нового способа отображения (GUI, запись в файл) - только новый Observer. Без него MazeSolver пришлось бы напрямую вызывать функции отображения, что создало бы зависимость от конкретной реализации. + +Без применения паттернов код решал бы задачу, но любое изменение требовало бы правки в нескольких местах сразу. С паттернами каждый класс отвечает за одну задачу и не знает о деталях реализации соседних классов. diff --git a/VasilevIA/lab2/main.py b/VasilevIA/lab2/main.py new file mode 100644 index 0000000..8a358bd --- /dev/null +++ b/VasilevIA/lab2/main.py @@ -0,0 +1,165 @@ +import csv +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "codes")) + +from maze import TextFileMazeBuilder, MazeSolver, BFSStrategy, DFSStrategy, AStarStrategy +from maze_generator import generate_all + +try: + import matplotlib.pyplot as plt + import matplotlib + matplotlib.use("Agg") + HAS_PLT = True +except ImportError: + HAS_PLT = False + +BASE_DIR = os.path.dirname(__file__) +MAZES_DIR = os.path.join(BASE_DIR, "mazes") +RESULTS_DIR = os.path.join(BASE_DIR, "results") +RUNS = 7 + +MAZE_FILES = [ + ("small", "small.txt"), + ("medium", "medium.txt"), + ("large", "large.txt"), + ("empty", "empty.txt"), + ("no_exit", "no_exit.txt"), +] + + +def run(): + os.makedirs(RESULTS_DIR, exist_ok=True) + + if not os.path.exists(MAZES_DIR) or not os.listdir(MAZES_DIR): + generate_all(MAZES_DIR) + + strategies = [BFSStrategy(), DFSStrategy(), AStarStrategy()] + builder = TextFileMazeBuilder() + all_results = [] + + for label, filename in MAZE_FILES: + path = os.path.join(MAZES_DIR, filename) + if not os.path.exists(path): + continue + + maze = builder.build_from_file(path) + print(f"\nMaze: {label} ({maze.width}x{maze.height})") + + solver = MazeSolver(maze, strategies[0]) + + for strategy in strategies: + solver.set_strategy(strategy) + times, visited_list, lengths = [], [], [] + + for _ in range(RUNS): + stats = solver.solve() + times.append(stats.time_ms) + visited_list.append(stats.visited) + lengths.append(stats.path_length) + + avg_time = sum(times) / RUNS + avg_visited = sum(visited_list) / RUNS + avg_len = sum(lengths) / RUNS + + found = f"length={avg_len:.0f}" if avg_len > 0 else "not found" + print(f" {strategy.name:<6} time={avg_time:.4f} ms visited={avg_visited:.0f} {found}") + + all_results.append({ + "maze": label, + "strategy": strategy.name, + "time_ms": round(avg_time, 4), + "visited_cells": round(avg_visited, 1), + "path_length": round(avg_len, 1), + }) + + save_csv(all_results) + save_plots(all_results) + show_sample() + print("\nDone. See results/ and docs/") + + +def save_csv(results): + path = os.path.join(RESULTS_DIR, "results_maze.csv") + with open(path, "w", newline="", encoding="utf-8") as f: + writer = csv.DictWriter( + f, fieldnames=["maze", "strategy", "time_ms", "visited_cells", "path_length"] + ) + writer.writeheader() + writer.writerows(results) + print(f"\nCSV saved: {path}") + + +def save_plots(results): + if not HAS_PLT: + return + + mazes = list(dict.fromkeys(r["maze"] for r in results)) + strategies = list(dict.fromkeys(r["strategy"] for r in results)) + colors = ["#2196F3", "#FF5722", "#4CAF50"] + + def val(maze, strat, key): + for r in results: + if r["maze"] == maze and r["strategy"] == strat: + return float(r[key]) + return 0.0 + + metrics = [ + ("time_ms", "Time (ms)"), + ("visited_cells", "Visited cells"), + ("path_length", "Path length"), + ] + + fig, axes = plt.subplots( + len(metrics), len(mazes), + figsize=(3.5 * len(mazes), 4 * len(metrics)) + ) + + def fmt(v): + if v == 0: + return "0" + if v >= 100: + return f"{v:.0f}" + if v >= 1: + return f"{v:.2f}" + return f"{v:.3f}" + + for row_i, (key, ylabel) in enumerate(metrics): + for col_i, maze in enumerate(mazes): + ax = axes[row_i][col_i] + vals = [val(maze, s, key) for s in strategies] + bars = ax.bar(strategies, vals, color=colors[:len(strategies)]) + if row_i == 0: + ax.set_title(maze, fontsize=9) + if col_i == 0: + ax.set_ylabel(ylabel) + for bar, v in zip(bars, vals): + ax.text( + bar.get_x() + bar.get_width() / 2, + bar.get_height() * 1.02, + fmt(v), ha="center", va="bottom", fontsize=7 + ) + ax.tick_params(axis="x", labelsize=8) + + plt.tight_layout() + out = os.path.join(RESULTS_DIR, "benchmark_plot.png") + plt.savefig(out, dpi=120) + plt.close() + print(f"Chart saved: {out}") + + +def show_sample(): + path = os.path.join(MAZES_DIR, "sample.txt") + if not os.path.exists(path): + return + builder = TextFileMazeBuilder() + maze = builder.build_from_file(path) + solver = MazeSolver(maze, BFSStrategy()) + stats = solver.solve() + print("\nSample maze with BFS path:") + print(maze.render(path=stats.path)) + + +if __name__ == "__main__": + run() diff --git a/VasilevIA/lab2/mazes/empty.txt b/VasilevIA/lab2/mazes/empty.txt new file mode 100644 index 0000000..8a42a81 --- /dev/null +++ b/VasilevIA/lab2/mazes/empty.txt @@ -0,0 +1,21 @@ +################################################### +#S # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# # +# E# +################################################### diff --git a/VasilevIA/lab2/mazes/large.txt b/VasilevIA/lab2/mazes/large.txt new file mode 100644 index 0000000..efbe7ea --- /dev/null +++ b/VasilevIA/lab2/mazes/large.txt @@ -0,0 +1,101 @@ +##################################################################################################### +#S # # # # # # # # # # # # # +### # # ##### # ### # # ##### ### ### ############# # # # # ##### # ######### ### # # ##### ### # ### +# # # # # # # # # # # # # # # # # # # # # # # # # # +# # # ##### ### ### # # ########### ####### # ####### ### ### # # # ####### ##### # # ### # # ##### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # +# ##### # ### ### # ### # ### # # ##### # ##### ####### ### ### # ### ### ### # ### ### # ##### # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # ### # # ### # ##### ##### # ### # # ##### # # ####### ######### ### # # ######### # # # ##### +# # # # # # # # # # # # # # # # # # # # # # # # # # +# # ##### ### # ##### # ##### ### # ##### ######### # # ####### ####### # ### # # ########### ##### # +# # # # # # # # # # # # # # # # # # # # # # +##### ##### ########### ### # # ########### # # # # ##### ####### # ####### # ##### ### ### ### ### # +# # # # # # # # # # # # # # # # # # # # # # # # +####### # ### # # ####### ##### ### ##### # # ##### # ##### ### # # # ######### # ### ### ### ### ### +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### # ########### # # # ### ### ##### ##### # # ##### # # # ##### # # ##### ##### # ##### ### ### # +# # # # # # # # # # # # # # # # # # # # # # # # +# # ### # ### # ### ### ### ####### # ########### ####### # # ##### # # ### ########### ### ### # # # +# # # # # # # # # # # # # # # # # # # # # # # # +# # ####### # # # # ##### ### # ####### # ##### ##### ##### # # # ### ### # # ##### ##### ### ####### +# # # # # # # # # # # # # # # # # # # # # # # # # # # +########### # # ##### # # # ####### # ### # ##### # ##### ### # # # # # ####### # ##### ### # # ### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### # # ### # # ##### # # # # # ##### # # # ############# # ##### # ############### ### # # ### # # +# # # # # # # # # # # # # # # # # # # # # # # +# # ####### ##### # ########### # # # ######### ### ############# # ####### ####################### # +# # # # # # # # # # # # # # # # # # # # # +# # ### ### # # # # # ### # ##### # ### # ####### # ##### # ######### ####### # ##### # ########### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# ##### ##### ######### ####### ##### # # # # # ### # ##### # ### # ### # ##### # # # # # ### # # ### +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### # # # ### ##### ### # # ### ##### ##### ####### # # ##### # ### ##### ####### # # # ####### # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # ### ####### # ### # # ### ### ##### ##### # # # # ### # # # ####### # # ### ##### # # ####### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +##### # # # # # ### # ### ### ### ### # ### # ### ##### # # ########### # # ### # # ##### ##### # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### ##### ### ### # # ### ### # # ##### ### # ##### ########### ### ### ### # # # # ### ######### # +# # # # # # # # # # # # # # # # # # # # # # # # # +# # ### # ### ### # # ####### # # ######### ### # ########### # ### ### ##### ### # ### # # ### ### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # ######### # ####### ##### ######### # ### ### # ##### # ### ### ### # ### ### # ##### ##### # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # ### ### # # # # # ##### # # # # ####### # # # ### # ### # ### ### ##### ### # # ### # # # ### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### # ### ######### # ##### # ##### # # ############# # # ### ####### # # ##### # # # # # # ### # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# ##### ####### ### # ### ##### # # # ##### # ####### ### # # ### # # # # ### ### # ########### # ### +# # # # # # # # # # # # # # # # # # # # # # # # # # +# ####### # ##### # ### # ##### # # ##### # ##### # ### ### ######### ##### ### # ##### # ######### # +# # # # # # # # # # # # # # # # # # # # # # # # # # +### # # # ### ####### # # # # ##### # ############### # # ##### ####### ##### # ### # # # # ### # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### ########### ### # ############# # # # ### # ##### ##### ### ####### # ### # # # ##### ### # ### +# # # # # # # # # # # # # # # # # # # # # # # # # +### ##### ##### ### ### ### # # # # ##### # # ####### # # # ####### # # ### # ### # # # # ### ##### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### # ### # # # ####### # # # # ### ####### # # # ##### ### # # ##### # ##### # # ### ### # # ### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # ### # ### # # # # ### ### ### # # ### ### # ##### ### # # ##### ####### # ##### # ##### ### # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### # # ### ####### ### # # ### ##### # # ######### ### ### # # # # # ### # ### # # ### ### # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # +### # # ### ### ##### ######### ######### # # # # # ########### ####### # # ### ########### ##### # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # ### ### ### # ### # # ######### # # # # ##### # # # ##### # ####### ####### # ##### # # ##### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# # ### ### ##### ####### # # ### ######### ### ### # # ########### # ##### # ### ##### ##### # ### # +# # # # # # # # # # # # # # # # # # # # # # # # +# ####### # # ##### ### # ### # ####### # # # ### ### # # ### # ####### # ##### ### # ######### ##### +# # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # ##### # ####### ####### # ########### ####### ######### # ### # ### # ### # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### # # ####### # ### ########### # # ######### ### # # # # # # ### # ######### # ### ########### # +# # # # # # # # # # # # # # # # # # # # # # # +### ####### # # # # # ### # # # ######### # # # ########################### # # ##### ####### # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # +# ### ### ############### ####### ##### # ########### # ### # # ##### ### # ##### # ##### # ### # ### +# # # # # # # # # # # # # # # # # # # # # # # # # +# ########### ##### ### ####### # # # ##### # # # # # ### # # ### ### # ### ### # ##### # # # # ### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # ##### # ### # ### # # ### # # ####### ####### # ### ##### ##### ##### ### ##### # ####### ####### +# # # # # # # # # # # # # # # # # # # # # # # # +# # # # # # # ##### ####### ### # # # ############# # # # # ##### ### ######### # ######### # # ### # +# # # # # # # # # # # # # # # # # # # # # # # # # +### # ##### ##### # ### # ### ####### # ####### # ### ##### # ######### ##### # # ####### ### ##### # +# # # # # # # # # # # # # # # # # # # # # # # # # +# ### # # # # ##### # ##### # ### # ### # ### ##### ### ##### ########### ### # ########### # # ##### +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # # # ##### # # # # # # # # ######### ### # ### # # ### # # # ### # # ### # # # # ### # # # # # # # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # ### # ### ######### ### ### ######### # ### # # ### # ### # # ##### # # ######### ### ### ##### # +# # # # # # # # # # # # # # # # # # # # # # # # +# ##### ##### # # ####### ### ### # ##### ##### ##### # # ##### ##### ############# # # ### ### ### # +# # # # # # # # # # # # # # # # # # # # # # # # # # # +# ### # # # ### ### # # ### # # ####### ##### # # # # # ### ##### ##### # ### ##### # # ######### ### +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # ##### ### ### # # ### ##### ##### ##### # ##### ### # ### # ### ##### ##### # ##### # # # # # # # +# # # # # # # # # # # # # E# +##################################################################################################### diff --git a/VasilevIA/lab2/mazes/medium.txt b/VasilevIA/lab2/mazes/medium.txt new file mode 100644 index 0000000..eb64693 --- /dev/null +++ b/VasilevIA/lab2/mazes/medium.txt @@ -0,0 +1,51 @@ +################################################### +#S# # # # # # # # +# ##### # # # # # # # ####### # ### # ### ##### # # +# # # # # # # # # # # # # # # # # # +### # ### # # ####### # ### # # # # ### # ### ### # +# # # # # # # # # # # # # # +# # # # # # ####### ####### # ### ######### ##### # +# # # # # # # # # # # # # # +# # # ##### # # # ### ####### ##### ##### # ### ### +# # # # # # # # # # # # # # # # # +# # ##### ### # ### ### # # ### # ##### # ### # # # +# # # # # # # # # # # # # # # # # +# # # # # # ### # ### ### ##### # # # ####### # # # +# # # # # # # # # # # # # # # +# ######### # # # # ### # # # ### # ####### # ### # +# # # # # # # # # # # # # # # # # # +# # ### # ### # # ### ### ### # ### # # # ### # # # +# # # # # # # # # # # # # # # +# ### ### # ### ####### ######### # ### ####### ### +# # # # # # # # # # # # # # # +# # ### ### # ### # ####### # # # # # ####### # # # +# # # # # # # # # # # # # # +# ####### # ############# ##### # # ####### # ### # +# # # # # # # # # # # +# # # # # ################# ########### # # ##### # +# # # # # # # # # # # +# ####### # ### ##### # # ### ### ######### # ##### +# # # # # # # # # # +####### # ####### ##### ### ##### # ############# # +# # # # # # # # # # # # +##### ####### # # ### # ##### # # ##### ##### # # # +# # # # # # # # # # # # # # # +## #### # # # ##### ##### # ### ### # # # ##### # # +# # # # # # # # # # # # # # +# ####### # ### # ### # # # ################# ### # +# # # # # # # # # # +### ##### ########### # # ############# ### ### ### +# # # # # # # # # # # # +# ### ### # ####### # # ### # ####### # # ### ### # +# # # # # # # # # # # # # # # +# # ######### ### # # # # # ### ### ####### # ### # +# # # # # # # # # # # # # # +# ##### # ##### # ##### ##### ### ####### ##### ### +# # # # # # # # # # # # +# # # # # # ##### ### ########### # # ####### ### # +# # # # # # # # # # # # # # # +# # ### ##### # ### ##### ##### ##### # # ### # # # +# # # # # # # # # # # # # +### # ### ##### ####### ##### ##### ####### # # # # +# # # # # #E# +################################################### diff --git a/VasilevIA/lab2/mazes/no_exit.txt b/VasilevIA/lab2/mazes/no_exit.txt new file mode 100644 index 0000000..d23335e --- /dev/null +++ b/VasilevIA/lab2/mazes/no_exit.txt @@ -0,0 +1,11 @@ +########### +#S# #E# +# ######### +# # # +##### # ### +# # # +# ####### # +# # # +### ### # # +# # # +########### diff --git a/VasilevIA/lab2/mazes/sample.txt b/VasilevIA/lab2/mazes/sample.txt new file mode 100644 index 0000000..0bdb731 --- /dev/null +++ b/VasilevIA/lab2/mazes/sample.txt @@ -0,0 +1,15 @@ +############### +#S# # # +# ### # # ### # +# # # # # # +### ### ### # # +# # # # # # +# ### # # ### # +# # # # # +## ###### # # # +# # # # +# ### ######### +# # # # +# # ##### # # # +# # #E# +############### diff --git a/VasilevIA/lab2/mazes/small.txt b/VasilevIA/lab2/mazes/small.txt new file mode 100644 index 0000000..ae99e6f --- /dev/null +++ b/VasilevIA/lab2/mazes/small.txt @@ -0,0 +1,11 @@ +########### +#S # # # +##### # # +# # # # +# ####### # +# # # # +# ### # # # +# # # # +### # ### # +# # E# +########### diff --git a/VasilevIA/lab2/results/benchmark_plot.png b/VasilevIA/lab2/results/benchmark_plot.png new file mode 100644 index 0000000..d103767 Binary files /dev/null and b/VasilevIA/lab2/results/benchmark_plot.png differ diff --git a/VasilevIA/lab2/results/results_maze.csv b/VasilevIA/lab2/results/results_maze.csv new file mode 100644 index 0000000..ef305df --- /dev/null +++ b/VasilevIA/lab2/results/results_maze.csv @@ -0,0 +1,16 @@ +maze,strategy,time_ms,visited_cells,path_length +small,BFS,0.0676,39.0,33.0 +small,DFS,0.061,33.0,33.0 +small,A*,0.1093,35.0,33.0 +medium,BFS,1.4027,793.0,497.0 +medium,DFS,0.8985,515.0,497.0 +medium,A*,2.3001,707.0,497.0 +large,BFS,6.1605,3533.0,1613.0 +large,DFS,3.3919,1957.0,1613.0 +large,A*,11.2172,3379.0,1613.0 +empty,BFS,1.7583,931.0,67.0 +empty,DFS,1.0076,451.0,451.0 +empty,A*,3.4836,931.0,67.0 +no_exit,BFS,0.067,40.0,0.0 +no_exit,DFS,0.0599,40.0,0.0 +no_exit,A*,0.1099,40.0,0.0