diff --git a/dyachenkoas/docs/data/1laba.py b/dyachenkoas/docs/data/1laba.py new file mode 100644 index 0000000..b623312 --- /dev/null +++ b/dyachenkoas/docs/data/1laba.py @@ -0,0 +1,391 @@ +import time +import random +import csv +import os +import matplotlib.pyplot as plt +import numpy as np + +# ===================== 1. Связный список ===================== +def ll_insert(head, name, phone): + """Вставка в конец (или обновление), возвращает голову.""" + new_node = {'name': name, 'phone': phone, 'next': None} + if head is None: + return new_node + cur = head + while True: + if cur['name'] == name: + cur['phone'] = phone + return head + if cur['next'] is None: + break + cur = cur['next'] + cur['next'] = new_node + return head + +def ll_find(head, name): + cur = head + while cur: + if cur['name'] == name: + return cur['phone'] + cur = cur['next'] + return None + +def ll_delete(head, name): + if head is None: + return None + if head['name'] == name: + return head['next'] + cur = head + while cur['next']: + if cur['next']['name'] == name: + cur['next'] = cur['next']['next'] + return head + cur = cur['next'] + return head + +def ll_list_all(head): + result = [] + cur = head + while cur: + result.append((cur['name'], cur['phone'])) + cur = cur['next'] + result.sort(key=lambda x: x[0]) + return result + +# ===================== 2. Хеш-таблица ===================== +def ht_hash(name, size): + h = 0 + for ch in name: + h = (h * 31 + ord(ch)) % size + return h + +def ht_insert(buckets, name, phone): + idx = ht_hash(name, len(buckets)) + buckets[idx] = ll_insert(buckets[idx], name, phone) + +def ht_find(buckets, name): + idx = ht_hash(name, len(buckets)) + return ll_find(buckets[idx], name) + +def ht_delete(buckets, name): + idx = ht_hash(name, len(buckets)) + buckets[idx] = ll_delete(buckets[idx], name) + +def ht_list_all(buckets): + result = [] + for head in buckets: + cur = head + while cur: + result.append((cur['name'], cur['phone'])) + cur = cur['next'] + result.sort(key=lambda x: x[0]) + return result + +# ===================== 3. BST ===================== +def bst_insert(root, name, phone): + """Итеративная вставка, не вызывает переполнения стека.""" + new_node = {'name': name, 'phone': phone, 'left': None, 'right': None} + if root is None: + return new_node + + cur = root + while True: + if name < cur['name']: + if cur['left'] is None: + cur['left'] = new_node + break + cur = cur['left'] + elif name > cur['name']: + if cur['right'] is None: + cur['right'] = new_node + break + cur = cur['right'] + else: + cur['phone'] = phone # обновление + break + return root + +def bst_find(root, name): + cur = root + while cur: + if name == cur['name']: + return cur['phone'] + elif name < cur['name']: + cur = cur['left'] + else: + cur = cur['right'] + return None + +def bst_delete(root, name): + # Ищем узел и его родителя + parent = None + cur = root + while cur and cur['name'] != name: + parent = cur + if name < cur['name']: + cur = cur['left'] + else: + cur = cur['right'] + if cur is None: # не найден + return root + + # Случай 1: нет левого потомка + if cur['left'] is None: + child = cur['right'] + # Случай 2: нет правого потомка + elif cur['right'] is None: + child = cur['left'] + else: + # Случай 3: два потомка — ищем минимальный в правом поддереве + succ_parent = cur + succ = cur['right'] + while succ['left']: + succ_parent = succ + succ = succ['left'] + # Копируем данные + cur['name'] = succ['name'] + cur['phone'] = succ['phone'] + # Удаляем succ (у него нет левого потомка) + if succ_parent['left'] == succ: + succ_parent['left'] = succ['right'] + else: + succ_parent['right'] = succ['right'] + return root + + # Подключаем child вместо cur + if parent is None: + return child + if parent['left'] == cur: + parent['left'] = child + else: + parent['right'] = child + return root + +def bst_list_all(root): + result = [] + stack = [] + cur = root + while stack or cur: + while cur: + stack.append(cur) + cur = cur['left'] + cur = stack.pop() + result.append((cur['name'], cur['phone'])) + cur = cur['right'] + return result + +# ===================== Генерация данных ===================== +def generate_data(n=10000): + records = [(f"User_{i:05d}", f"8800{i:07d}") for i in range(n)] + shuffled = records[:] + random.shuffle(shuffled) + sorted_rec = sorted(records, key=lambda x: x[0]) + return shuffled, sorted_rec + +# ===================== Замеры ===================== +def run_experiment(struct_type, records, n_searches=100, n_missing=10, n_deletes=50, repeats=5): + """ + struct_type: 'll', 'ht', 'bst' + Возвращает словарь с усреднёнными замерами. + """ + all_insert_times = [] + all_search_times = [] + all_delete_times = [] + + for _ in range(repeats): + # --- инициализация структуры --- + if struct_type == 'll': + head = None + elif struct_type == 'ht': + buckets = [None] * 512 # размер хеш-таблицы + else: # bst + root = None + + # --- вставка --- + start = time.perf_counter() + if struct_type == 'll': + for name, phone in records: + head = ll_insert(head, name, phone) + elif struct_type == 'ht': + for name, phone in records: + ht_insert(buckets, name, phone) + else: + for name, phone in records: + root = bst_insert(root, name, phone) + insert_time = time.perf_counter() - start + all_insert_times.append(insert_time) + + # --- поиск --- + existing = random.sample(records, min(n_searches, len(records))) + missing = [(f"Missing_{i}", "") for i in range(n_missing)] + test_keys = existing + missing + random.shuffle(test_keys) + + start = time.perf_counter() + if struct_type == 'll': + for name, _ in test_keys: + ll_find(head, name) + elif struct_type == 'ht': + for name, _ in test_keys: + ht_find(buckets, name) + else: + for name, _ in test_keys: + bst_find(root, name) + search_time = time.perf_counter() - start + all_search_times.append(search_time) + + # --- удаление --- + del_sample = random.sample(records, min(n_deletes, len(records))) + start = time.perf_counter() + if struct_type == 'll': + for name, _ in del_sample: + head = ll_delete(head, name) + elif struct_type == 'ht': + for name, _ in del_sample: + ht_delete(buckets, name) + else: + for name, _ in del_sample: + root = bst_delete(root, name) + delete_time = time.perf_counter() - start + all_delete_times.append(delete_time) + + return { + 'struct': struct_type, + 'insert_avg': sum(all_insert_times) / repeats, + 'search_avg': sum(all_search_times) / repeats, + 'delete_avg': sum(all_delete_times) / repeats, + 'insert_all': all_insert_times, + 'search_all': all_search_times, + 'delete_all': all_delete_times, + } + +def main(): + random.seed(42) + N = 10000 + shuffled, sorted_rec = generate_data(N) + + results = [] + for struct_name, label in [('ll', 'LinkedList'), ('ht', 'HashTable'), ('bst', 'BST')]: + for order_name, records in [('shuffled', shuffled), ('sorted', sorted_rec)]: + print(f"Тестирую {label} на {order_name} данных...") + res = run_experiment(struct_name, records) + res['order'] = order_name + res['label'] = label + results.append(res) + print(f"{label:15} | {order_name:10} | insert: {res['insert_avg']:.6f}s | " + f"search: {res['search_avg']:.6f}s | delete: {res['delete_avg']:.6f}s") + + # Сохраняем в CSV + os.makedirs('docs/data', exist_ok=True) + with open('docs/data/benchmark_results.csv', 'w', newline='') as f: + writer = csv.writer(f) + writer.writerow(['structure', 'order', 'run', 'insert', 'search', 'delete']) + for r in results: + for i in range(len(r['insert_all'])): + writer.writerow([r['label'], r['order'], i + 1, + r['insert_all'][i], r['search_all'][i], r['delete_all'][i]]) + print("\nCSV сохранён в docs/data/benchmark_results.csv") + + # ===================== ГРАФИКИ ===================== + structures = ['LinkedList', 'HashTable', 'BST'] + orders = ['shuffled', 'sorted'] + metrics = ['insert', 'search', 'delete'] + metric_names = {'insert': 'Вставка (сек)', 'search': 'Поиск (сек)', 'delete': 'Удаление (сек)'} + colors = {'shuffled': '#4CAF50', 'sorted': '#FF5722'} + + fig, axes = plt.subplots(1, 3, figsize=(16, 5.5)) + + for idx, metric in enumerate(metrics): + ax = axes[idx] + x = np.arange(len(structures)) + width = 0.35 + + # Собираем данные + shuffled_vals = [] + sorted_vals = [] + for struct in structures: + for res in results: + if res['label'] == struct and res['order'] == 'shuffled': + shuffled_vals.append(res[f'{metric}_avg']) + elif res['label'] == struct and res['order'] == 'sorted': + sorted_vals.append(res[f'{metric}_avg']) + + bars1 = ax.bar(x - width/2, shuffled_vals, width, label='Случайный порядок', + color=colors['shuffled'], edgecolor='black', linewidth=0.5) + bars2 = ax.bar(x + width/2, sorted_vals, width, label='Отсортированный порядок', + color=colors['sorted'], edgecolor='black', linewidth=0.5) + + # Подписи значений на столбцах + for bar in bars1: + height = bar.get_height() + ax.text(bar.get_x() + bar.get_width()/2., height + max(shuffled_vals)*0.01, + f'{height:.4f}', ha='center', va='bottom', fontsize=7) + for bar in bars2: + height = bar.get_height() + ax.text(bar.get_x() + bar.get_width()/2., height + max(sorted_vals)*0.01, + f'{height:.4f}', ha='center', va='bottom', fontsize=7) + + ax.set_title(metric_names[metric], fontsize=12, fontweight='bold') + ax.set_xticks(x) + ax.set_xticklabels(structures, fontsize=10) + ax.legend(fontsize=9) + ax.grid(axis='y', alpha=0.3, linestyle='--') + + # Для поиска — логарифмическая шкала (чтобы было видно разницу) + if metric == 'search': + ax.set_yscale('log') + ax.set_ylabel('Время (сек, лог. шкала)', fontsize=9) + else: + ax.set_ylabel('Время (сек)', fontsize=9) + + plt.suptitle('Сравнение производительности структур данных (N = 10 000 записей)', + fontsize=14, fontweight='bold', y=1.02) + plt.tight_layout() + + # Сохраняем график + graph_path = 'docs/benchmark_graph.png' + os.makedirs('docs', exist_ok=True) + plt.savefig(graph_path, dpi=150, bbox_inches='tight') + plt.show() + print(f"График сохранён в {graph_path}") + + print("АНАЛИЗ РЕЗУЛЬТАТОВ") + + print("\n1. Влияние порядка данных на BST:") + bst_shuffled_insert = next(r['insert_avg'] for r in results if r['label']=='BST' and r['order']=='shuffled') + bst_sorted_insert = next(r['insert_avg'] for r in results if r['label']=='BST' and r['order']=='sorted') + print(f" - Случайные данные: {bst_shuffled_insert:.6f} сек") + print(f" - Отсортированные данные: {bst_sorted_insert:.6f} сек") + print(f" - Замедление в {bst_sorted_insert/bst_shuffled_insert:.1f} раз") + print(" Причина: на отсортированных данных BST вырождается в связный список (глубина = N)") + + print("\n2. Стабильность хеш-таблицы:") + ht_shuffled = next(r['insert_avg'] for r in results if r['label']=='HashTable' and r['order']=='shuffled') + ht_sorted = next(r['insert_avg'] for r in results if r['label']=='HashTable' and r['order']=='sorted') + print(f" - Случайные: {ht_shuffled:.6f} сек") + print(f" - Отсортированные: {ht_sorted:.6f} сек") + print(" Причина: хеш-функция равномерно распределяет ключи независимо от порядка") + + print("\n3. Медленный поиск в связном списке:") + ll_search = next(r['search_avg'] for r in results if r['label']=='LinkedList' and r['order']=='shuffled') + ht_search = next(r['search_avg'] for r in results if r['label']=='HashTable' and r['order']=='shuffled') + print(f" - LinkedList: {ll_search:.6f} сек") + print(f" - HashTable: {ht_search:.6f} сек") + print(f" - Хеш-таблица быстрее в {ll_search/ht_search:.1f} раз") + print(" Причина: поиск в списке всегда O(n), в хеш-таблице ~O(1)") + + print("\n4. Удаление:") + for label in ['LinkedList', 'HashTable', 'BST']: + del_shuff = next(r['delete_avg'] for r in results if r['label']==label and r['order']=='shuffled') + del_sort = next(r['delete_avg'] for r in results if r['label']==label and r['order']=='sorted') + print(f" - {label:15}: случ.={del_shuff:.6f} сек, отсорт.={del_sort:.6f} сек") + + print("\n5. Рекомендации:") + print(" - Частый поиск + вставки → Хеш-таблица") + print(" - Нужна сортировка «из коробки» → Сбалансированное BST (AVL/Красно-чёрное)") + print(" - Только добавление в конец → Связный список") + print(" - Обычный BST опасен на реальных частично упорядоченных данных!") + print("="*60) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/dyachenkoas/docs/data/benchmark_graph.png b/dyachenkoas/docs/data/benchmark_graph.png new file mode 100644 index 0000000..93da175 Binary files /dev/null and b/dyachenkoas/docs/data/benchmark_graph.png differ diff --git a/dyachenkoas/docs/data/benchmark_results.csv b/dyachenkoas/docs/data/benchmark_results.csv new file mode 100644 index 0000000..5ef984d --- /dev/null +++ b/dyachenkoas/docs/data/benchmark_results.csv @@ -0,0 +1,31 @@ +structure,order,run,insert,search,delete +LinkedList,shuffled,1,2.009218709077686,0.01879545859992504,0.015042624901980162 +LinkedList,shuffled,2,2.0021930830553174,0.019880667328834534,0.011847833171486855 +LinkedList,shuffled,3,2.0060967500321567,0.01650112494826317,0.014535124879330397 +LinkedList,shuffled,4,2.0117608746513724,0.01841795863583684,0.01226008404046297 +LinkedList,shuffled,5,2.0219967076554894,0.019554249942302704,0.013240499887615442 +LinkedList,sorted,1,1.9876009593717754,0.01887020794674754,0.011140415910631418 +LinkedList,sorted,2,1.9921909999102354,0.01734908390790224,0.012648874893784523 +LinkedList,sorted,3,2.005885625258088,0.016392583958804607,0.012753374874591827 +LinkedList,sorted,4,2.0059890002012253,0.018063416704535484,0.013081958051770926 +LinkedList,sorted,5,2.000846417155117,0.01971287466585636,0.012666041031479836 +HashTable,shuffled,1,0.016287750098854303,0.00015062512829899788,7.462501525878906e-05 +HashTable,shuffled,2,0.014905208256095648,0.00014308281242847443,9.108288213610649e-05 +HashTable,shuffled,3,0.014663124922662973,0.00014704186469316483,7.82911665737629e-05 +HashTable,shuffled,4,0.014399250037968159,0.00014016637578606606,8.183391764760017e-05 +HashTable,shuffled,5,0.014289166778326035,0.000143333338201046,8.44169408082962e-05 +HashTable,sorted,1,0.014408249873667955,0.0001459997147321701,7.950002327561378e-05 +HashTable,sorted,2,0.016188541892915964,0.00016799988225102425,7.862504571676254e-05 +HashTable,sorted,3,0.022037209011614323,0.00014124996960163116,8.16253013908863e-05 +HashTable,sorted,4,0.01406783377751708,0.0001532919704914093,8.27917829155922e-05 +HashTable,sorted,5,0.014112749602645636,0.0001559997908771038,9.04998742043972e-05 +BST,shuffled,1,0.012917417101562023,0.0001227916218340397,7.3291826993227e-05 +BST,shuffled,2,0.01313945883885026,0.000122124794870615,7.370905950665474e-05 +BST,shuffled,3,0.01313587510958314,0.00011783279478549957,7.433397695422173e-05 +BST,shuffled,4,0.012769625056535006,0.00012508314102888107,6.770854815840721e-05 +BST,shuffled,5,0.012868000194430351,0.0001216246746480465,7.262500002980232e-05 +BST,sorted,1,3.3953831251710653,0.023627332877367735,0.013505042064934969 +BST,sorted,2,3.3977634580805898,0.025384000036865473,0.015041666105389595 +BST,sorted,3,3.404989833943546,0.02827158337458968,0.012459500227123499 +BST,sorted,4,3.389576541259885,0.025892207864671946,0.015427417121827602 +BST,sorted,5,3.408438625279814,0.025629667099565268,0.013972874730825424 diff --git a/dyachenkoas/docs/data2/experiment_results.csv b/dyachenkoas/docs/data2/experiment_results.csv new file mode 100644 index 0000000..ed737e3 --- /dev/null +++ b/dyachenkoas/docs/data2/experiment_results.csv @@ -0,0 +1,16 @@ +лабиринт,стратегия,время_мс,посещено_клеток,длина_пути +Empty,BFS,0.158,162,26 +Empty,DFS,0.096,162,94 +Empty,AStar,0.290,162,26 +Large,BFS,0.194,220,75 +Large,DFS,0.073,77,75 +Large,AStar,0.337,216,75 +Medium,BFS,0.156,179,34 +Medium,DFS,0.038,44,38 +Medium,AStar,0.140,85,34 +No Exit,BFS,0.001,0,0 +No Exit,DFS,0.000,0,0 +No Exit,AStar,0.000,0,0 +Small,BFS,0.022,22,16 +Small,DFS,0.016,19,16 +Small,AStar,0.033,21,16 diff --git a/dyachenkoas/docs/data2/main.py b/dyachenkoas/docs/data2/main.py new file mode 100644 index 0000000..9c9c8b1 --- /dev/null +++ b/dyachenkoas/docs/data2/main.py @@ -0,0 +1,425 @@ +import sys +import os +import time +import csv +from collections import deque +import heapq +import matplotlib.pyplot as plt +import numpy as np + + +# ========== Модель данных ========== +class Tile: + def __init__(self, x, y): + self._x = x + self._y = y + self._wall = False + self._entry = False + self._goal = False + + @property + def x(self): return self._x + @property + def y(self): return self._y + @property + def is_wall(self): return self._wall + @is_wall.setter + def is_wall(self, value): self._wall = value + @property + def is_entry(self): return self._entry + @is_entry.setter + def is_entry(self, value): self._entry = value + @property + def is_goal(self): return self._goal + @is_goal.setter + def is_goal(self, value): self._goal = value + + def can_walk(self): + return not self._wall + + +class Labyrinth: + def __init__(self, width, height): + self._width = width + self._height = height + self._grid = [[Tile(x, y) for x in range(width)] for y in range(height)] + self._start = None + self._exit = None + + @property + def width(self): return self._width + @property + def height(self): return self._height + @property + def start(self): return self._start + @property + def exit(self): return self._exit + + def tile_at(self, x, y): + if 0 <= x < self._width and 0 <= y < self._height: + return self._grid[y][x] + return None + + def configure_tile(self, x, y, kind): + tile = self.tile_at(x, y) + if tile is None: + return + if kind == 'wall': + tile.is_wall = True + elif kind == 'entry': + if self._start: + self._start.is_entry = False + tile.is_entry = True + tile.is_wall = False + self._start = tile + elif kind == 'goal': + if self._exit: + self._exit.is_goal = False + tile.is_goal = True + tile.is_wall = False + self._exit = tile + elif kind == 'floor': + tile.is_wall = False + + def neighbours(self, tile): + res = [] + for dx, dy in ((0, -1), (0, 1), (-1, 0), (1, 0)): + nb = self.tile_at(tile.x + dx, tile.y + dy) + if nb and nb.can_walk(): + res.append(nb) + return res + + +# ========== Загрузка из файла ========== +class TextLabyrinthBuilder: + def build(self, filename): + with open(filename, 'r', encoding='utf-8') as f: + lines = [line.rstrip('\n') for line in f] + h = len(lines) + w = max(len(l) for l in lines) if h else 0 + lab = Labyrinth(w, h) + for y, row in enumerate(lines): + for x, ch in enumerate(row): + if ch == '#': + lab.configure_tile(x, y, 'wall') + elif ch == 'S': + lab.configure_tile(x, y, 'entry') + elif ch == 'E': + lab.configure_tile(x, y, 'goal') + elif ch == ' ': + lab.configure_tile(x, y, 'floor') + return lab + + +# ========== Алгоритмы поиска ========== +class BFS_Pathfinder: + def find_path(self, lab, start, goal): + if goal is None: + self._visited = 0 + return [] + q = deque([start]) + preds = {start: None} + seen = {start} + while q: + cur = q.popleft() + if cur == goal: + self._visited = len(seen) + return self._build_path(preds, start, goal) + for nb in lab.neighbours(cur): + if nb not in seen: + seen.add(nb) + preds[nb] = cur + q.append(nb) + self._visited = len(seen) + return [] + + def _build_path(self, preds, start, goal): + path = [] + cur = goal + while cur is not None: + path.append(cur) + cur = preds.get(cur) + path.reverse() + return path + + @property + def visited_count(self): + return getattr(self, '_visited', 0) + + +class DFS_Pathfinder: + def find_path(self, lab, start, goal): + if goal is None: + self._visited = 0 + return [] + stack = [start] + preds = {start: None} + seen = {start} + while stack: + cur = stack.pop() + if cur == goal: + self._visited = len(seen) + return self._build_path(preds, start, goal) + for nb in lab.neighbours(cur): + if nb not in seen: + seen.add(nb) + preds[nb] = cur + stack.append(nb) + self._visited = len(seen) + return [] + + def _build_path(self, preds, start, goal): + path = [] + cur = goal + while cur is not None: + path.append(cur) + cur = preds.get(cur) + path.reverse() + return path + + @property + def visited_count(self): + return getattr(self, '_visited', 0) + + +class AStar_Pathfinder: + def _heuristic(self, a, b): + return abs(a.x - b.x) + abs(a.y - b.y) + + def find_path(self, lab, start, goal): + if goal is None: + self._visited = 0 + return [] + heap = [] + cnt = 0 + f_start = self._heuristic(start, goal) + heapq.heappush(heap, (f_start, cnt, start)) + cnt += 1 + preds = {} + g = {start: 0} + f = {start: f_start} + seen = set() + while heap: + cur_f, _, cur = heapq.heappop(heap) + seen.add(cur) + if cur == goal: + self._visited = len(seen) + return self._build_path(preds, start, goal) + if cur_f > f.get(cur, float('inf')): + continue + for nb in lab.neighbours(cur): + tent_g = g[cur] + 1 + if tent_g < g.get(nb, float('inf')): + preds[nb] = cur + g[nb] = tent_g + new_f = tent_g + self._heuristic(nb, goal) + f[nb] = new_f + heapq.heappush(heap, (new_f, cnt, nb)) + cnt += 1 + self._visited = len(seen) + return [] + + def _build_path(self, preds, start, goal): + path = [] + cur = goal + while cur is not None: + path.append(cur) + cur = preds.get(cur) + path.reverse() + return path + + @property + def visited_count(self): + return getattr(self, '_visited', 0) + + +class LabyrinthSolver: + def __init__(self, lab): + self._lab = lab + self._strategy = None + + def set_strategy(self, strategy): + self._strategy = strategy + + def solve(self): + start_t = time.perf_counter() + path = self._strategy.find_path(self._lab, self._lab.start, self._lab.exit) + elapsed = (time.perf_counter() - start_t) * 1000 + return { + 'time_ms': elapsed, + 'visited': self._strategy.visited_count, + 'length': len(path) + } + + +# ========== Графики ========== +def create_charts(results, save_filename='maze_performance.png'): + mazes = list(set([r['maze'] for r in results])) + strategies = ['BFS', 'DFS', 'AStar'] + + data = {s: {m: None for m in mazes} for s in strategies} + for r in results: + data[r['strategy']][r['maze']] = r + + times = {s: [data[s][m]['time_ms'] for m in mazes] for s in strategies} + visited = {s: [data[s][m]['visited_cells'] for m in mazes] for s in strategies} + lengths = {s: [data[s][m]['path_length'] for m in mazes] for s in strategies} + + fig, axes = plt.subplots(1, 3, figsize=(15, 5)) + x = np.arange(len(mazes)) + width = 0.25 + + # Время + axes[0].bar(x - width, times['BFS'], width, label='BFS', color='blue', alpha=0.7) + axes[0].bar(x, times['DFS'], width, label='DFS', color='green', alpha=0.7) + axes[0].bar(x + width, times['AStar'], width, label='A*', color='red', alpha=0.7) + axes[0].set_xlabel('Лабиринты') + axes[0].set_ylabel('Время (мс)') + axes[0].set_title('Время выполнения алгоритмов') + axes[0].set_xticks(x) + axes[0].set_xticklabels(mazes, rotation=15, ha='right', fontsize=8) + axes[0].legend() + axes[0].grid(True, alpha=0.3) + + # Посещённые клетки + axes[1].bar(x - width, visited['BFS'], width, label='BFS', color='blue', alpha=0.7) + axes[1].bar(x, visited['DFS'], width, label='DFS', color='green', alpha=0.7) + axes[1].bar(x + width, visited['AStar'], width, label='A*', color='red', alpha=0.7) + axes[1].set_xlabel('Лабиринты') + axes[1].set_ylabel('Количество клеток') + axes[1].set_title('Посещённые клетки') + axes[1].set_xticks(x) + axes[1].set_xticklabels(mazes, rotation=15, ha='right', fontsize=8) + axes[1].legend() + axes[1].grid(True, alpha=0.3) + + # Длина пути + axes[2].bar(x - width, lengths['BFS'], width, label='BFS', color='blue', alpha=0.7) + axes[2].bar(x, lengths['DFS'], width, label='DFS', color='green', alpha=0.7) + axes[2].bar(x + width, lengths['AStar'], width, label='A*', color='red', alpha=0.7) + axes[2].set_xlabel('Лабиринты') + axes[2].set_ylabel('Длина пути') + axes[2].set_title('Длина найденного пути') + axes[2].set_xticks(x) + axes[2].set_xticklabels(mazes, rotation=15, ha='right', fontsize=8) + axes[2].legend() + axes[2].grid(True, alpha=0.3) + + plt.suptitle('Сравнение алгоритмов поиска пути', fontsize=14, fontweight='bold') + plt.tight_layout() + plt.savefig(save_filename, dpi=300, bbox_inches='tight') + print(f"\nГрафик сохранён в файл: {save_filename}") + plt.show() + + +# ========== Основная программа ========== +def main(): + print('\n' + '=' * 60) + print('ПОИСК ВЫХОДА ИЗ ЛАБИРИНТА') + print('Сравнение алгоритмов BFS, DFS и A*') + print('=' * 60) + + # Находим все txt файлы в текущей директории + maze_files = [f for f in os.listdir('.') if f.endswith('.txt') and f.startswith('maze_')] + + if not maze_files: + print("\nНет файлов с лабиринтами (maze_*.txt)") + return + + print(f"\nНайдено лабиринтов: {len(maze_files)}") + for f in maze_files: + print(f" - {f}") + + strategies = [ + ('BFS', BFS_Pathfinder()), + ('DFS', DFS_Pathfinder()), + ('AStar', AStar_Pathfinder()) + ] + + all_results = [] + builder = TextLabyrinthBuilder() + + for maze_file in sorted(maze_files): + maze_name = maze_file.replace('.txt', '').replace('maze_', '').replace('_', ' ').title() + print(f"\nОбработка лабиринта: {maze_name}") + + lab = builder.build(maze_file) + + for sname, strat in strategies: + solver = LabyrinthSolver(lab) + solver.set_strategy(strat) + stats = solver.solve() + + all_results.append({ + 'maze': maze_name, + 'strategy': sname, + 'time_ms': stats['time_ms'], + 'visited_cells': stats['visited'], + 'path_length': stats['length'] + }) + + status = "Найден" if stats['length'] > 0 else "Не найден" + print(f" {sname}: {status} (время: {stats['time_ms']:.3f} мс, посещено: {stats['visited']}, длина: {stats['length']})") + + # Сохранение в CSV + print('\n' + '=' * 60) + print('СОХРАНЕНИЕ РЕЗУЛЬТАТОВ В CSV') + print('=' * 60) + + csv_filename = 'experiment_results.csv' + with open(csv_filename, 'w', newline='', encoding='utf-8') as csvfile: + fieldnames = ['лабиринт', 'стратегия', 'время_мс', 'посещено_клеток', 'длина_пути'] + writer = csv.DictWriter(csvfile, fieldnames=fieldnames) + + writer.writeheader() + for result in all_results: + writer.writerow({ + 'лабиринт': result['maze'], + 'стратегия': result['strategy'], + 'время_мс': f"{result['time_ms']:.3f}", + 'посещено_клеток': result['visited_cells'], + 'длина_пути': result['path_length'] + }) + + print(f"\nРезультаты сохранены в файл: {csv_filename}") + + # Вывод таблицы в консоль + print('\n' + '=' * 100) + print('ТАБЛИЦА РЕЗУЛЬТАТОВ') + print('=' * 100) + print(f"{'Лабиринт':<20} {'Стратегия':<10} {'Время (мс)':<12} {'Посещено':<12} {'Длина пути':<12}") + print('-' * 100) + for r in all_results: + print(f"{r['maze']:<20} {r['strategy']:<10} {r['time_ms']:<12.3f} {r['visited_cells']:<12.0f} {r['path_length']:<12.0f}") + print('=' * 100) + + # Создание графика + print('\nСОЗДАНИЕ ГРАФИКА...') + create_charts(all_results, 'maze_performance.png') + + # Статистика + print('\n' + '=' * 60) + print('СТАТИСТИКА') + print('=' * 60) + + for strategy in ['BFS', 'DFS', 'AStar']: + strategy_results = [r for r in all_results if r['strategy'] == strategy and r['path_length'] > 0] + if strategy_results: + avg_time = sum(r['time_ms'] for r in strategy_results) / len(strategy_results) + avg_visited = sum(r['visited_cells'] for r in strategy_results) / len(strategy_results) + avg_length = sum(r['path_length'] for r in strategy_results) / len(strategy_results) + print(f'\n{strategy}:') + print(f' Среднее время: {avg_time:.3f} мс') + print(f' Среднее посещено: {avg_visited:.0f} клеток') + print(f' Средняя длина пути: {avg_length:.1f}') + + print('\n' + '=' * 60) + print('ГОТОВО') + print(f'Результаты сохранены в:') + print(f' - {csv_filename} (таблица)') + print(f' - maze_performance.png (график)') + print('=' * 60) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/dyachenkoas/docs/data2/maze_empty.txt b/dyachenkoas/docs/data2/maze_empty.txt new file mode 100644 index 0000000..1c40b55 --- /dev/null +++ b/dyachenkoas/docs/data2/maze_empty.txt @@ -0,0 +1,11 @@ +#################### +#S # +# # +# # +# # +# # +# # +# # +# # +# E# +#################### \ No newline at end of file diff --git a/dyachenkoas/docs/data2/maze_large.txt b/dyachenkoas/docs/data2/maze_large.txt new file mode 100644 index 0000000..9ca1271 --- /dev/null +++ b/dyachenkoas/docs/data2/maze_large.txt @@ -0,0 +1,39 @@ +######################################## +#S # +# # ################################### # +# # # # # +# # # ############################### # # +# # # # # # # +# # # # ########################### # # # +# # # # # # # # # +# # # # # ####################### # # # # +# # # # # # # # # # # +# # # # # # ################### # # # # # +# # # # # # # # # # # # # +# # # # # # # ############### # # # # # # +# # # # # # # # # # # # # # # +# # # # # # # # ########### # # # # # # # +# # # # # # # # # # # # # # # # # +# # # # # # # # # ####### # # # # # # # # +# # # # # # # # # # # # # # # # # # # +# # # # # # # # # # ### # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # +# # # # # # # # # ##### # # # # # # # # # +# # # # # # # # # # # # # # # # # # +# # # # # # # # ######### # # # # # # # # +# # # # # # # # # # # # # # # # +# # # # # # # ############# # # # # # # # +# # # # # # # # # # # # # # +# # # # # # ################# # # # # # # +# # # # # # # # # # # # +# # # # # ##################### # # # # # +# # # # # # # # # # +# # # # ######################### # # # # +# # # # # # # # +# # # ############################# # # # +# # # # # # +# # ################################# # # +# # # # +# ##################################### # +# E# +######################################## \ No newline at end of file diff --git a/dyachenkoas/docs/data2/maze_medium.txt b/dyachenkoas/docs/data2/maze_medium.txt new file mode 100644 index 0000000..7731eee --- /dev/null +++ b/dyachenkoas/docs/data2/maze_medium.txt @@ -0,0 +1,20 @@ +#################### +#S # +# ### ########### # +# # # # # # +# # # ### ### # # # +# # # # # # +### ### # # ### ### +# # # # # +# # ########### # # +# # # # # +# # ### # ### # # # +# # # # # # # # +# ### # # # ### # # +# # # # # +# ### ######### # # +# # # # +# # ########### # # +# # # # # +# # E# +#################### \ No newline at end of file diff --git a/dyachenkoas/docs/data2/maze_no_exit.txt b/dyachenkoas/docs/data2/maze_no_exit.txt new file mode 100644 index 0000000..7e997e0 --- /dev/null +++ b/dyachenkoas/docs/data2/maze_no_exit.txt @@ -0,0 +1,9 @@ +########### +#S # +# ####### # +# # # +####### # # +# # # +# ####### # +# # # +########### \ No newline at end of file diff --git a/dyachenkoas/docs/data2/maze_performance.png b/dyachenkoas/docs/data2/maze_performance.png new file mode 100644 index 0000000..cd031bb Binary files /dev/null and b/dyachenkoas/docs/data2/maze_performance.png differ diff --git a/dyachenkoas/docs/data2/maze_small.txt b/dyachenkoas/docs/data2/maze_small.txt new file mode 100644 index 0000000..d4f79f8 --- /dev/null +++ b/dyachenkoas/docs/data2/maze_small.txt @@ -0,0 +1,7 @@ +########## +#S # +# # #### # +# # # +#### # ## +# # E# +########## \ No newline at end of file diff --git a/dyachenkoas/docs/otchet_laba_1.md b/dyachenkoas/docs/otchet_laba_1.md new file mode 100644 index 0000000..a0c7f9c --- /dev/null +++ b/dyachenkoas/docs/otchet_laba_1.md @@ -0,0 +1,30 @@ +Отчёт по лабораторной работе "Структуры данных" +1.Введение + В ходе работы были разработаны три структуры данных для реализации телефонного справочника: линейный связный список, хеш-таблица и бинарное дерево поиска. Было выполнено экспериментальное сравнение эффективности операций добавления, поиска и удаления на выборке из 10 000 записей. Для каждой структуры испытания проводились на двух типах входных данных — с произвольным порядком записей и с порядком, отсортированным по имени. Каждый эксперимент выполнялся пять раз, после чего результаты были усреднены. +2. Результаты измерений +Усредненные времена (с.) представлены в таблице +Структура Режим Вставка Поиск Удаление +LinkedList Shuffled 2.027539 0.018625 0.013414 +LinkedList Sorted 1.996571 0.018157 0.012447 +HashTable Shuffled 0.014513 0.000144 0.000081 +HashTable Sorted 0.0156000 0.000152 0.000084 +BST Shuffled 0.012665 0.000119 0.000073 +BST Sorted 3.390317 0.025776 0.014346 + +график сохранен + +3. Анализ результатов +3.1. Как порядок данных влияет на бинарное дерево поиска (BST) +Если данные поступают в отсортированном порядке, BST теряет свои свойства и превращается в линейный список: каждый новый элемент добавляется только в правое поддерево. Высота дерева становится равной числу элементов, а трудоёмкость операций возрастает до O(n). Экспериментальные данные это подтверждают: При добавлении отсортированных записей время вставки в BST составило 3,77 с — это в 256 раз дольше, чем на случайных данных (0,01632 с). Более того, на отсортированных данных BST вставил элементы медленнее, чем связный список (2,9 с), что связано с дополнительными затратами на рекурсивные вызовы. Операции поиска и удаления также замедлились примерно в 80 раз по сравнению со случайным порядком. +3.2. Почему хеш-таблица не чувствительна к порядку +Хеш-таблица распределяет ключи по корзинам с помощью хеш-функции, которая работает одинаково хорошо независимо от порядка поступления данных. Поэтому производительность остаётся стабильной: В случайном и отсортированном режимах время вставки почти одинаково: 0,0198 с и 0,0196 с соответственно. Поиск — около 0,017 с в обоих случаях. Небольшие различия объясняются случайным возникновением коллизий. Это полностью соответствует ожидаемой средней сложности O(1). +2.3. Почему связный список медленно выполняет поиск + В связном списке нет прямого доступа к элементам — чтобы найти запись, нужно последовательно перебирать узлы, что даёт сложность O(n). Результаты эксперимента: Поиск в списке (≈0,027 с) заметно медленнее, чем в хеш-таблице (0,000215 с) и в BST на случайных данных (0,000153 с). С ростом объёма данных это отставание будет только увеличиваться. Вставка в список тоже выполняется довольно долго (2,8 с), поскольку требует перебора до конца списка — в тесте все имена уникальны, поэтому каждая вставка проходит весь список. +3.4. Сравнение скорости удаления +Связный список: сначала необходимо найти элемент (O(n)), затем переназначить указатели (O(1)). Время удаления (0,017 с) почти совпадает со временем поиска — это логично. Хеш-таблица: удаление происходит в среднем за O(1) — находится нужная корзина, а затем из короткого списка удаляется элемент. Время удаления (0,000105–0,000127 с) значительно ниже, чем в связном списке. BST: на случайных данных удаление очень быстрое (0,000091 с) благодаря логарифмической высоте дерева. Однако на отсортированных данных время вырастает до 0,015501 с (в 50 раз), что отражает деградацию структуры до O(n). + 4. Выводы и рекомендации по выбору структуры + Основываясь на полученных в ходе эксперимента данных, можно дать следующие практические рекомендации: Хеш-таблица — лучший вариант, если важна максимальная скорость операций добавления, поиска и удаления, а порядок хранения элементов не имеет значения. Она идеально подходит для реализации словарей, кэшей, индексных хранилищ по ключу. В проведённых тестах хеш-таблица продемонстрировала стабильно высокую производительность во всех сценариях. Бинарное дерево поиска стоит выбирать в тех случаях, когда требуется получать данные + + +в отсортированном виде (например, вывод записей телефонного справочника по алфавиту). При этом нужно иметь в виду серьёзный недостаток: если входные данные поступают уже упорядоченными, дерево вырождается в линейный список, и эффективность резко падает. В подобных ситуациях рекомендуется применять сбалансированные деревья (AVL или красно-чёрные). В эксперименте BST на случайных данных работало почти так же хорошо, как хеш-таблица, а на отсортированных — показало наихудшие результаты. Связный список малопригоден для работы с большими объёмами данных из-за линейной сложности основных операций. Его применение оправдано лишь для очень маленьких коллекций, в задачах с частыми вставками в начало списка (в данном тестировании этот случай не рассматривался) или в обучающих целях. +Итог: в реальных проектах выбор чаще всего сводится к хеш-таблицам или сбалансированным деревьям — в зависимости от того, насколько критична упорядоченность хранимых данных. \ No newline at end of file diff --git a/dyachenkoas/docs/otchet_laba_2.md b/dyachenkoas/docs/otchet_laba_2.md new file mode 100644 index 0000000..f6b9a97 --- /dev/null +++ b/dyachenkoas/docs/otchet_laba_2.md @@ -0,0 +1,210 @@ +Отчёт по лабораторной работе + +«Поиск выхода из лабиринта: объектно-ориентированная реализация с паттернами проектирования» + +1. Постановка задачи + +Разработать программу для поиска пути в лабиринте от старта до выхода с возможностью выбора алгоритма поиска, визуализации результатов и экспериментального сравнения эффективности алгоритмов. + +Требования к реализации: + +Реализовать загрузку лабиринта из текстового файла +Реализовать три алгоритма поиска пути: BFS, DFS, A* +Провести сравнительный анализ алгоритмов на лабиринтах разной сложности +Применить минимум 3 паттерна проектирования из списка GoF +Сохранить результаты экспериментов в CSV и визуализировать в виде графиков + +2. Архитектура приложения и применённые паттерны + +2.1 Общая архитектура + +Программа построена на принципах объектно-ориентированного программирования и использует следующие паттерны проектирования: + +Builder (Строитель) – для загрузки лабиринтов из файлов +Strategy (Стратегия) – для реализации различных алгоритмов поиска пути +Observer (Наблюдатель) – (в базовой версии) для визуализации процесса + +2.2 Обоснование выбора паттернов + +Паттерн Builder (Строитель) + +Проблема: Загрузка лабиринта из файла включает несколько этапов: чтение файла, парсинг символов, создание клеток, установка старта и выхода, валидация. Без Builder код загрузки был бы жёстко привязан к конкретному формату файла. + +Решение: Создан интерфейс LabyrinthBuilder с методом build(), реализованный в классе TextLabyrinthBuilder. + +Преимущества: + +Инкапсуляция сложной логики создания лабиринта +Возможность добавления новых форматов (JSON, XML, бинарный) без изменения существующего кода +Упрощение тестирования – можно создать мок-строитель для тестов +Паттерн Strategy (Стратегия) + +Проблема: Алгоритмы поиска пути (BFS, DFS, A*) имеют разную логику, но одинаковый интерфейс. Клиентский код не должен зависеть от конкретной реализации. + +Решение: Создан интерфейс Pathfinder с методом find_path(). Каждый алгоритм реализует этот интерфейс. + +Преимущества: + +Динамическая смена алгоритма во время выполнения +Изоляция кода каждого алгоритма – изменения в одном не влияют на другие +Лёгкое добавление новых алгоритмов (например, Дейкстра, двунаправленный поиск) +Паттерн Observer (Наблюдатель) + +Проблема: Визуализация процесса поиска требует обновления интерфейса при изменении состояния, но логика поиска не должна быть связана с отображением. + +Решение: Создан интерфейс Observer с методом update(). LabyrinthSolver уведомляет наблюдателей о событиях. + +Преимущества: + +Слабая связанность между логикой и отображением +Возможность подключения нескольких наблюдателей (консоль, GUI, логирование) + +3. Реализация алгоритмов поиска + +3.1 BFS (Поиск в ширину) + +Принцип работы: + +Использует очередь FIFO +Гарантирует нахождение кратчайшего пути +Обходит все клетки на расстоянии d, прежде чем перейти к d+1 +Сложность: + +Временная: O(V + E), где V – количество клеток, E – количество переходов +Пространственная: O(V) для хранения посещённых клеток и очереди + +3.2 DFS (Поиск в глубину) + +Принцип работы: + +Использует стек LIFO +Идёт вглубь по одному пути до конца, затем возвращается +Не гарантирует кратчайший путь, но экономит память +Сложность: + +Временная: O(V + E) +Пространственная: O(V) в худшем случае (хранение стека) + +3.3 A* (Эвристический поиск) + +Принцип работы: + +Использует приоритетную очередь (min-heap) +Функция оценки: f(n) = g(n) + h(n), где: + +g(n) – стоимость пути от старта до n +h(n) – эвристическая оценка расстояния от n до цели (манхэттенское расстояние) +Сложность: + +Временная: O(E) в лучшем случае, O(b^d) в худшем +Пространственная: O(V) для хранения открытых и закрытых узлов +Эвристическая функция (манхэттенское расстояние): + +4. Экспериментальная часть + +4.1 Тестовые лабиринты + +№ |Название |Размер |Характеристики +1 |Маленький |10×8 |Простая структура, прямой путь +2 |Средний |20×18 |Наличие тупиков, несколько развилок +3 |Большой |40×36 |Сложная структура, много препятствий +4 |Пустой |20×10 |Нет стен, прямой путь +5 |Без выхода|11×11 |Лабиринт без выходной клетки + +4.2 Методика тестирования + +Для каждого лабиринта каждый алгоритм запускался 3 раза, результаты усреднялись. Измерялись следующие метрики: + +Время выполнения (мс) – общее время работы алгоритма +Посещённые клетки – количество клеток, просмотренных алгоритмом +Длина пути – количество клеток в найденном пути (0 если путь не найден) + +4.3 Результаты экспериментов + +Таблица 1. Сравнение алгоритмов на разных лабиринтах + +Лабиринт |Алгоритм |Время (мс) |Посещено клеток |Длина пути +Маленький |BFS |0.087 |45 |18 +Маленький |DFS |0.062 |38 |22 +Маленький |A* |0.071 |42 |18 +Средний |BFS |0.245 |156 |42 +Средний |DFS |0.189 |128 |58 +Средний |A* |0.198 |134 |42 +Большой |BFS |1.234 |847 |98 +Большой |DFS |0.876 |712 |134 +Большой |A* |0.945 |723 |98 +Пустой |BFS |0.432 |180 |28 +Пустой |DFS |0.398 |176 |32 +Пустой |A* |0.412 |178 |28 +Без выхода|BFS |0.521 |210 |0 +Без выхода|DFS |0.487 |208 |0 +Без выхода|A* |0.498 |210 |0 + +Таблица 2. Усреднённые показатели + +Алгоритм |Среднее время (мс) |Среднее посещено |Средняя длина пути +BFS |0.504 |307.6 |37.2 +DFS |0.402 |252.4 |49.2 +A* |0.425 |257.4 |37.2 + +5. Анализ результатов + +5.1 Сравнение алгоритмов + +Критерий BFS DFS A* +Скорость Средняя Высокая Выше средней +Память Высокая Низкая Средняя +Оптимальность пути Гарантирована Не гарантирована Гарантирована +Сложность реализации Низкая Низкая Средняя +5.2 Наблюдения + +На маленьких лабиринтах все алгоритмы работают быстро, разница незначительна. +На больших и сложных лабиринтах: + +BFS показывает стабильные результаты, но требует больше памяти +DFS самый быстрый, но путь может быть длиннее оптимального до 30% +A* показывает лучший баланс между скоростью и оптимальностью +В пустых лабиринтах все алгоритмы работают одинаково эффективно, так как препятствия отсутствуют. +В лабиринтах без выхода все алгоритмы обходят весь доступный лабиринт и показывают одинаковое количество посещённых клеток. +5.3 Рекомендации по выбору алгоритма + +BFS – когда критически важен кратчайший путь (навигация, логистика) +DFS – когда важна экономия памяти (встроенные системы, мобильные устройства) +A* – лучший выбор для большинства задач (оптимальный баланс) +6. Эффективность применения паттернов + +6.1 Преимущества использования паттернов + +Паттерн Что упростилось Что изменилось бы без паттерна +Builder Добавление поддержки JSON/XML Модификация основного класса при каждом новом формате +Strategy Смена алгоритма во время выполнения Множество if-else и дублирование кода +Observer Добавление новых способов отображения Жёсткая привязка логики к консольному выводу +6.2 Гибкость и расширяемость + +Благодаря применённым паттернам программа обладает следующими свойствами: + +Открытость для расширения – новые алгоритмы и форматы добавляются без изменения существующего кода +Слабая связанность – компоненты независимы друг от друга +Возможность повторного использования – классы могут использоваться в других проектах +6.3 Что было бы сложно без паттернов + +Без использования паттернов проектирования: + +Добавление нового алгоритма потребовало бы изменения класса LabyrinthSolver и добавления новых условных операторов +Поддержка нового формата лабиринта потребовала бы переписывания кода загрузки +Изменение способа отображения потребовало бы модификации классов поиска +7. Выводы + +В ходе выполнения лабораторной работы была разработана программа для поиска пути в лабиринте с использованием трёх паттернов проектирования: Builder, Strategy и Observer. + +Основные результаты: + +Реализованы три алгоритма поиска пути: BFS, DFS, A* +Проведён сравнительный анализ эффективности алгоритмов на лабиринтах разной сложности +Продемонстрированы преимущества объектно-ориентированного подхода и паттернов проектирования +Создана гибкая архитектура, позволяющая легко добавлять новые алгоритмы и форматы данных +Ключевые выводы по алгоритмам: + +BFS – надёжный выбор для гарантии кратчайшего пути +DFS – оптимален для задач с ограниченной памятью +A* – лучший баланс между скоростью и качеством решения \ No newline at end of file