diff --git a/stepinim/docs/data/grafik.png b/stepinim/docs/data/grafik.png deleted file mode 100644 index 957c750..0000000 Binary files a/stepinim/docs/data/grafik.png and /dev/null differ diff --git a/stepinim/docs/data/results.csv b/stepinim/docs/data/results.csv deleted file mode 100644 index b192034..0000000 --- a/stepinim/docs/data/results.csv +++ /dev/null @@ -1,91 +0,0 @@ -Структура,Режим,Операция,Время (сек) -LinkedList,shuffled,insert,0.154480 -LinkedList,shuffled,search,0.001006 -LinkedList,shuffled,delete,0.000890 -LinkedList,shuffled,insert,1.084111 -LinkedList,shuffled,search,0.000904 -LinkedList,shuffled,delete,0.000629 -LinkedList,shuffled,insert,0.131441 -LinkedList,shuffled,search,0.001123 -LinkedList,shuffled,delete,0.000622 -LinkedList,shuffled,insert,0.163422 -LinkedList,shuffled,search,0.000789 -LinkedList,shuffled,delete,0.000530 -LinkedList,shuffled,insert,0.145036 -LinkedList,shuffled,search,0.000570 -LinkedList,shuffled,delete,0.000318 -LinkedList,sorted,insert,24.938719 -LinkedList,sorted,search,0.106848 -LinkedList,sorted,delete,0.096196 -LinkedList,sorted,insert,24.883229 -LinkedList,sorted,search,0.106409 -LinkedList,sorted,delete,0.094658 -LinkedList,sorted,insert,24.408379 -LinkedList,sorted,search,0.115546 -LinkedList,sorted,delete,0.099195 -LinkedList,sorted,insert,24.421941 -LinkedList,sorted,search,0.102282 -LinkedList,sorted,delete,0.092586 -LinkedList,sorted,insert,24.125530 -LinkedList,sorted,search,0.106052 -LinkedList,sorted,delete,0.093177 -HashTable,shuffled,insert,0.024262 -HashTable,shuffled,search,0.000651 -HashTable,shuffled,delete,0.000211 -HashTable,shuffled,insert,0.022815 -HashTable,shuffled,search,0.000259 -HashTable,shuffled,delete,0.000115 -HashTable,shuffled,insert,0.026916 -HashTable,shuffled,search,0.000264 -HashTable,shuffled,delete,0.000115 -HashTable,shuffled,insert,0.022850 -HashTable,shuffled,search,0.000251 -HashTable,shuffled,delete,0.000115 -HashTable,shuffled,insert,0.023054 -HashTable,shuffled,search,0.000261 -HashTable,shuffled,delete,0.000114 -HashTable,sorted,insert,0.021750 -HashTable,sorted,search,0.000246 -HashTable,sorted,delete,0.000110 -HashTable,sorted,insert,0.022438 -HashTable,sorted,search,0.000248 -HashTable,sorted,delete,0.000111 -HashTable,sorted,insert,0.021394 -HashTable,sorted,search,0.000230 -HashTable,sorted,delete,0.000106 -HashTable,sorted,insert,0.022591 -HashTable,sorted,search,0.000285 -HashTable,sorted,delete,0.000125 -HashTable,sorted,insert,0.021119 -HashTable,sorted,search,0.000272 -HashTable,sorted,delete,0.000122 -BST,shuffled,insert,0.054849 -BST,shuffled,search,0.000554 -BST,shuffled,delete,0.000293 -BST,shuffled,insert,0.053888 -BST,shuffled,search,0.000415 -BST,shuffled,delete,0.000260 -BST,shuffled,insert,0.053399 -BST,shuffled,search,0.000407 -BST,shuffled,delete,0.000256 -BST,shuffled,insert,0.056071 -BST,shuffled,search,0.000412 -BST,shuffled,delete,0.000261 -BST,shuffled,insert,0.053024 -BST,shuffled,search,0.000409 -BST,shuffled,delete,0.000285 -BST,sorted,insert,24.942325 -BST,sorted,search,0.108153 -BST,sorted,delete,0.094860 -BST,sorted,insert,25.196583 -BST,sorted,search,0.109160 -BST,sorted,delete,0.096340 -BST,sorted,insert,24.691507 -BST,sorted,search,0.115560 -BST,sorted,delete,0.094962 -BST,sorted,insert,24.461825 -BST,sorted,search,0.103381 -BST,sorted,delete,0.095198 -BST,sorted,insert,24.798636 -BST,sorted,search,0.101888 -BST,sorted,delete,0.093775 diff --git a/stepinim/docs/otchet_1lab b/stepinim/docs/otchet_1lab deleted file mode 100644 index 6675f22..0000000 --- a/stepinim/docs/otchet_1lab +++ /dev/null @@ -1,44 +0,0 @@ -Анализ по пунктам задания - Влияние порядка входных данных на вставку в BST - На отсортированных данных BST превращается в связный список - (все узлы добавляются только в правое поддерево), - поэтому каждая операция вставки требует прохода по всем ранее вставленным - элементам. В результате вместо среднего O(log n) получается O(n) – это хорошо - видно по резкому росту времени: с 0.02 с до ~2 с. На перемешанных данных - дерево остаётся относительно сбалансированным, и вставка быстра. - - Хеш-таблица почти не чувствительна к порядку - Время вставки, поиска и удаления в хеш-таблице определяется в первую - очередь длиной цепочек, которая зависит только от количества коллизий, а не - от порядка поступления ключей. Хеш-функция равномерно распределяет ключи по - бакетам, поэтому shuffled и sorted данные дают практически одинаковые результаты. - Небольшое влияние порядка могло бы проявиться лишь при очень высоком коэффициенте - заполнения и специфических паттернах хеширования, но на наших масштабах оно - пренебрежимо мало. - - Связный список всегда медленен при поиске - Поиск в связном списке – линейный (O(n)), потому что требуется перебрать все узлы - от головы до искомого или до конца. В нашем эксперименте поиск 110 имён занимал - в среднем 0.03 с, что на два порядка медленнее хеш-таблицы и BST в нормальном - режиме. Порядок данных не влияет на время поиска (линейный обход всегда одинаков), - что видно из таблицы. - - Удаление в каждой структуре - В связном списке удаление также O(n) из-за необходимости найти предшествующий узел. - В хеш-таблице удаление сводится к удалению в цепочке (коротком связном списке) - и практически не отличается от поиска. - В BST удаление требует поиска узла (O(log n) в сбалансированном, O(n) в - вырожденном), плюс операции по перестройке дерева (поиск минимального в правом - поддереве). В вырожденном случае (sorted) удаление деградирует так же, как и поиск/вставка. - -Вывод: какую структуру выбирать в реальной жизни - - Частые вставки/удаления + быстрый поиск → Хеш-таблица. Она обеспечивает O(1) в среднем для всех основных операций, не требует поддержания порядка, проста в реализации. Идеально для словарей, кэшей, индексов баз данных. - - Необходимость получать данные в отсортированном порядке → Сбалансированное BST (красно-чёрное, AVL-дерево). Несбалансированное BST, как показано в эксперименте, может деградировать до O(n) при неудачном порядке данных, поэтому в реальных системах всегда применяют самобалансирующиеся варианты. Их операции выполняются за O(log n) в худшем случае, а in-order обход сразу даёт отсортированный список без дополнительной сортировки. Используются в базах данных (индексы), файловых системах, ordered map в языках. - - Связный список сам по себе редко применяется для задач с частым поиском; он оправдан в сценариях, где данные обрабатываются строго последовательно (очереди, стеки, LRU-кэши), или когда вставка/удаление происходят только в начале/конце и не требуется произвольный доступ. - - Дополнительно: Если нужна и быстрая вставка/удаление, и произвольный доступ по индексу, и порядок, то рассматривают сбалансированные деревья (например, B-деревья) или комбинированные структуры (LinkedHashMap). - -Таким образом, выбор структуры определяется типичными паттернами использования: частота операций вставки, поиска, удаления и требование к упорядоченности данных. \ No newline at end of file diff --git a/stepinim/lab1_structure/docs/data/lab1_graph.png b/stepinim/lab1_structure/docs/data/lab1_graph.png new file mode 100644 index 0000000..7989d6b Binary files /dev/null and b/stepinim/lab1_structure/docs/data/lab1_graph.png differ diff --git a/stepinim/lab1_structure/docs/data/lab1_results.csv b/stepinim/lab1_structure/docs/data/lab1_results.csv new file mode 100644 index 0000000..208876b --- /dev/null +++ b/stepinim/lab1_structure/docs/data/lab1_results.csv @@ -0,0 +1,55 @@ +Структура,Режим,Повтор,Операция,Время (сек) +LinkedList,shuffled,1,insert,0.5023735999711789 +LinkedList,shuffled,1,search,0.022223800013307482 +LinkedList,shuffled,1,delete,0.010106799949426204 +LinkedList,shuffled,2,insert,0.5151404999778606 +LinkedList,shuffled,2,search,0.023844500014092773 +LinkedList,shuffled,2,delete,0.010028599994257092 +LinkedList,shuffled,3,insert,0.5328615000471473 +LinkedList,shuffled,3,search,0.020557800016831607 +LinkedList,shuffled,3,delete,0.012162799946963787 +LinkedList,sorted,1,insert,0.4577932999818586 +LinkedList,sorted,1,search,0.017212599981576204 +LinkedList,sorted,1,delete,0.012185800005681813 +LinkedList,sorted,2,insert,0.43183969997335225 +LinkedList,sorted,2,search,0.01829650002764538 +LinkedList,sorted,2,delete,0.012130599992815405 +LinkedList,sorted,3,insert,0.436789300001692 +LinkedList,sorted,3,search,0.017460400005802512 +LinkedList,sorted,3,delete,0.012465099978726357 +HashTable,shuffled,1,insert,0.0032562999986112118 +HashTable,shuffled,1,search,9.469996439293027e-05 +HashTable,shuffled,1,delete,5.15999854542315e-05 +HashTable,shuffled,2,insert,0.0031429000082425773 +HashTable,shuffled,2,search,9.000004502013326e-05 +HashTable,shuffled,2,delete,4.360004095360637e-05 +HashTable,shuffled,3,insert,0.003212600015103817 +HashTable,shuffled,3,search,0.00010830000974237919 +HashTable,shuffled,3,delete,4.650000482797623e-05 +HashTable,sorted,1,insert,0.0030796999926678836 +HashTable,sorted,1,search,8.420000085607171e-05 +HashTable,sorted,1,delete,4.730001091957092e-05 +HashTable,sorted,2,insert,0.0030180999892763793 +HashTable,sorted,2,search,9.079999290406704e-05 +HashTable,sorted,2,delete,5.299999611452222e-05 +HashTable,sorted,3,insert,0.0029779999749734998 +HashTable,sorted,3,search,8.510000770911574e-05 +HashTable,sorted,3,delete,6.589997792616487e-05 +BST,shuffled,1,insert,0.011618499993346632 +BST,shuffled,1,search,0.00031289999606087804 +BST,shuffled,1,delete,0.0002456999500282109 +BST,shuffled,2,insert,0.021565500006545335 +BST,shuffled,2,search,0.00032350001856684685 +BST,shuffled,2,delete,0.0002101999707520008 +BST,shuffled,3,insert,0.011865400010719895 +BST,shuffled,3,search,0.0003497999859973788 +BST,shuffled,3,delete,0.0002114999806508422 +BST,sorted,1,insert,1.961912199971266 +BST,sorted,1,search,0.025325599999632686 +BST,sorted,1,delete,0.03309909999370575 +BST,sorted,2,insert,1.8450072000268847 +BST,sorted,2,search,0.025074300006963313 +BST,sorted,2,delete,0.03284020000137389 +BST,sorted,3,insert,1.8502263000118546 +BST,sorted,3,search,0.028948499995749444 +BST,sorted,3,delete,0.040639499959070235 diff --git a/stepinim/lab1_structure/docs/otchet_1lab.md b/stepinim/lab1_structure/docs/otchet_1lab.md new file mode 100644 index 0000000..6bf4dba --- /dev/null +++ b/stepinim/lab1_structure/docs/otchet_1lab.md @@ -0,0 +1,15 @@ +В ходе экспериментов было показано, что производительность структуры данных сильно зависит +от её внутреннего устройства и характера входных данных. + +BST работает быстро на случайных данных, но при отсортированном порядке деградирует почти до +связного списка, из-за чего время вставки и удаления резко увеличивается. Хеш-таблица +практически не зависит от порядка входных данных, так как доступ к элементам происходит через +хеш-функцию, поэтому она показала лучшие результаты при поиске и вставке. Связный список +оказался самым медленным при поиске, так как требует последовательного обхода элементов. + +Удаление также работает по-разному: в связном списке и BST сначала требуется поиск элемента, +а в хеш-таблице удаление обычно выполняется быстрее за счёт обращения к нужному бакету. + +На практике хеш-таблицы лучше подходят для частого поиска и вставки данных, BST — когда +важно хранить элементы в отсортированном виде, а связные списки полезны в более простых +задачах, где структура данных часто изменяется и не требуется быстрый поиск. \ No newline at end of file diff --git a/stepinim/lab1_structure/test.py b/stepinim/lab1_structure/test.py index 2e18dd8..46d6350 100644 --- a/stepinim/lab1_structure/test.py +++ b/stepinim/lab1_structure/test.py @@ -1,7 +1,7 @@ import sys -sys.setrecursionlimit(20000) +sys.setrecursionlimit(30000) -csv_path = 'C:/Users/xalva/2026-rff_mp/stepinim/docs/data/results.csv' +csv_path = '/stepinim/docs/data/lab1_results.csv' #Связный список def ll_insert(head, name, phone): @@ -131,125 +131,303 @@ def bst_list_all(root): inorder(root) return result -#ТЕСТ +# ============================================================ +# TECT +# ============================================================ + +import os import random -random.seed(42) -N = 10000 -base_records = [(f"User_{i:05d}", f"123-{i:05d}") for i in range(N)] -records_shuffled = base_records.copy() -random.shuffle(records_shuffled) -records_sorted = sorted(base_records, key=lambda x: x[0]) - -# 100 случайных существующих имён из всего набора -random_sample = random.sample(base_records, 100) -search_existing = [name for name, _ in random_sample] -# 10 несуществующих -search_nonexist = [f"None_{i}" for i in range(10)] -# 50 случайных для удаления -delete_sample = random.sample(base_records, 50) -delete_names = [name for name, _ in delete_sample] - import time import csv -import statistics +import pandas as pd +import matplotlib.pyplot as plt + +# ============================================================ +# ПОДГОТОВКА ПАПОК +# ============================================================ + +DATA_DIR = os.path.join("docs", "data") +os.makedirs(DATA_DIR, exist_ok=True) + +csv_path = os.path.join(DATA_DIR, "lab1_results.csv") +graph_path = os.path.join(DATA_DIR, "lab1_graph.png") + +# ============================================================ +# ТЕСТОВЫЕ ДАННЫЕ +# ============================================================ + +random.seed(42) + +N = 3000 + +base_records = [ + (f"User_{i:05d}", f"123-{i:05d}") + for i in range(N) +] + +records_shuffled = base_records.copy() +random.shuffle(records_shuffled) + +records_sorted = sorted(base_records, key=lambda x: x[0]) + +# Поиск +search_existing = [ + name for name, _ in random.sample(base_records, 100) +] + +search_nonexist = [ + f"None_{i}" + for i in range(10) +] + +# Удаление +delete_names = [ + name for name, _ in random.sample(base_records, 50) +] + +# ============================================================ +# СОЗДАНИЕ СТРУКТУР +# ============================================================ + +def build_structure(records, struct_type): + + if struct_type == "ll": + structure = None + + for name, phone in records: + structure = ll_insert(structure, name, phone) + + return structure + + elif struct_type == "ht": + structure = [None] * HASH_SIZE + + for name, phone in records: + ht_insert(structure, name, phone) + + return structure + + elif struct_type == "bst": + structure = None + + for name, phone in records: + structure = bst_insert(structure, name, phone) + + return structure -def measure_operations(records, struct_type): - results = [] - for rep in range(5): - if struct_type == 'll': - head = None - elif struct_type == 'ht': - head = [None] * HASH_SIZE - else: - root = None +# ============================================================ +# INSERT +# ============================================================ - 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(head, name, phone) - else: - for name, phone in records: - root = bst_insert(root, name, phone) - insert_time = time.perf_counter() - start - results.append((rep + 1, 'insert', insert_time)) +def measure_insert(records, struct_type): - start = time.perf_counter() - if struct_type == 'll': - for name in search_existing + search_nonexist: - ll_find(head, name) - elif struct_type == 'ht': - for name in search_existing + search_nonexist: - ht_find(head, name) - else: - for name in search_existing + search_nonexist: - bst_find(root, name) - search_time = time.perf_counter() - start - results.append((rep + 1, 'search', search_time)) + start = time.perf_counter() - start = time.perf_counter() - if struct_type == 'll': - for name in delete_names: - head = ll_delete(head, name) - elif struct_type == 'ht': - for name in delete_names: - ht_delete(head, name) - else: - for name in delete_names: - root = bst_delete(root, name) - delete_time = time.perf_counter() - start - results.append((rep + 1, 'delete', delete_time)) - return results + build_structure(records, struct_type) + + end = time.perf_counter() + + return end - start + + +# ============================================================ +# SEARCH +# ============================================================ + +def measure_search(records, struct_type): + + structure = build_structure(records, struct_type) + + start = time.perf_counter() + + if struct_type == "ll": + for name in search_existing + search_nonexist: + ll_find(structure, name) + + elif struct_type == "ht": + for name in search_existing + search_nonexist: + ht_find(structure, name) + + elif struct_type == "bst": + for name in search_existing + search_nonexist: + bst_find(structure, name) + + end = time.perf_counter() + + return end - start + + +# ============================================================ +# DELETE +# ============================================================ + +def measure_delete(records, struct_type): + + structure = build_structure(records, struct_type) + + start = time.perf_counter() + + if struct_type == "ll": + for name in delete_names: + structure = ll_delete(structure, name) + + elif struct_type == "ht": + for name in delete_names: + ht_delete(structure, name) + + elif struct_type == "bst": + for name in delete_names: + structure = bst_delete(structure, name) + + end = time.perf_counter() + + return end - start + + +# ============================================================ +# ЗАМЕРЫ +# ============================================================ all_data = [] -for struct_name, mode_label, records in [ - ("LinkedList", "shuffled", records_shuffled), - ("LinkedList", "sorted", records_sorted), - ("HashTable", "shuffled", records_shuffled), - ("HashTable", "sorted", records_sorted), - ("BST", "shuffled", records_shuffled), - ("BST", "sorted", records_sorted), -]: - for rep, op, t in measure_operations(records, struct_name.split('d')[0].lower()[:2] if 'Linked' in struct_name else ('ht' if 'Hash' in struct_name else 'bst')): - all_data.append([struct_name, mode_label, op, f"{t:.6f}"]) +experiments = [ + ("LinkedList", "ll"), + ("HashTable", "ht"), + ("BST", "bst") +] +modes = [ + ("shuffled", records_shuffled), + ("sorted", records_sorted) +] + +for struct_name, struct_type in experiments: + + for mode_name, records in modes: + + for rep in range(1, 4): + + insert_time = measure_insert(records, struct_type) + + search_time = measure_search(records, struct_type) + + delete_time = measure_delete(records, struct_type) + + all_data.append([ + struct_name, + mode_name, + rep, + "insert", + insert_time + ]) + + all_data.append([ + struct_name, + mode_name, + rep, + "search", + search_time + ]) + + all_data.append([ + struct_name, + mode_name, + rep, + "delete", + delete_time + ]) + +# ============================================================ +# CSV +# ============================================================ + +with open(csv_path, "w", newline="", encoding="utf-8") as f: -with open(csv_path, 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f) - writer.writerow(["Структура", "Режим", "Операция", "Время (сек)"]) + + writer.writerow([ + "Структура", + "Режим", + "Повтор", + "Операция", + "Время (сек)" + ]) + writer.writerows(all_data) -print("CSV сохранён в docs/data/results.csv") +print(f"CSV сохранён: {csv_path}") - - -import matplotlib.pyplot as plt -import pandas as pd +# ============================================================ +# ГРАФИК +# ============================================================ df = pd.read_csv(csv_path) -df_avg = df.groupby(['Структура', 'Режим', 'Операция'])['Время (сек)'].mean().reset_index() +df_avg = ( + df.groupby( + ["Структура", "Режим", "Операция"] + )["Время (сек)"] + .mean() + .reset_index() +) + +fig, ax = plt.subplots(figsize=(12, 6)) + +ops = ["insert", "search", "delete"] -fig, ax = plt.subplots(figsize=(10,6)) -ops = ['insert', 'search', 'delete'] x = range(len(ops)) + width = 0.12 -for i, (struct, mode) in enumerate([('LinkedList','shuffled'),('LinkedList','sorted'), - ('HashTable','shuffled'),('HashTable','sorted'), - ('BST','shuffled'),('BST','sorted')]): - subset = df_avg[(df_avg['Структура']==struct) & (df_avg['Режим']==mode)] - times = [subset[subset['Операция']==op]['Время (сек)'].values[0] for op in ops] - ax.bar([p + i*width for p in x], times, width, label=f"{struct} ({mode})") +configs = [ + ("LinkedList", "shuffled"), + ("LinkedList", "sorted"), + ("HashTable", "shuffled"), + ("HashTable", "sorted"), + ("BST", "shuffled"), + ("BST", "sorted") +] + +for i, (struct, mode) in enumerate(configs): + + subset = df_avg[ + (df_avg["Структура"] == struct) + & + (df_avg["Режим"] == mode) + ] + + times = [ + subset[ + subset["Операция"] == op + ]["Время (сек)"].values[0] + for op in ops + ] + + ax.bar( + [p + i * width for p in x], + times, + width, + label=f"{struct} ({mode})" + ) + +ax.set_xticks([p + 2.5 * width for p in x]) -ax.set_xticks([p + 2.5*width for p in x]) ax.set_xticklabels(ops) -ax.set_ylabel('Среднее время (сек)') -ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left') + +ax.set_ylabel("Среднее время (сек)") + +ax.set_title("Сравнение структур данных") + +ax.legend( + bbox_to_anchor=(1.05, 1), + loc="upper left" +) + plt.tight_layout() -plt.savefig('C:/Users/xalva/2026-rff_mp/stepinim/docs/data/grafik.png') + +plt.savefig(graph_path) + +print(f"График сохранён: {graph_path}") + plt.show() \ No newline at end of file diff --git a/stepinim/lab2_oop/docs/data/chart_time_2lab.png b/stepinim/lab2_oop/docs/data/chart_time_2lab.png new file mode 100644 index 0000000..e2ace45 Binary files /dev/null and b/stepinim/lab2_oop/docs/data/chart_time_2lab.png differ diff --git a/stepinim/lab2_oop/docs/data/empty_2lab.txt b/stepinim/lab2_oop/docs/data/empty_2lab.txt new file mode 100644 index 0000000..db11fb1 --- /dev/null +++ b/stepinim/lab2_oop/docs/data/empty_2lab.txt @@ -0,0 +1,20 @@ +S + + + + + + + + + + + + + + + + + + + E diff --git a/stepinim/lab2_oop/docs/data/large_2lab.txt b/stepinim/lab2_oop/docs/data/large_2lab.txt new file mode 100644 index 0000000..a446b35 --- /dev/null +++ b/stepinim/lab2_oop/docs/data/large_2lab.txt @@ -0,0 +1,100 @@ +#################################################################################################### +#S # # ### ## ## # # ### # # ## # # ### ## #### # ## ## # ##### +# # # # ## ## # # # # # # # # # ##### # ## # # ## # ## # # +## # ## ### ## #### ## # # # ## #### # ## #### ## ### # # # ## # # ## # +### # ## ## ### # # #### # # # # ## ## ###### ## ## # # ### # ### +## # # # ### # # ## ### ### # # ## # # # ## # # ####### # # +###### ### ## # # # # # # ### ### ## ### ### ##### # # ## # # ## ## #### +#### # ## # # ## # ## ## ## # ## # # ## ## # # # # ## ### +# # # # # ###### # # ## #### # # # ## # # ### ##### ## # # +# ### # # # # ## ### # # ## #### # # # ###### # # ###### # ## # +## ## ## ### # # ### # # ## # ## ### ### ## ## # ### # # # +## # ## # # ## # ### # ## # ### ## # ### ### ### # ## ## # ### # # # +# # # # # ### # ## # # # ## # # # # # # # # # # ### ## # # ## # # # +## # # # #### ## # # # # # # ### # # ##### # # # # # # # # # +# ## ## # ## # # #### # # ## #### # ## # # # # # # # # # +# # ### # # # ## # # # # ## ## # # # # ## # # # # ## ## #### # ## # # # +# # ### # # ## # ## #### #### ## # #### # ### ## # # # # ## ## # +##### # # # # # ## # ## # ### # # # # # # # # # # ### ## # ## ## #### # # # +# # # #### ## ## ## # # # # # # # # # ## # # ## # ### # # ## # # # ## +### # ### ####### ### # # # # # ## ## # ## # ## # ## ### +# # ### ######## ### ## # # # # # # ## ## # ## ### # # # ### ### ### +# # ## ## # # ### ### ## # # ## # # # # # ## ## ### # # #### #### ### +# ### ## ### #### # ## # # ## # ## # ## ### # # # # ### # ## ### +#### ## # ### # # ## ### ## # # # # # # # ## # ## ## # # # # ## ## # +### ## # # #### ## ## ##### ## # # # ## # ## # # # # # # # # ## # ## # # +# # # # # # # ## ## # ### # ## ## # ## # # # ## # # ## ## # # # # ## # +# # ###### # # # # # # # # ## ## # # # ## # # # # # # ## # # ## # # ## +# # # # # ## # # # # # # # # ## # ## # #### # ### #### ## # # ## ## +## # # ## # ### # #### ## # ### ## # # # # ### # ## # # # # # +# # ### # ### # ## # ## ## # # # ### ##### ## ### # ## ### # # # ## ###### # +## # # # ## # # #### # # ## # ## # # # # # # # # # # # # # +## # # # ## #### # ### ### ## # ## ##### # # # # # # # # # # ##### # # ## +# ## ## # # # # ### ## # # ### ### ## # # # # ## # # ## ### # # # ### +# ## ## ## ### # # #### # ## ## ## ### ### # # ## ## +## # # ##### #### # # ## ## ### # # # # # # # # # # ### # #### # +# # # # # # #### # # ## # # # ## # # # # # # # ### # # ## +## ### ## ### # ## ## ### # ## # # # # # # # # # # #### ## ## +## # ### ## ## # # ### # # #### # # # ### # # # # # # #### # # +# # # ## ### # # # # # # ## ## # # ## ## # ## # # # # ### ## # # +# ### # ### ## # # # # # # ## # ###### # ## ## ## # ## ## ## # ## # # +# # # # # ## # # # # # # ## # # # ###### # # ### # # +# # ## # # ## # # ## ### # ## ## ## # # # # # # # # # ## # # # +## #### ## ### #### # # ### # # # # # ## # ## # # ##### ## ## # # ## # +## # # # ## # ## # # ## # #### # # ### # #### # ## # ##### # # # # +# # # # # ## #### # # ## # # # # ## # ## ## # # # ## # # ## ## ### # # +# # # ## # ## # # ## ## # ## # # ## # ## # # # ##### # +## #### ## ##### # ### # ### # # ## # ## # # # # # # # ## # ### # ## # +# # # # # ## # ## # ## ## # # ### # ####### # # ## # ### ## ## ## ## ## # +### ## # ## ## # ## # ### ### # ### # # # ###### ### ### # # # ## ### # # +## # ## ## # ## ### # # # # ## # ### # ### ## # ## # # # # # +# # ## ## ## # # # # ## # # ## # ## # # # ## # # # # # # # # ## # # # +# # # ## # ## ## ## #### ## ## ## ### ##### ###### ## ### # # # +# # ## # # # # ## # # # # # ## # #### ## # ## # ### # ## # +# # ## # # # ### # # # # ## ### ## # ## # # # # # ## # # # ### ## # # +# ## # # # ## # # # # # ## # # # # ## # # # # ###### ## ### # # +# ## ## # ### # # ## ####### ## # ####### # # # # ## # ### # ## # # # +## ### ## ### # ## # # ## ### # ### ### # # # ## # # ## # ### # # +# # # ### ## # # ## # ## ## # ### # # # ### ###### # # # # +## # ### ## # ### ## ## # ## # ## ## ### ## ### ### ## ## # ## # +# # #### ## # # ## ## # #### # ### # ## ## # # # # # ## # # +# # # # #### # # # ### ## # ## ## ## # # ## # ##### # # # # # ## # #### # # # +# # ### # # ## #### # # # # ## ## # ## ## ### # ## ## ## # ## # +## # ## # # # # # ## ## # ## ## ## # ## # #### ## # ## # +# # # ## # ## # # # # ### # # ## ## # # # # # ### # ### ## # # ## # +# # # ### # # ## ## # # ## # ### # # ### # ## # ### # ## ## ## # # +### # ## # # #### ### ## # ## # ## # ## #### # # ###### # # #### # ## # +# # # # # # ### # # # # ## #### ### # # # # # ## # # # #### # #### # # +## ## # ## # # # # # ## # ### #### # ## ### # ##### ### ## ### # # # +# # # ####### ## ### # ####### # # # ####### # # # ### # # ## # # # # +## # # ## ## ## # ### ## ## ### ## ## # # ## # ## ## # # # # # +### # # #### ### # # ## # ## # # # #### # # # # # # # # ## # ## # +# ##### # ## ### ## #### # # # ## #### # # # # ## # # ## # # +# # # # # ### # ## #### # # # ### # # # # ## # # # # # # # +# # ## ## #### # # ### ## ## ### ### ## ## # # # # # ## # ## ### # +## # # ## # # ## # ## # ### # ### # # # ## # ### #### # ## # +# ## # # # # ## ## # # # # ## # ### ## ###### ### # ## ### # # # # # # +### # # # # #### # # # # ### ##### # ## # ## # # ## # # # ## # +# # # # ##### # # #### # # # ## # ## # # ## ### # # ## # #### # +### ## # # # ## # # ## # # ## # # # ### ## ## # ### # # ## # # # # # +#### # # # # ## # # # ## ### # # # ### # ## # # ## # # # ## # # +# # # ## # ## # # ## ### # ## ## # ## # ## ## # # # ## # # ### ## ## ## # +# # # ## # # # ## # ## ## # ## # ## ## #### #### # ## # # # +## # # #### ### ## # #### ### ### # ## # # ## ## # ## # ### # +# ## ##### # # ## ### ### # # # # ### #### ## ## ## ## ### ## # # +# # ## # ## ## # # ### # # # # ## # ### ## ## ## ## ## # # # # +# #### # # #### # ## ### ###### # # # ## # # # # # # # # # #### ## # # # # +# #### # ### ##### # ## ## # # ## ## # ## ## # ## ### # # # # # # # +# # ## # # # ## # # ## ## # # # # ## # # # ## ### # ## # # +### # ## # # # ## # # # #### ##### # # ## # ## # # # # ### # # +# # # # ### # ## # # # # ## # # # ### # ## # # # # # # # # # # # # +## # # # # ## ## # #### ## # # ## # # # # # ### # # # #### # # ## ## # # # +## # # ## # #### # # # # ## # ### ## ## #### # ## ## # ## ## #### ## ### #### # +# ### ### ### # ### ### ## # # # ###### # # ### ## # # # # # # # # +## ### ## # ## # ## # ## ### # # # #### ### ### # # +# # ## # # # ## ## # # # # ### ### # ### ### # # # # ### ## # # +## # ## # # # # ###### ## ### # ### # # # ## # ## ## # ### # # # ## # ## # +# ## # ## # # # ## ## # ## ## ## # ## # ## # # # # ## # # # # +# ### # ## ## # #### # ##### # ## ## ## ### # # # # ### # +## # ## # # # # ##### # ## # # # ## # ### # # # ## # ## # # # ## E# +#################################################################################################### diff --git a/stepinim/lab2_oop/docs/data/medium_2lab.txt b/stepinim/lab2_oop/docs/data/medium_2lab.txt new file mode 100644 index 0000000..b6b2a97 --- /dev/null +++ b/stepinim/lab2_oop/docs/data/medium_2lab.txt @@ -0,0 +1,50 @@ +################################################## +#S ## ### # # # #### # ## # +# # ## # # # # ##### ### # ### # # +# ## #### # ### ### ## # # #### ### # # # +#### # # # # # ## # #### +# ## # #### ## # # ## # # # # +# ### # # # # ### # ## +# # # # ## # # ## # ## # # # +## # # # #### # # # # # ### ## +## # # ## ## ### # # ## # +## # # # # # # # # # # # # # +# # ## # ## ## ## ## ## # ### +# ### # # # ## ## # # # # ## # ## +## # # # # ## # # # # # +# # # # # ### # ### # # ## +# # # # # # # ##### # ### ## +# # # # ## ## # # # # ### #### # +## # # # ## # # ## # ### ## ### # # +## # ## # # # # # +# ##### ## ## # # # ## # ## # # # +# # # # ### ##### ### # # ## # +## # # # ## # # ## # # # # ## # +#### # # ## # # # ## ## # ## ## # +# # ### ### ## # ## #### # # +# # ### # ## ##### # # # # ## # # +## #### # # # # # # +# ## # # # # ## ## # # ## # # +# # ## # ### # ### ## # ## # +# # # # # ## ## ## # # +# # ## ### ## ## # # # # # ## # # +## # # #### # #### # ## ## # ## # +# # # ## # # # # # # # # # +# ### ### # # # # # # # # +# ## # # # ####### # # # # # # ### # +## # # # # # # # # # ## # ## # +# # # # ## ## # # ## ### # # # # # # # +# ## # ### # # # # # # # # +# # # # # # # ## # ### # # +# # ### # # # ### # ## # # # +# ### # # # # ## # # ## # # +# ## # ### # ## ## ### # # # # +# ## # # ## ## # # # ## # # +# # ## # # # # # # # # ## # +## # # # # # # # # # # # # +# # # # ### ## ### # ## # # # # +## ##### # # # ## # ## ### # # +# ## # ## ##### # # # ## # +# # # # # # ### # # # # # # +### # # # # # # ## ## ### ## #E# +################################################## diff --git a/stepinim/lab2_oop/docs/data/no_exit_2lab.txt b/stepinim/lab2_oop/docs/data/no_exit_2lab.txt new file mode 100644 index 0000000..9cc457c --- /dev/null +++ b/stepinim/lab2_oop/docs/data/no_exit_2lab.txt @@ -0,0 +1,15 @@ +############### +#S # # +# # +## # # +# # # # +## ## # # # # +# # # # +# # # +# # # ## # +## # # # +## # +## # # # # +# # # ## +# # #E# +############### diff --git a/stepinim/lab2_oop/docs/data/results_2lab.csv b/stepinim/lab2_oop/docs/data/results_2lab.csv new file mode 100644 index 0000000..ba69850 --- /dev/null +++ b/stepinim/lab2_oop/docs/data/results_2lab.csv @@ -0,0 +1,21 @@ +maze,strategy,time_ms,visited,path_length +small,BFS,0.1971,28,15 +small,DFS,0.062,16,15 +small,A*,0.1713,28,15 +small,Dijkstra,0.148,28,15 +medium,BFS,5.3354,1377,95 +medium,DFS,0.7772,282,151 +medium,A*,3.8703,500,95 +medium,Dijkstra,8.3548,1363,95 +large,BFS,16.9817,4391,195 +large,DFS,3.414,614,285 +large,A*,5.7519,559,195 +large,Dijkstra,31.018,4380,195 +empty,BFS,2.3012,400,39 +empty,DFS,1.4237,400,191 +empty,A*,3.6105,400,39 +empty,Dijkstra,2.9606,400,39 +no_exit,BFS,0.5791,136,0 +no_exit,DFS,0.5479,136,0 +no_exit,A*,0.9933,136,0 +no_exit,Dijkstra,0.8121,136,0 diff --git a/stepinim/lab2_oop/docs/data/small_2lab.txt b/stepinim/lab2_oop/docs/data/small_2lab.txt new file mode 100644 index 0000000..9fec091 --- /dev/null +++ b/stepinim/lab2_oop/docs/data/small_2lab.txt @@ -0,0 +1,10 @@ +########## +#S # +# ###### # +# # # # +# # ## # # +# # ## # # +# # # # +# ###### # +# E# +########## diff --git a/stepinim/lab2_oop/docs/otchet_2lab.md b/stepinim/lab2_oop/docs/otchet_2lab.md new file mode 100644 index 0000000..3d7029b --- /dev/null +++ b/stepinim/lab2_oop/docs/otchet_2lab.md @@ -0,0 +1,122 @@ +ОПИСАНИЕ ЗАДАЧИ И ВЫБРАННЫХ ПАТТЕРНОВ + +Цель работы — разработать систему поиска пути в лабиринте с использованием +оопп и паттернов проектирования. + +В работе были использованы следующие паттерны: + +Strategy — для реализации алгоритмов поиска пути (BFS, DFS, A*, Dijkstra). +Позволяет менять алгоритм без изменения кода основного класса MazeSolver. + +Builder — для создания лабиринта из текстового файла. +Отделяет логику загрузки данных от основной системы. +'''mermaid + +classDiagram +class Cell { ++x ++y ++is_wall ++is_start ++is_exit ++weight ++isPassable() +} + +class Maze { ++width ++height ++start ++exit ++getCell() ++getNeighbors() ++getWeightedNeighbors() +} + +class MazeBuilder { ++buildFromFile() +} + +class TextFileMazeBuilder +MazeBuilder <|-- TextFileMazeBuilder + +class PathFindingStrategy { ++findPath() +} + +class BFSStrategy +class DFSStrategy +class AStarStrategy +class DijkstraStrategy + +PathFindingStrategy <|-- BFSStrategy +PathFindingStrategy <|-- DFSStrategy +PathFindingStrategy <|-- AStarStrategy +PathFindingStrategy <|-- DijkstraStrategy + +class MazeSolver { ++setStrategy() ++solve() +} + +MazeSolver --> PathFindingStrategy +Maze --> Cell +''' +ЛИСТИНГИ КЛЮЧЕВЫХ КЛАССОВ + +В проекте реализованы основные классы: +Cell — хранение информации о клетке лабиринта +Maze — структура лабиринта и работа с соседями +MazeSolver — запуск поиска пути +PathFindingStrategy — интерфейс алгоритмов +BFSStrategy, DFSStrategy, AStarStrategy, DijkstraStrategy — реализации алгоритмов +TextFileMazeBuilder — загрузка лабиринта из файла +SearchStats — хранение статистики + +РЕЗУЛЬТАТЫ ЭКСПЕРИМЕНТОВ + +Алгоритмы тестировались на разных лабиринтах: small, medium, large, empty, no_exit. + +Сравнивались: + +время выполнения +количество посещённых клеток +длина найденного пути + +Результаты в общем виде: + +BFS — гарантирует кратчайший путь, но посещает много клеток +DFS — быстрый, но не гарантирует оптимальный путь +A* — самый быстрый в большинстве случаев за счёт эвристики +Dijkstra — стабильный, но медленнее A* на больших лабиринтах + +АНАЛИЗ ЭФФЕКТИВНОСТИ И ПАТТЕРНОВ + +Результаты показали, что A* является наиболее эффективным алгоритмом на больших данных, +так как использует эвристику и уменьшает количество проверяемых клеток. + +BFS всегда находит оптимальный путь, но работает медленнее из-за полного обхода пространства. + +DFS быстрее по времени, но не гарантирует лучший результат. + +Dijkstra корректно работает с весами, но в данной задаче часто уступает A*. + +Паттерн Strategy позволил легко переключать алгоритмы без изменения основной логики программы. +Паттерн Builder упростил создание лабиринтов и отделил загрузку данных от логики поиска. + +ВЫВОДЫ + +В ходе работы была создана гибкая система поиска пути в лабиринте с использованием ООП +и паттернов проектирования. Благодаря Strategy алгоритмы стали независимыми и легко +заменяемыми. Благодаря Builder упростилась работа с созданием и загрузкой лабиринтов. +В целом, архитектура получилась расширяемой: можно легко добавить новый алгоритм или тип +лабиринта без переписывания существующего кода. +Таким образом, наиболее сбалансированным алгоритмом для поиска пути в лабиринте является A*, +так как он обеспечивает: + +высокую скорость работы, +оптимальность результата, +минимальное количество исследуемых состояний. + +Алгоритмы BFS и Dijkstra гарантируют оптимальность, но проигрывают по производительности, +а DFS является самым быстрым, но не гарантирует качество решения. \ No newline at end of file diff --git a/stepinim/lab2_oop/poisk.py b/stepinim/lab2_oop/poisk.py new file mode 100644 index 0000000..92b8ec4 --- /dev/null +++ b/stepinim/lab2_oop/poisk.py @@ -0,0 +1,789 @@ +import time +from collections import deque +import heapq +import csv +import os +import random +import matplotlib.pyplot as plt + + +# ============================================================ +# ЭТАП 1. МОДЕЛЬ ЛАБИРИНТА +# ============================================================ + +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 + self.weight = 1 + + def isPassable(self): + return not self.is_wall + + def __repr__(self): + return f"Cell({self.x},{self.y})" + + def __hash__(self): + return hash((self.x, self.y)) + + def __eq__(self, other): + return isinstance(other, Cell) and self.x == other.x and self.y == other.y + + +class Maze: + def __init__(self, width, height): + self.width = width + self.height = height + self.cells = [] + self.start = None + self.exit = None + + def getCell(self, x, y): + if 0 <= x < self.width and 0 <= y < self.height: + return self.cells[y][x] + return None + + def getNeighbors(self, cell): + neighbors = [] + + for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]: + nx = cell.x + dx + ny = cell.y + dy + + neighbor = self.getCell(nx, ny) + + if neighbor and neighbor.isPassable(): + neighbors.append(neighbor) + + return neighbors + + def getWeightedNeighbors(self, cell): + return [(n, n.weight) for n in self.getNeighbors(cell)] + + +# ============================================================ +# ЭТАП 2. BUILDER +# ============================================================ + +class MazeBuilder: + def buildFromFile(self, filename): + raise NotImplementedError + + +class TextFileMazeBuilder(MazeBuilder): + + def buildFromFile(self, filename): + + with open(filename, 'r', encoding='utf-8') as f: + lines = [line.rstrip('\n') for line in f] + + height = len(lines) + width = max(len(line) for line in lines) + + maze = Maze(width, height) + + for y, line in enumerate(lines): + + row = [] + + for x, char in enumerate(line): + + if char == '#': + cell = Cell(x, y, is_wall=True) + + elif char == 'S': + cell = Cell(x, y, is_start=True) + maze.start = cell + + elif char == 'E': + cell = Cell(x, y, is_exit=True) + maze.exit = cell + + else: + cell = Cell(x, y) + + row.append(cell) + + while len(row) < width: + row.append(Cell(len(row), y, is_wall=True)) + + maze.cells.append(row) + + if maze.start is None or maze.exit is None: + raise ValueError("В лабиринте нет S или E") + + return maze + + +# ============================================================ +# ВОССТАНОВЛЕНИЕ ПУТИ +# ============================================================ + +def reconstruct_path(parents, end_cell): + + path = [] + + current = end_cell + + while current is not None: + path.append(current) + current = parents[current] + + path.reverse() + + return path + + +# ============================================================ +# ЭТАП 3. STRATEGY +# ============================================================ + +class PathFindingStrategy: + + @property + def name(self): + return "Unknown" + + def findPath(self, maze, start, exit): + raise NotImplementedError + + +# ============================================================ +# BFS +# ============================================================ + +class BFSStrategy(PathFindingStrategy): + + @property + def name(self): + return "BFS" + + def findPath(self, maze, start, exit): + + queue = deque([start]) + + visited = {start} + + parents = { + start: None + } + + visited_count = 1 + + while queue: + + current = queue.popleft() + + if current == exit: + path = reconstruct_path(parents, exit) + return path, visited_count + + for neighbor in maze.getNeighbors(current): + + if neighbor not in visited: + + visited.add(neighbor) + + parents[neighbor] = current + + visited_count += 1 + + queue.append(neighbor) + + return [], visited_count + + +# ============================================================ +# DFS +# ============================================================ + +class DFSStrategy(PathFindingStrategy): + + @property + def name(self): + return "DFS" + + def findPath(self, maze, start, exit): + + stack = [start] + + visited = {start} + + parents = { + start: None + } + + visited_count = 1 + + while stack: + + current = stack.pop() + + if current == exit: + path = reconstruct_path(parents, exit) + return path, visited_count + + for neighbor in maze.getNeighbors(current): + + if neighbor not in visited: + + visited.add(neighbor) + + parents[neighbor] = current + + visited_count += 1 + + stack.append(neighbor) + + return [], visited_count + + +# ============================================================ +# A* +# ============================================================ + +class AStarStrategy(PathFindingStrategy): + + @property + def name(self): + return "A*" + + def heuristic(self, a, b): + return abs(a.x - b.x) + abs(a.y - b.y) + + def findPath(self, maze, start, exit): + + counter = 0 + + open_set = [] + + heapq.heappush(open_set, (0, counter, start)) + + parents = { + start: None + } + + g_score = { + start: 0 + } + + visited = set() + + visited_count = 0 + + while open_set: + + _, _, current = heapq.heappop(open_set) + + if current in visited: + continue + + visited.add(current) + + visited_count += 1 + + if current == exit: + path = reconstruct_path(parents, exit) + return path, visited_count + + for neighbor in maze.getNeighbors(current): + + tentative_g = g_score[current] + 1 + + if neighbor not in g_score or tentative_g < g_score[neighbor]: + + g_score[neighbor] = tentative_g + + parents[neighbor] = current + + f_score = tentative_g + self.heuristic(neighbor, exit) + + counter += 1 + + heapq.heappush( + open_set, + (f_score, counter, neighbor) + ) + + return [], visited_count + + +# ============================================================ +# DIJKSTRA +# ============================================================ + +class DijkstraStrategy(PathFindingStrategy): + + @property + def name(self): + return "Dijkstra" + + def findPath(self, maze, start, exit): + + counter = 0 + + queue = [] + + heapq.heappush(queue, (0, counter, start)) + + distances = { + start: 0 + } + + parents = { + start: None + } + + visited = set() + + visited_count = 0 + + while queue: + + dist, _, current = heapq.heappop(queue) + + if current in visited: + continue + + visited.add(current) + + visited_count += 1 + + if current == exit: + path = reconstruct_path(parents, exit) + return path, visited_count + + for neighbor, weight in maze.getWeightedNeighbors(current): + + new_dist = dist + weight + + if neighbor not in distances or new_dist < distances[neighbor]: + + distances[neighbor] = new_dist + + parents[neighbor] = current + + counter += 1 + + heapq.heappush( + queue, + (new_dist, counter, neighbor) + ) + + return [], visited_count + + +# ============================================================ +# ЭТАП 4. STATS + SOLVER +# ============================================================ + +class SearchStats: + + def __init__( + self, + strategy_name, + time_ms, + visited_cells, + path_length, + path_found + ): + self.strategy_name = strategy_name + self.time_ms = time_ms + self.visited_cells = visited_cells + self.path_length = path_length + self.path_found = path_found + + +class MazeSolver: + + def __init__(self, maze, strategy=None): + self.maze = maze + self.strategy = strategy + + def setStrategy(self, strategy): + self.strategy = strategy + + def solve(self): + + if self.strategy is None: + raise ValueError("Стратегия не выбрана") + + start_time = time.perf_counter() + + path, visited = self.strategy.findPath( + self.maze, + self.maze.start, + self.maze.exit + ) + + end_time = time.perf_counter() + + elapsed_ms = (end_time - start_time) * 1000 + + return SearchStats( + self.strategy.name, + elapsed_ms, + visited, + len(path), + len(path) > 0 + ), path + + +# ============================================================ +# ВИЗУАЛИЗАЦИЯ +# ============================================================ + +def render(maze, path=None): + + path_set = set(path) if path else set() + + for y in range(maze.height): + + line = "" + + for x in range(maze.width): + + cell = maze.getCell(x, y) + + if cell == maze.start: + line += "S" + + elif cell == maze.exit: + line += "E" + + elif cell in path_set: + line += "." + + elif cell.is_wall: + line += "#" + + else: + line += " " + + print(line) + + print() + + +# ============================================================ +# ФАЙЛЫ И ПУТИ +# ============================================================ + +OUTPUT_DIR = os.path.join("docs", "data") + +PREFIX = "_2lab" + +os.makedirs(OUTPUT_DIR, exist_ok=True) + + +def get_path(filename): + + name, ext = os.path.splitext(filename) + + return os.path.join( + OUTPUT_DIR, + f"{name}{PREFIX}{ext}" + ) + + +# ============================================================ +# СОЗДАНИЕ ЛАБИРИНТА +# ============================================================ + +def create_test_maze(filename, lines): + + with open(filename, 'w', encoding='utf-8') as f: + + for line in lines: + f.write(line + '\n') + + return filename + + +# ============================================================ +# ГЕНЕРАЦИЯ +# ============================================================ + +def generate_maze(width, height, wall_density=0.3): + + grid = [[' ' for _ in range(width)] for _ in range(height)] + + for x in range(width): + grid[0][x] = '#' + grid[height - 1][x] = '#' + + for y in range(height): + grid[y][0] = '#' + grid[y][width - 1] = '#' + + x, y = 1, 1 + + path_cells = {(x, y)} + + while x < width - 2 or y < height - 2: + + if x < width - 2 and random.random() > 0.3: + x += 1 + + elif y < height - 2: + y += 1 + + else: + x += 1 + + path_cells.add((x, y)) + + for yy in range(1, height - 1): + + for xx in range(1, width - 1): + + if (xx, yy) not in path_cells: + + if random.random() < wall_density: + grid[yy][xx] = '#' + + grid[1][1] = 'S' + grid[height - 2][width - 2] = 'E' + + return [''.join(row) for row in grid] + + +def generate_empty_maze(size): + + lines = [" " * size for _ in range(size)] + + lines[0] = "S" + " " * (size - 1) + + lines[size - 1] = " " * (size - 1) + "E" + + return lines + + +def generate_no_exit_maze(size): + + lines = generate_maze(size, size, wall_density=0.2) + + for y, line in enumerate(lines): + + if 'E' in line: + + x = line.index('E') + + for dy, dx in [(-1, 0), (1, 0), (0, -1), (0, 1)]: + + ny = y + dy + nx = x + dx + + if 0 <= ny < size and 0 <= nx < size: + + if lines[ny][nx] == ' ': + + lines[ny] = ( + lines[ny][:nx] + + '#' + + lines[ny][nx + 1:] + ) + + return lines + + +# ============================================================ +# ЭКСПЕРИМЕНТЫ +# ============================================================ + +def run_experiments(): + + mazes = { + + "small": [ + "##########", + "#S #", + "# ###### #", + "# # # #", + "# # ## # #", + "# # ## # #", + "# # # #", + "# ###### #", + "# E#", + "##########" + ], + + "medium": generate_maze(50, 50, 0.35), + + "large": generate_maze(100, 100, 0.4), + + "empty": generate_empty_maze(20), + + "no_exit": generate_no_exit_maze(15) + } + + strategies = [ + BFSStrategy(), + DFSStrategy(), + AStarStrategy(), + DijkstraStrategy() + ] + + results = [] + + print("=" * 60) + print("ЭКСПЕРИМЕНТЫ") + print("=" * 60) + + for maze_name, lines in mazes.items(): + + filename = get_path(f"{maze_name}.txt") + + create_test_maze(filename, lines) + + maze = TextFileMazeBuilder().buildFromFile(filename) + + print(f"\nЛабиринт: {maze_name}") + print("-" * 60) + + for strategy in strategies: + + times = [] + visited_values = [] + + final_path_len = 0 + + for _ in range(5): + + solver = MazeSolver(maze) + + solver.setStrategy(strategy) + + stats, path = solver.solve() + + times.append(stats.time_ms) + + visited_values.append(stats.visited_cells) + + final_path_len = stats.path_length + + avg_time = sum(times) / len(times) + + avg_visited = sum(visited_values) / len(visited_values) + + results.append({ + "maze": maze_name, + "strategy": strategy.name, + "time_ms": round(avg_time, 4), + "visited": int(avg_visited), + "path_length": final_path_len + }) + + status = "найден" if final_path_len > 0 else "не найден" + + print( + f"{strategy.name:<10} | " + f"{avg_time:>8.4f} мс | " + f"{int(avg_visited):>5} клеток | " + f"путь {status}" + ) + + csv_path = get_path("results.csv") + + with open(csv_path, "w", newline="", encoding='utf-8') as f: + + writer = csv.DictWriter( + f, + fieldnames=[ + "maze", + "strategy", + "time_ms", + "visited", + "path_length" + ] + ) + + writer.writeheader() + + writer.writerows(results) + + print(f"\nCSV сохранён: {csv_path}") + + return results + + +# ============================================================ +# ГРАФИК +# ============================================================ + +def build_charts(results): + + mazes = list(dict.fromkeys(r["maze"] for r in results)) + + strategies = list(dict.fromkeys(r["strategy"] for r in results)) + + fig, ax = plt.subplots(figsize=(12, 6)) + + x = range(len(mazes)) + + width = 0.2 + + colors = { + 'BFS': '#3498db', + 'DFS': '#e74c3c', + 'A*': '#2ecc71', + 'Dijkstra': '#f39c12' + } + + for i, strategy in enumerate(strategies): + + times = [ + r["time_ms"] + for r in results + if r["strategy"] == strategy + ] + + ax.bar( + [j + i * width for j in x], + times, + width, + label=strategy, + color=colors.get(strategy, 'gray') + ) + + ax.set_xlabel("Лабиринт") + + ax.set_ylabel("Время (мс)") + + ax.set_title("Сравнение алгоритмов") + + ax.set_xticks([j + width * 1.5 for j in x]) + + ax.set_xticklabels(mazes) + + ax.legend() + + ax.grid(axis='y', alpha=0.3) + + plt.tight_layout() + + chart_path = get_path("chart_time.png") + + plt.savefig(chart_path, dpi=150, bbox_inches='tight') + + print(f"График сохранён: {chart_path}") + + plt.show() + + +# ============================================================ +# MAIN +# ============================================================ + +def main(): + + results = run_experiments() + + build_charts(results) + + +if __name__ == "__main__": + main() \ No newline at end of file