From c865eb2b885b0b89e1199d86d0f65c3f8bd8bc48 Mon Sep 17 00:00:00 2001 From: falvah Date: Sat, 21 Feb 2026 11:29:28 +0300 Subject: [PATCH 1/7] [1]nachlo --- stepinim/[1]laba.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 stepinim/[1]laba.py diff --git a/stepinim/[1]laba.py b/stepinim/[1]laba.py new file mode 100644 index 0000000..ce47b77 --- /dev/null +++ b/stepinim/[1]laba.py @@ -0,0 +1 @@ +print("hello") \ No newline at end of file -- 2.43.0 From d346e6c40bf8d15bc8641cca0a09fedb16083673 Mon Sep 17 00:00:00 2001 From: falvah Date: Sat, 21 Feb 2026 12:42:55 +0300 Subject: [PATCH 2/7] [2]proverka --- stepinim/[1]laba.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stepinim/[1]laba.py b/stepinim/[1]laba.py index ce47b77..2435cd6 100644 --- a/stepinim/[1]laba.py +++ b/stepinim/[1]laba.py @@ -1 +1,2 @@ -print("hello") \ No newline at end of file +print("hello") +print("hi") \ No newline at end of file -- 2.43.0 From d467c49886a8aaf3827b59d7ef1ef6ed8142902c Mon Sep 17 00:00:00 2001 From: xalva Date: Sat, 11 Apr 2026 20:34:09 +0300 Subject: [PATCH 3/7] [2]new_sistem --- .idea/.gitignore | 5 +++++ .idea/2026-rff_mp.iml | 8 ++++++++ .idea/inspectionProfiles/profiles_settings.xml | 6 ++++++ .idea/misc.xml | 7 +++++++ .idea/modules.xml | 8 ++++++++ .idea/vcs.xml | 6 ++++++ 6 files changed, 40 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/2026-rff_mp.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..272833f --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Игнорируемые файлы по умолчанию +/shelf/ +/workspace.xml +# HTTP-клиент на основе редактора +/httpRequests/ diff --git a/.idea/2026-rff_mp.iml b/.idea/2026-rff_mp.iml new file mode 100644 index 0000000..c03f621 --- /dev/null +++ b/.idea/2026-rff_mp.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..590a59e --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..a150c6b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file -- 2.43.0 From 56509603c7b6b99739cc97d590df78a831bd1947 Mon Sep 17 00:00:00 2001 From: xalva Date: Tue, 28 Apr 2026 21:25:45 +0300 Subject: [PATCH 4/7] [3]lab1_test --- README.md | 54 ------- stepinim/[1]laba.py | 2 - stepinim/docs/data/grafik.png | Bin 0 -> 23463 bytes stepinim/docs/data/results.csv | 91 ++++++++++++ stepinim/docs/otchet_1lab | 44 ++++++ stepinim/lab1_structure/test.py | 255 ++++++++++++++++++++++++++++++++ 6 files changed, 390 insertions(+), 56 deletions(-) delete mode 100644 README.md delete mode 100644 stepinim/[1]laba.py create mode 100644 stepinim/docs/data/grafik.png create mode 100644 stepinim/docs/data/results.csv create mode 100644 stepinim/docs/otchet_1lab create mode 100644 stepinim/lab1_structure/test.py diff --git a/README.md b/README.md deleted file mode 100644 index 7b46480..0000000 --- a/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# 2026-MP - -Практика по курсам "Методы программирования" и "Программная инженерия" РФФ ННГУ - -[Презентация по курсу (обновляемая)](https://docs.google.com/presentation/d/1wmYjy5QDoYECEHi7NAAINPulU9pLsaIi-aLaUppspps/edit?usp=sharing) - -Для работы необходим python 3.11 и выше. Библиотеки: numpy, pandas, matplotlib, tensorflow, Pillow. Редактор любой. Из неплохих: IDLE (родной, идёт вместе с установщиком), Visual Studio Code, notepad++, PyCharm, vim (для любителей сначала страдать, потом наслаждаться). - -Работа с блокнотами онлайн, с возможностью подключения удалённых мощностей гугла (GPU, TPU): https://colab.research.google.com/ - -Мой контакт: nsmorozov@rf.unn.ru - -Внутри папки группы создать папку имени себя (фамилия и имя). В своей папке можете делать все что угодно, в чужие не залезать, в корневую тоже. Я буду ориентироваться на файлы, где в названии будет номер лабораторной. - -**Название пулл-реквеста должно начинаться с квадратных скобок, в которых перечислены номера сдаваемых лабораторных работ. Не больше одного активного реквеста, если надо довнести -- надо обновить текущий.** - -### Крайний срок приема работ 25.05.2026 до 14:00 - -## Задание 1 -- репозиторий - -0. Создай пользователя (логин — фамилия+инициалы слитно транслитом, как в терминал-классе). - -1. Зайди в этот репозиторий на Gitea, нажми кнопку **Форкнуть**, чтобы создать копию в своем аккаунте. - -2. **Клонирование:** Скопируй ссылку на свой форк и выполни: - ```bash - git clone <ссылка_на_ваш_форк> - cd <название_репозитория> - ``` - -3. **Создай ветку** (название — фамилия+инициалы слитно транслитом, буква в букву как логин): - ```bash - git checkout -b IvanovII - ``` - -4. **Создай папку** с таким же названием (`IvanovII`) и внутри неё — текстовый файл, названный номером вашей группы (например, `101.md`). - -5. **Сохрани изменения:** - ```bash - git add -A - git commit -m "[0] initial commit" - ``` - -6. Отправь ветку **в свой форк** на Gitea: - ```bash - git push origin IvanovII - ``` - -7. **Создай запрос на слияние (Pull Request):** На Gitea перейди в свой форк, выбери ветку `IvanovII`, нажмите **Запрос на слияние**. Убедитесь, что: - - Базовый репозиторий: **учебный** (преподавателя) - - Базовая ветка: **develop** - - Сравниваемая ветка: **свой форк / IvanovII** - -8. Отправь PR. \ No newline at end of file diff --git a/stepinim/[1]laba.py b/stepinim/[1]laba.py deleted file mode 100644 index 2435cd6..0000000 --- a/stepinim/[1]laba.py +++ /dev/null @@ -1,2 +0,0 @@ -print("hello") -print("hi") \ No newline at end of file diff --git a/stepinim/docs/data/grafik.png b/stepinim/docs/data/grafik.png new file mode 100644 index 0000000000000000000000000000000000000000..957c7508d60d3a3305fbcda1e1759275a1a6b291 GIT binary patch literal 23463 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 PsAQ#;u4i4n`}F?;Wr>0h literal 0 HcmV?d00001 diff --git a/stepinim/docs/data/results.csv b/stepinim/docs/data/results.csv new file mode 100644 index 0000000..b192034 --- /dev/null +++ b/stepinim/docs/data/results.csv @@ -0,0 +1,91 @@ +Структура,Режим,Операция,Время (сек) +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 new file mode 100644 index 0000000..6675f22 --- /dev/null +++ b/stepinim/docs/otchet_1lab @@ -0,0 +1,44 @@ +Анализ по пунктам задания + Влияние порядка входных данных на вставку в 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/test.py b/stepinim/lab1_structure/test.py new file mode 100644 index 0000000..2e18dd8 --- /dev/null +++ b/stepinim/lab1_structure/test.py @@ -0,0 +1,255 @@ +import sys +sys.setrecursionlimit(20000) + +csv_path = 'C:/Users/xalva/2026-rff_mp/stepinim/docs/data/results.csv' + +#Связный список +def ll_insert(head, name, phone): + new_node = {'name': name, 'phone': phone, 'next': None} + if head is None: + return new_node + + curr = head + prev = None + while curr is not None: + if curr['name'] == name: + curr['phone'] = phone + return head + prev = curr + curr = curr['next'] + prev['next'] = new_node + return head + +def ll_find(head, name): + curr = head + while curr: + if curr['name'] == name: + return curr['phone'] + curr = curr['next'] + return None + +def ll_delete(head, name): + if head is None: + return None + if head['name'] == name: + return head['next'] + curr = head + while curr['next']: + if curr['next']['name'] == name: + curr['next'] = curr['next']['next'] + return head + curr = curr['next'] + return head + +def ll_list_all(head): + result = [] + curr = head + while curr: + result.append((curr['name'], curr['phone'])) + curr = curr['next'] + result.sort(key=lambda x: x[0]) + return result + +#Хэш-таблица +HASH_SIZE = 1009 +def _hash_name(name): + return hash(name) % HASH_SIZE + +def ht_insert(buckets, name, phone): + idx = _hash_name(name) + buckets[idx] = ll_insert(buckets[idx], name, phone) + +def ht_find(buckets, name): + idx = _hash_name(name) + return ll_find(buckets[idx], name) + +def ht_delete(buckets, name): + idx = _hash_name(name) + buckets[idx] = ll_delete(buckets[idx], name) + +def ht_list_all(buckets): + all_entries = [] + for bucket in buckets: + if bucket is not None: + curr = bucket + while curr: + all_entries.append((curr['name'], curr['phone'])) + curr = curr['next'] + all_entries.sort(key=lambda x: x[0]) + return all_entries + +#Двоичное дерево поиска +def bst_insert(root, name, phone): + if root is None: + return {'name': name, 'phone': phone, 'left': None, 'right': None} + if name < root['name']: + root['left'] = bst_insert(root['left'], name, phone) + elif name > root['name']: + root['right'] = bst_insert(root['right'], name, phone) + else: + root['phone'] = phone + return root + +def bst_find(root, name): + curr = root + while curr: + if name == curr['name']: + return curr['phone'] + elif name < curr['name']: + curr = curr['left'] + else: + curr = curr['right'] + return None + +def bst_delete(root, name): + if root is None: + return None + if name < root['name']: + root['left'] = bst_delete(root['left'], name) + elif name > root['name']: + root['right'] = bst_delete(root['right'], name) + else: + if root['left'] is None: + return root['right'] + if root['right'] is None: + return root['left'] + min_node = root['right'] + while min_node['left']: + min_node = min_node['left'] + root['name'] = min_node['name'] + root['phone'] = min_node['phone'] + root['right'] = bst_delete(root['right'], min_node['name']) + return root + +def bst_list_all(root): + result = [] + def inorder(node): + if node: + inorder(node['left']) + result.append((node['name'], node['phone'])) + inorder(node['right']) + inorder(root) + return result + +#ТЕСТ +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 + + +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 + + 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)) + + 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() + 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 + +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}"]) + + +with open(csv_path, 'w', newline='', encoding='utf-8') as f: + writer = csv.writer(f) + writer.writerow(["Структура", "Режим", "Операция", "Время (сек)"]) + writer.writerows(all_data) + +print("CSV сохранён в docs/data/results.csv") + + + +import matplotlib.pyplot as plt +import pandas as pd + +df = pd.read_csv(csv_path) + +df_avg = df.groupby(['Структура', 'Режим', 'Операция'])['Время (сек)'].mean().reset_index() + +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})") + +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') +plt.tight_layout() +plt.savefig('C:/Users/xalva/2026-rff_mp/stepinim/docs/data/grafik.png') +plt.show() \ No newline at end of file -- 2.43.0 From f4e8b9732e2fd5e0cea51f0d15c8745bae4010dc Mon Sep 17 00:00:00 2001 From: xalva Date: Wed, 20 May 2026 15:19:09 +0300 Subject: [PATCH 5/7] [4]full --- stepinim/docs/data/grafik.png | Bin 23463 -> 0 bytes stepinim/docs/data/results.csv | 91 -- stepinim/docs/otchet_1lab | 44 - .../lab1_structure/docs/data/lab1_graph.png | Bin 0 -> 30319 bytes .../lab1_structure/docs/data/lab1_results.csv | 55 ++ stepinim/lab1_structure/docs/otchet_1lab.md | 15 + stepinim/lab1_structure/test.py | 370 +++++--- .../lab2_oop/docs/data/chart_time_2lab.png | Bin 0 -> 33165 bytes stepinim/lab2_oop/docs/data/empty_2lab.txt | 20 + stepinim/lab2_oop/docs/data/large_2lab.txt | 100 +++ stepinim/lab2_oop/docs/data/medium_2lab.txt | 50 ++ stepinim/lab2_oop/docs/data/no_exit_2lab.txt | 15 + stepinim/lab2_oop/docs/data/results_2lab.csv | 21 + stepinim/lab2_oop/docs/data/small_2lab.txt | 10 + stepinim/lab2_oop/docs/otchet_2lab.md | 122 +++ stepinim/lab2_oop/poisk.py | 789 ++++++++++++++++++ 16 files changed, 1471 insertions(+), 231 deletions(-) delete mode 100644 stepinim/docs/data/grafik.png delete mode 100644 stepinim/docs/data/results.csv delete mode 100644 stepinim/docs/otchet_1lab create mode 100644 stepinim/lab1_structure/docs/data/lab1_graph.png create mode 100644 stepinim/lab1_structure/docs/data/lab1_results.csv create mode 100644 stepinim/lab1_structure/docs/otchet_1lab.md create mode 100644 stepinim/lab2_oop/docs/data/chart_time_2lab.png create mode 100644 stepinim/lab2_oop/docs/data/empty_2lab.txt create mode 100644 stepinim/lab2_oop/docs/data/large_2lab.txt create mode 100644 stepinim/lab2_oop/docs/data/medium_2lab.txt create mode 100644 stepinim/lab2_oop/docs/data/no_exit_2lab.txt create mode 100644 stepinim/lab2_oop/docs/data/results_2lab.csv create mode 100644 stepinim/lab2_oop/docs/data/small_2lab.txt create mode 100644 stepinim/lab2_oop/docs/otchet_2lab.md create mode 100644 stepinim/lab2_oop/poisk.py diff --git a/stepinim/docs/data/grafik.png b/stepinim/docs/data/grafik.png deleted file mode 100644 index 957c7508d60d3a3305fbcda1e1759275a1a6b291..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23463 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 PsAQ#;u4i4n`}F?;Wr>0h 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 0000000000000000000000000000000000000000..7989d6bbc39448157f0d2667b98874e5e393c2fb GIT binary patch literal 30319 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e2ace45ed23bcac82ae2991e4c10551752f84666 GIT binary patch literal 33165 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

V_MkeXWfV7 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 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 -- 2.43.0 From bd678b4716945fd5e32f689cdb2a748b06d5edf9 Mon Sep 17 00:00:00 2001 From: xalva Date: Thu, 21 May 2026 13:40:02 +0300 Subject: [PATCH 6/7] [5]+komments --- stepinim/lab1_structure/test.py | 283 +++++++++------------- stepinim/lab2_oop/poisk.py | 410 ++++++++------------------------ 2 files changed, 209 insertions(+), 484 deletions(-) diff --git a/stepinim/lab1_structure/test.py b/stepinim/lab1_structure/test.py index 46d6350..eae9bf8 100644 --- a/stepinim/lab1_structure/test.py +++ b/stepinim/lab1_structure/test.py @@ -1,136 +1,153 @@ import sys -sys.setrecursionlimit(30000) -csv_path = '/stepinim/docs/data/lab1_results.csv' +sys.setrecursionlimit(30000) # Увеличиваю лимит рекурсии для BST -#Связный список +# Связный список def ll_insert(head, name, phone): - new_node = {'name': name, 'phone': phone, 'next': None} - if head is None: - return new_node + new_node = {'name': name, 'phone': phone, 'next': None} # Создаю новый узел + if head is None: # Если список пуст + return new_node # Возвращаю узел как голову - curr = head - prev = None - while curr is not None: - if curr['name'] == name: - curr['phone'] = phone + curr = head # Указатель для обхода + prev = None # Храню предыдущий узел + while curr is not None: # Иду по списку + if curr['name'] == name: # Если нашел такое же имя + curr['phone'] = phone # Обновляю телефон return head prev = curr curr = curr['next'] - prev['next'] = new_node + prev['next'] = new_node # Добавляю в конец return head + def ll_find(head, name): - curr = head - while curr: - if curr['name'] == name: - return curr['phone'] - curr = curr['next'] - return None + curr = head # Начинаю с головы + while curr: # Иду по всему списку + if curr['name'] == name: # Сравниваю имена + return curr['phone'] # Возвращаю телефон + curr = curr['next'] # Перехожу к следующему + return None # Не нашел + def ll_delete(head, name): - if head is None: + if head is None: # Пустой список return None - if head['name'] == name: - return head['next'] + if head['name'] == name: # Удаляю голову + return head['next'] # Возвращаю второй элемент curr = head - while curr['next']: - if curr['next']['name'] == name: - curr['next'] = curr['next']['next'] + while curr['next']: # Иду пока есть следующий + if curr['next']['name'] == name: # Нашел элемент для удаления + curr['next'] = curr['next']['next'] # Перепрыгиваю через него return head curr = curr['next'] return head + def ll_list_all(head): result = [] curr = head - while curr: + while curr: # Собираю все элементы result.append((curr['name'], curr['phone'])) curr = curr['next'] - result.sort(key=lambda x: x[0]) + result.sort(key=lambda x: x[0]) # Сортирую по имени return result -#Хэш-таблица -HASH_SIZE = 1009 + +# Хэш-таблица +HASH_SIZE = 1009 # Размер таблицы - простое число + + def _hash_name(name): - return hash(name) % HASH_SIZE + return hash(name) % HASH_SIZE # Беру остаток от деления - это индекс корзины + def ht_insert(buckets, name, phone): - idx = _hash_name(name) - buckets[idx] = ll_insert(buckets[idx], name, phone) + idx = _hash_name(name) # Вычисляю индекс корзины + buckets[idx] = ll_insert(buckets[idx], name, phone) # Метод цепочек - вставляю в список + def ht_find(buckets, name): - idx = _hash_name(name) - return ll_find(buckets[idx], name) + idx = _hash_name(name) # Нахожу корзину + return ll_find(buckets[idx], name) # Ищу в цепочке + def ht_delete(buckets, name): - idx = _hash_name(name) - buckets[idx] = ll_delete(buckets[idx], name) + idx = _hash_name(name) # Нахожу корзину + buckets[idx] = ll_delete(buckets[idx], name) # Удаляю из цепочки + def ht_list_all(buckets): all_entries = [] - for bucket in buckets: + for bucket in buckets: # Прохожу по всем корзинам if bucket is not None: curr = bucket - while curr: + while curr: # Собираю всю цепочку all_entries.append((curr['name'], curr['phone'])) curr = curr['next'] all_entries.sort(key=lambda x: x[0]) return all_entries -#Двоичное дерево поиска + +# Двоичное дерево поиска def bst_insert(root, name, phone): - if root is None: + if root is None: # Пустое место - создаю узел return {'name': name, 'phone': phone, 'left': None, 'right': None} - if name < root['name']: - root['left'] = bst_insert(root['left'], name, phone) - elif name > root['name']: - root['right'] = bst_insert(root['right'], name, phone) - else: + if name < root['name']: # Меньше - иду влево + root['left'] = bst_insert(root['left'], name, phone) # Рекурсивно вставляю в левое поддерево + elif name > root['name']: # Больше - иду вправо + root['right'] = bst_insert(root['right'], name, phone) # Рекурсивно вставляю в правое поддерево + else: # Равно - обновляю root['phone'] = phone return root + def bst_find(root, name): curr = root - while curr: - if name == curr['name']: + while curr: # Итеративный спуск по дереву + if name == curr['name']: # Нашел return curr['phone'] - elif name < curr['name']: + elif name < curr['name']: # Искомое меньше - налево curr = curr['left'] - else: + else: # Искомое больше - направо curr = curr['right'] return None + def bst_delete(root, name): if root is None: return None - if name < root['name']: + if name < root['name']: # Ищу в левом поддереве root['left'] = bst_delete(root['left'], name) - elif name > root['name']: + elif name > root['name']: # Ищу в правом поддереве root['right'] = bst_delete(root['right'], name) - else: - if root['left'] is None: - return root['right'] - if root['right'] is None: - return root['left'] + else: # Нашел узел для удаления + if root['left'] is None: # Нет левого ребенка + return root['right'] # Заменяю правым + if root['right'] is None: # Нет правого ребенка + return root['left'] # Заменяю левым + # Есть оба ребенка - ищу минимальный в правом поддереве min_node = root['right'] - while min_node['left']: + while min_node['left']: # Иду до самого левого min_node = min_node['left'] - root['name'] = min_node['name'] + root['name'] = min_node['name'] # Копирую данные преемника root['phone'] = min_node['phone'] - root['right'] = bst_delete(root['right'], min_node['name']) + root['right'] = bst_delete(root['right'], min_node['name']) # Удаляю преемника return root + def bst_list_all(root): result = [] - def inorder(node): + + def inorder(node): # Симметричный обход if node: - inorder(node['left']) - result.append((node['name'], node['phone'])) - inorder(node['right']) + inorder(node['left']) # Сначала левое + result.append((node['name'], node['phone'])) # Потом корень + inorder(node['right']) # Потом правое + inorder(root) return result + # ============================================================ # TECT # ============================================================ @@ -156,9 +173,9 @@ graph_path = os.path.join(DATA_DIR, "lab1_graph.png") # ТЕСТОВЫЕ ДАННЫЕ # ============================================================ -random.seed(42) +random.seed(42) # Фиксирую seed для повторяемости -N = 3000 +N = 3000 # 3000 записей base_records = [ (f"User_{i:05d}", f"123-{i:05d}") @@ -166,53 +183,47 @@ base_records = [ ] records_shuffled = base_records.copy() -random.shuffle(records_shuffled) +random.shuffle(records_shuffled) # Перемешанный порядок -records_sorted = sorted(base_records, key=lambda x: x[0]) +records_sorted = sorted(base_records, key=lambda x: x[0]) # Отсортированный порядок -# Поиск +# Данные для поиска search_existing = [ - name for name, _ in random.sample(base_records, 100) + name for name, _ in random.sample(base_records, 100) # 100 существующих имен ] search_nonexist = [ f"None_{i}" - for i in range(10) + for i in range(10) # 10 несуществующих имен ] -# Удаление +# Данные для удаления delete_names = [ - name for name, _ in random.sample(base_records, 50) + name for name, _ in random.sample(base_records, 50) # 50 имен для удаления ] + # ============================================================ # СОЗДАНИЕ СТРУКТУР # ============================================================ def build_structure(records, struct_type): - if struct_type == "ll": structure = None - for name, phone in records: - structure = ll_insert(structure, name, phone) - + 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) - + ht_insert(structure, name, phone) # Вставка с хэшированием return structure elif struct_type == "bst": structure = None - for name, phone in records: - structure = bst_insert(structure, name, phone) - + structure = bst_insert(structure, name, phone) # Вставка с ветвлением return structure @@ -221,13 +232,9 @@ def build_structure(records, struct_type): # ============================================================ def measure_insert(records, struct_type): - start = time.perf_counter() - - build_structure(records, struct_type) - + build_structure(records, struct_type) # Замеряю время построения структуры end = time.perf_counter() - return end - start @@ -236,25 +243,20 @@ def measure_insert(records, struct_type): # ============================================================ def measure_search(records, struct_type): - - structure = build_structure(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) - + ll_find(structure, name) # Поиск перебором elif struct_type == "ht": for name in search_existing + search_nonexist: - ht_find(structure, name) - + ht_find(structure, name) # Поиск через хэш elif struct_type == "bst": for name in search_existing + search_nonexist: - bst_find(structure, name) + bst_find(structure, name) # Поиск спуском по дереву end = time.perf_counter() - return end - start @@ -263,25 +265,20 @@ def measure_search(records, struct_type): # ============================================================ def measure_delete(records, struct_type): - - structure = build_structure(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) - + structure = ll_delete(structure, name) # Удаление со сдвигом elif struct_type == "ht": for name in delete_names: - ht_delete(structure, name) - + ht_delete(structure, name) # Удаление из цепочки elif struct_type == "bst": for name in delete_names: - structure = bst_delete(structure, name) + structure = bst_delete(structure, name) # Удаление с ребалансировкой end = time.perf_counter() - return end - start @@ -298,62 +295,28 @@ experiments = [ ] modes = [ - ("shuffled", records_shuffled), - ("sorted", records_sorted) + ("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): - + for rep in range(1, 4): # 3 повтора для усреднения 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 - ]) + 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: - writer = csv.writer(f) - - writer.writerow([ - "Структура", - "Режим", - "Повтор", - "Операция", - "Время (сек)" - ]) - + writer.writerow(["Структура", "Режим", "Повтор", "Операция", "Время (сек)"]) writer.writerows(all_data) print(f"CSV сохранён: {csv_path}") @@ -365,9 +328,7 @@ print(f"CSV сохранён: {csv_path}") df = pd.read_csv(csv_path) df_avg = ( - df.groupby( - ["Структура", "Режим", "Операция"] - )["Время (сек)"] + df.groupby(["Структура", "Режим", "Операция"])["Время (сек)"] .mean() .reset_index() ) @@ -375,9 +336,7 @@ df_avg = ( fig, ax = plt.subplots(figsize=(12, 6)) ops = ["insert", "search", "delete"] - x = range(len(ops)) - width = 0.12 configs = [ @@ -390,20 +349,14 @@ configs = [ ] for i, (struct, mode) in enumerate(configs): - subset = df_avg[ - (df_avg["Структура"] == struct) - & + (df_avg["Структура"] == struct) & (df_avg["Режим"] == mode) - ] - + ] times = [ - subset[ - subset["Операция"] == op - ]["Время (сек)"].values[0] + subset[subset["Операция"] == op]["Время (сек)"].values[0] for op in ops ] - ax.bar( [p + i * width for p in x], times, @@ -412,22 +365,12 @@ for i, (struct, mode) in enumerate(configs): ) ax.set_xticks([p + 2.5 * width for p in x]) - ax.set_xticklabels(ops) - ax.set_ylabel("Среднее время (сек)") - ax.set_title("Сравнение структур данных") - -ax.legend( - bbox_to_anchor=(1.05, 1), - loc="upper left" -) +ax.legend(bbox_to_anchor=(1.05, 1), loc="upper left") plt.tight_layout() - plt.savefig(graph_path) - print(f"График сохранён: {graph_path}") - plt.show() \ No newline at end of file diff --git a/stepinim/lab2_oop/poisk.py b/stepinim/lab2_oop/poisk.py index 92b8ec4..d284267 100644 --- a/stepinim/lab2_oop/poisk.py +++ b/stepinim/lab2_oop/poisk.py @@ -18,17 +18,20 @@ class Cell: self.is_wall = is_wall self.is_start = is_start self.is_exit = is_exit - self.weight = 1 + self.weight = 1 # Вес клетки (нужен для Дейкстры) + # Можно ли пройти через клетку def isPassable(self): return not self.is_wall def __repr__(self): return f"Cell({self.x},{self.y})" + # Хеш по координатам — чтобы класть клетки в set и dict def __hash__(self): return hash((self.x, self.y)) + # Сравнение двух клеток (нужно для set и dict) def __eq__(self, other): return isinstance(other, Cell) and self.x == other.x and self.y == other.y @@ -37,35 +40,34 @@ class Maze: def __init__(self, width, height): self.width = width self.height = height - self.cells = [] + self.cells = [] # Двумерный список: cells[y][x] 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)]: + 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 +# ЭТАП 2. ЗАГРУЗКА ЛАБИРИНТА ИЗ ФАЙЛА # ============================================================ class MazeBuilder: @@ -74,75 +76,62 @@ class MazeBuilder: 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) - + 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) - + cell = Cell(x, y, is_wall=True) # Стена elif char == 'S': cell = Cell(x, y, is_start=True) - maze.start = cell - + maze.start = cell # Запомнили старт elif char == 'E': cell = Cell(x, y, is_exit=True) - maze.exit = cell - + maze.exit = cell # Запомнили выход else: - cell = Cell(x, y) - + 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 - + # Идём от выхода к старту по цепочке parents while current is not None: path.append(current) current = parents[current] - - path.reverse() - + path.reverse() # Разворачиваем — получаем путь от старта к выходу return path # ============================================================ -# ЭТАП 3. STRATEGY +# ЭТАП 3. АЛГОРИТМЫ ПОИСКА ПУТИ # ============================================================ class PathFindingStrategy: - @property def name(self): return "Unknown" @@ -152,137 +141,89 @@ class PathFindingStrategy: # ============================================================ -# BFS +# BFS — обход в ширину (очередь) # ============================================================ - class BFSStrategy(PathFindingStrategy): - @property def name(self): return "BFS" def findPath(self, maze, start, exit): - - queue = deque([start]) - + queue = deque([start]) # Очередь: кто первый зашёл — первый вышел visited = {start} - - parents = { - start: None - } - + parents = {start: None} # Откуда пришли в клетку visited_count = 1 while queue: - - current = queue.popleft() - + 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) - + queue.append(neighbor) # Кладём в конец очереди return [], visited_count # ============================================================ -# DFS +# DFS — обход в глубину (стек) # ============================================================ - class DFSStrategy(PathFindingStrategy): - @property def name(self): return "DFS" def findPath(self, maze, start, exit): - - stack = [start] - + stack = [start] # Стек: кто последний зашёл — первый вышел visited = {start} - - parents = { - start: None - } - + parents = {start: None} visited_count = 1 while stack: - - current = stack.pop() - + 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) - + stack.append(neighbor) # Кладём на вершину стека return [], visited_count # ============================================================ -# A* +# 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 = [] - + counter = 0 # Чтобы различать клетки с одинаковым приоритетом + open_set = [] # Куча: всегда берём самую перспективную клетку heapq.heappush(open_set, (0, counter, start)) - - parents = { - start: None - } - - g_score = { - start: 0 - } - + parents = {start: None} + g_score = {start: 0} # Пройденное расстояние от старта visited = set() - visited_count = 0 while open_set: - - _, _, current = heapq.heappop(open_set) - + _, _, current = heapq.heappop(open_set) # Достаём клетку с лучшей оценкой if current in visited: continue - visited.add(current) - visited_count += 1 if current == exit: @@ -290,137 +231,86 @@ class AStarStrategy(PathFindingStrategy): return path, visited_count for neighbor in maze.getNeighbors(current): - - tentative_g = g_score[current] + 1 - + 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) - ) - + 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 = [] - + queue = [] # Куча: всегда берём клетку с кратчайшим путём от старта heapq.heappush(queue, (0, counter, start)) - - distances = { - start: 0 - } - - parents = { - start: None - } - + distances = {start: 0} # Кратчайшее известное расстояние до каждой клетки + parents = {start: None} visited = set() - visited_count = 0 while queue: - - dist, _, current = heapq.heappop(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 + # Здесь используем вес клеток, а не просто +1 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) - ) - + heapq.heappush(queue, (new_dist, counter, neighbor)) return [], visited_count # ============================================================ -# ЭТАП 4. STATS + SOLVER +# ЭТАП 4. РЕШАТЕЛЬ И СТАТИСТИКА # ============================================================ class SearchStats: - - def __init__( - self, - strategy_name, - time_ms, - visited_cells, - path_length, - path_found - ): + 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 + 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 - ) - + 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( @@ -433,171 +323,126 @@ class MazeSolver: # ============================================================ -# ВИЗУАЛИЗАЦИЯ +# ВЫВОД ЛАБИРИНТА В КОНСОЛЬ # ============================================================ def render(maze, path=None): - - path_set = set(path) if path else set() + 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 += "." - + 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) +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}" - ) + 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] = '#' + # Прокладываем гарантированную дорожку от (1,1) до (width-2, height-2) 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 - + ny, nx = y + dy, x + dx if 0 <= ny < size and 0 <= nx < size: - if lines[ny][nx] == ' ': - - lines[ny] = ( - lines[ny][:nx] - + '#' - + lines[ny][nx + 1:] - ) - + lines[ny] = lines[ny][:nx] + '#' + lines[ny][nx + 1:] return lines # ============================================================ -# ЭКСПЕРИМЕНТЫ +# ЗАПУСК ЭКСПЕРИМЕНТОВ # ============================================================ def run_experiments(): - + # Набор лабиринтов для тестов mazes = { - "small": [ "##########", "#S #", @@ -610,16 +455,13 @@ def run_experiments(): "# 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(), @@ -634,39 +476,28 @@ def run_experiments(): 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 + # Запускаем 5 раз и считаем среднее время 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({ @@ -678,110 +509,61 @@ def run_experiments(): }) status = "найден" if final_path_len > 0 else "не найден" + print(f"{strategy.name:<10} | {avg_time:>8.4f} мс | {int(avg_visited):>5} клеток | путь {status}") - print( - f"{strategy.name:<10} | " - f"{avg_time:>8.4f} мс | " - f"{int(avg_visited):>5} клеток | " - f"путь {status}" - ) - + # Сохраняем всё в CSV 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 = 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)) + 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 # Ширина одного столбика - width = 0.2 - - colors = { - 'BFS': '#3498db', - 'DFS': '#e74c3c', - 'A*': '#2ecc71', - 'Dijkstra': '#f39c12' - } + # Цвета для каждого алгоритма + 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') - ) + # Берём время этой стратегии для всех лабиринтов + 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_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) -- 2.43.0 From 74e30978aa9bd5af7cd50ad8369112a62dcbc472 Mon Sep 17 00:00:00 2001 From: stepinim Date: Sat, 30 May 2026 22:29:12 +0300 Subject: [PATCH 7/7] [6] README --- README.md | 489 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 489 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..79f7c6b --- /dev/null +++ b/README.md @@ -0,0 +1,489 @@ +# 2026-MP + +## Проверка работ: + +| имя | 1 работа | 2 работа | зачёт | +|-------------------|----------|----------|-------| +| agafonovdm | — | — | — | +| anikinvd | — | — | — | +| BolonkinNM | — | — | — | +| BoriskovaDV | — | — | — | +| BorisovMI | — | — | — | +| BudakovIS | — | — | — | +| chizhikovaSM | — | — | — | +| DerbenevRY | — | — | — | +| duznb | — | — | — | +| dyachenkoas | — | — | — | +| Ezhovnd | — | — | — | +| famutdinovmd | — | — | — | +| filippovavm | — | — | — | +| fomichevks | — | — | — | +| GorkinMM | — | — | — | +| groshevava | — | — | — | +| GutovVM | — | — | — | +| ivanchenkoam | — | — | — | +| ivantsovma | — | — | — | +| kalinovskiymi | — | — | — | +| KislyuninED | — | — | — | +| KolbasovPD | — | — | — | +| kolesovve | — | — | — | +| komissarovgo | — | — | — | +| konnovaea | — | — | — | +| kornevma | — | — | — | +| KorotkinSE | — | — | — | +| krasnovia | — | — | — | +| KuzminskiyAA | — | — | — | +| KuznetsovAS | — | — | — | +| KuznetsovMA | — | — | — | +| kuznetsovTD | — | — | — | +| KuznetsovYuM | — | — | — | +| LarikovaAA | — | — | — | +| lomakinae | — | — | — | +| LukovnikovDE | — | — | — | +| MalkinMV | — | — | — | +| MarkinAM | — | — | — | +| MashinDD | — | — | — | +| meosyam | — | — | — | +| MininaVD | — | — | — | +| MochalovAE | — | — | — | +| morozovns | — | — | — | +| MusinAA | — | — | — | +| MylnikovAS | — | — | — | +| nehoroshevaa | — | — | — | +| nikitovie | — | — | — | +| nikolaevda | — | — | — | +| novikovsd | — | — | — | +| osininyai | — | — | — | +| osipovamd | — | — | — | +| petryaninyas | — | — | — | +| pogodinda | — | — | — | +| pomelovsd | — | — | — | +| ProninVV | — | — | — | +| raskatovia | — | — | — | +| romanovpv | — | — | — | +| rybakovaa | — | — | — | +| SavelevMI | — | — | — | +| semyanovra | — | — | — | +| shahovaa | — | — | — | +| shalovsa | — | — | — | +| shapovalovka | — | — | — | +| shekurovaa | — | — | — | +| ShulpinIN | — | — | — | +| SimonovaMS | — | — | — | +| skorohodovsa | — | — | — | +| smirnovad | — | — | — | +| Smirnovvs | — | — | — | +| sobininaas | — | — | — | +| SobolevNS | — | — | — | +| SokolovEN | — | — | — | +| SokolovNE | — | — | — | +| soldatkinao | — | — | — | +| SolovevDD | — | — | — | +| SolovevDS | — | — | — | +| soninrv | — | — | — | +| SorokinAD | — | — | — | +| sorokinfi | — | — | — | +| starikovta | — | — | — | +| stepinim | — | — | — | +| stepushovgs | — | — | — | +| svetlakovkyu | — | — | — | +| talantsevgi | — | — | — | +| tseremonnikovaaa | — | — | — | +| VaravinVV | — | — | — | +| VarnakovAA | — | — | — | +| victorovaas | — | — | — | +| VildyaevAV | — | — | — | +| volkovim | — | — | — | +| VolkovVA | — | — | — | +| YanyaevAA | — | — | — | +| YaroslavtsevAS | — | — | — | +| zaharoves | — | — | — | +| ZelentsovAV | — | — | — | +| zhigalovrd | — | — | — | +| ZhuravlevDV | — | — | — | +| zverevem | — | — | — | + + + +Практика по курсам "Методы программирования" и "Программная инженерия" РФФ ННГУ + +[Презентация по курсу (обновляемая)](https://docs.google.com/presentation/d/1wmYjy5QDoYECEHi7NAAINPulU9pLsaIi-aLaUppspps/edit?usp=sharing) + +Для работы необходим python 3.11 и выше. Библиотеки: numpy, pandas, matplotlib, tensorflow, Pillow. Редактор любой. Из неплохих: IDLE (родной, идёт вместе с установщиком), Visual Studio Code, notepad++, PyCharm, vim (для любителей сначала страдать, потом наслаждаться). + +Работа с блокнотами онлайн, с возможностью подключения удалённых мощностей гугла (GPU, TPU): https://colab.research.google.com/ + +Мой контакт: nsmorozov@rf.unn.ru + +Внутри папки группы создать папку имени себя (фамилия и имя). В своей папке можете делать все что угодно, в чужие не залезать, в корневую тоже. Я буду ориентироваться на файлы, где в названии будет номер лабораторной. + +**Название пулл-реквеста должно начинаться с квадратных скобок, в которых перечислены номера сдаваемых лабораторных работ. Не больше одного активного реквеста, если надо довнести -- надо обновить текущий.** + +### Крайний срок приема работ 27.05.2026 в 10:00 ~~25.05.2026 до 14:00~~ +#### (поправочный на $\pi$, 19:00 26.05.2026, и на следующий рабочий день) + + +## Задание 0 -- репозиторий [отдельный срок на создание PR с папкой: 28.02.2026] + +0. Создай пользователя (логин — фамилия+инициалы слитно транслитом, как в терминал-классе). + +1. Зайди в этот репозиторий на Gitea, нажми кнопку **Форкнуть**, чтобы создать копию в своем аккаунте. + +2. **Клонирование:** Скопируй ссылку на свой форк и выполни: + ```bash + git clone <ссылка_на_ваш_форк> + cd <название_репозитория> + ``` + +3. **Создай ветку** (название — фамилия+инициалы слитно транслитом, буква в букву как логин): + ```bash + git checkout -b IvanovII + ``` + +4. **Создай папку** с таким же названием (`IvanovII`) и внутри неё — текстовый файл, названный номером вашей группы (например, `101.md`). + +5. **Сохрани изменения:** + ```bash + git add -A + git commit -m "[0] initial commit" + ``` + +6. Отправь ветку **в свой форк** на Gitea: + ```bash + git push origin + ``` + +если просит, перед этим сделать git push --set-upstream origin + +7. **Создай запрос на слияние (Pull Request):** На Gitea перейди в свой форк, выбери ветку `IvanovII`, нажмите **Запрос на слияние**. Убедитесь, что: + - Базовый репозиторий: **учебный** (преподавателя) + - Базовая ветка: **develop** + - Сравниваемая ветка: **свой форк / IvanovII** + +8. Отправь PR. + +## Задание 1 -- структуры данных +***Напоминание: под каждое задание вы создаете отдельную ветку*** + +>Для оформления результатов заведи папку **docs** в своей папке и сохраняй туда отчет (в любом формате от .doc до .md, а то и .jpnb). Вспомогательные файлы клади в подпапку **data** внутри **docs** + +**Цель работы** + +Реализовать три различные структуры данных «с нуля», применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций. Вы должны собственными руками написать код, чтобы понять внутреннее устройство связного списка, хеш-таблицы и двоичного дерева поиска, а также осознать их сильные и слабые стороны на практике. + +**!! Задание выполнять в структурной (процедурной) парадигме, не используя классы. Главное реализовать структуры данных «руками» и сравнить их производительность.** + +### Базовые операции (обязательны для всех): + +`insert(name, phone)` -- добавить или обновить запись. + +`find(name)` -- phone или None. + +`delete(name)` -- удалить запись, игнорировать отсутствие. + +`list_all()` -- список всех записей, отсортированный по имени (для BST in‑order обход; для списка и хеш‑таблицы — собрать и отсортировать явно). + +#### 1. Связный список (LinkedListPhoneBook) + +Узел представляется словарём: `{'name': 'Имя', 'phone': '123', 'next': None}.` + +**Функции:** + +`def ll_insert(head, name, phone)` — проходит до конца (или сразу добавляет в конец) и возвращает новую голову (если вставка в начало) или изменяет список по ссылке. Удобнее возвращать новую голову, если вставка может быть в начало. + +`def ll_find(head, name)` — ищет узел, возвращает телефон или None. + +`def ll_delete(head, name)` — удаляет узел, возвращает новую голову. + +`def ll_list_all(head)` — собирает все записи в список и сортирует (сортировка вынесена отдельно). + +#### 2. Хеш-таблица +Хранится как список buckets фиксированной длины, каждый элемент — голова связного списка (или None). + +**Функции:** + +`def ht_insert(buckets, name, phone)` — вычисляет индекс, вызывает ll_insert для соответствующего бакета. + +Аналогично `ht_find, ht_delete, ht_list_all` (последняя собирает все записи из всех бакетов и сортирует). + +#### 3. Двоичное дерево поиска +Узел — словарь: `{'name': 'Имя', 'phone': '123', 'left': None, 'right': None}.` + +**Функции:** + +`def bst_insert(root, name, phone)` — рекурсивно или итеративно вставляет, возвращает новый корень (если корень меняется). + +`def bst_find(root, name)` — поиск. + +`def bst_delete(root, name)` — удаление, возвращает новый корень. + +`def bst_list_all(root)` — центрированный обход (рекурсивно собирает записи в отсортированном порядке). + +### Экспериментальная часть (подробно об измерении времени) +#### 1. Генерация тестовых данных +Создайте список records из N элементов (например, N = 10000). Каждый элемент — кортеж (name, phone). + +Имена генерируйте как `f"User_{i:05d}"` (равномерное распределение) или случайные слова из небольшого набора (чтобы были повторения и коллизии). Для проверки влияния порядка подготовьте два варианта одного и того же набора: + +`records_shuffled` — случайный порядок. + +`records_sorted` — отсортированный по имени (по алфавиту). + +#### 2. Инструменты замера времени +Используйте модуль **time**: + +```python +import time + +start = time.perf_counter() +# ... операции ... +end = time.perf_counter() +elapsed = end - start # время в секундах +``` + +Для многократных замеров удобен `timeit`, но в этой задаче достаточно просто обернуть код в цикл и усреднить. + +#### 3. Проведение замеров +Для каждой структуры данных и для каждого режима входных данных (случайный / отсортированный) выполните: + +- А. Вставка всех записей + +Создайте пустую структуру. + +Засеките время, выполните insert для каждой записи из входного списка. + +Зафиксируйте общее время вставки. + +- Б. Поиск 100 случайных записей + +Возьмите 100 случайных имён из того же набора (гарантированно существующих) и 10 имён, которых нет (например, "None_{i}"). + +Засеките время на выполнение всех 110 вызовов find. + +- В. Удаление 50 случайных записей + +Выберите 50 случайных имён из набора. + +Засеките время на выполнение delete для каждого. + + +**!! Важно: после вставки структура остаётся заполненной, поиск и удаление выполняются на ней же. Если нужно повторить замер для другого порядка данных — создавайте новую структуру и заполняйте заново.** + +#### 4. Сохранение результатов + +**!! Каждый эксперимент повторить минимум 5 раз и записывать и среднее время, и все замеры.** + +Соберите все замеры в словарь или список, затем сохраните в CSV-файл: + +```python +import csv + +results = [ + ["Структура", "Режим", "Операция", "Время (сек)"], + ["LinkedList", "случайный", "вставка", 0.123], + ... +] + +with open("results.csv", "w", newline="") as f: + writer = csv.writer(f) + writer.writerows(results) +``` + + +#### 5. Анализ результатов +Постройте график (столбчатая диаграмма или линейный график) — можно в Excel, Google Sheets или с помощью matplotlib в Python. + +Сравните: + +- Как порядок входных данных влияет на скорость вставки в BST (деградация до O(n) на отсортированных данных). + +- Почему хеш-таблица почти не чувствительна к порядку. + +- Почему связный список всегда медленен при поиске. + +- Как удаление работает в каждой структуре. + +* Вывод должен содержать ответ на вопрос: какую структуру и для каких задач (частые вставки, частый поиск, необходимость получать данные в порядке) стоит выбирать в реальной жизни.* + +## Задание: Поиск выхода из лабиринта (объектно-ориентированная реализация с паттернами) + +### Цель работы +Разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. В ходе работы необходимо применить минимум 3 паттерна проектирования из списка GoF, обосновать их выбор и продемонстрировать преимущества такой архитектуры. + +### Общая схема приложения (пример) + +```mermaid +classDiagram + class Maze { + -Cell[] cells + -int width, height + -Cell start + -Cell exit + +getCell(x,y): Cell + +getNeighbors(cell): List~Cell~ + } + + class Cell { + -int x, y + -bool isWall + -bool isStart + -bool isExit + +isPassable(): bool + } + + class MazeBuilder { + <> + +buildFromFile(filename): Maze + } + + class TextFileMazeBuilder { + +buildFromFile(filename): Maze + } + + class PathFindingStrategy { + <> + +findPath(maze, start, exit): List~Cell~ + } + + class BFSStrategy + class DFSStrategy + class AStarStrategy + class DijkstraStrategy + + class SearchStats { + +timeMs: float + +visitedCells: int + +pathLength: int + } + + class MazeSolver { + -Maze maze + -PathFindingStrategy strategy + +setStrategy(strategy) + +solve(): SearchStats + } + + class Command { + <> + +execute() + +undo() + } + + class MoveCommand { + -Player player + -Direction dir + -Cell previousCell + +execute() + +undo() + } + + class Player { + -Cell currentCell + +moveTo(cell) + } + + class Observer { + <> + +update(event) + } + + class ConsoleView { + +update(event) + +render(maze, player, path) + } + + MazeBuilder <|.. TextFileMazeBuilder + MazeBuilder --> Maze : creates + PathFindingStrategy <|.. BFSStrategy + PathFindingStrategy <|.. DFSStrategy + PathFindingStrategy <|.. AStarStrategy + PathFindingStrategy <|.. DijkstraStrategy + MazeSolver --> PathFindingStrategy : uses + MazeSolver --> Maze : uses + Command <|.. MoveCommand + MoveCommand --> Player + Player --> Cell + Observer <|.. ConsoleView + MazeSolver --> Observer : notifies +``` + +### Выполнение + +#### Этап 1. Модель лабиринта (без паттернов, просто классы) +**Задача:** Создать классы `Cell` и `Maze`, которые представляют карту лабиринта. +- `Cell` хранит координаты (x, y), флаги `isWall`, `isStart`, `isExit`, метод `isPassable()` (возвращает `True` для прохода, если не стена). +- `Maze` хранит двумерный массив клеток, ширину, высоту, ссылки на стартовую и выходную клетку. Методы: `getCell(x, y)`, `getNeighbors(cell)` – возвращает список соседних проходимых клеток (вверх, вниз, влево, вправо, если в пределах границ и не стена). + +**Результат:** Лабиринт можно создать вручную в коде, но загрузку пока не делаем. + +#### Этап 2. Загрузка лабиринта из файла – применение паттерна **Builder** +**Задача:** Реализовать загрузку лабиринта из текстового файла, где `#` – стена, ` ` (пробел) – проход, `S` – старт, `E` – выход. +- Создать интерфейс `MazeBuilder` с методом `buildFromFile(filename)`. +- Реализовать класс `TextFileMazeBuilder`, который читает файл, парсит символы, создаёт объекты `Cell`, задаёт координаты и флаги, после чего возвращает готовый `Maze`. + +Процесс построения лабиринта сложный (парсинг, валидация, установка старта/выхода). Builder скрывает детали создания от клиента. В будущем можно легко добавить другой формат (например, JSON или бинарный) через новую реализацию `MazeBuilder`. + +#### Этап 3. Стратегии поиска пути – паттерн **Strategy** +**Задача:** Реализовать семейство алгоритмов поиска пути от старта до выхода. +- Создать интерфейс `PathFindingStrategy` с методом `findPath(maze, start, exit)`, возвращающим список клеток пути (от старта до выхода включительно) или пустой список, если пути нет. +- Реализовать минимум 3 стратегии: + - **BFS** (поиск в ширину) – гарантирует кратчайший путь по количеству шагов. + - **DFS** (поиск в глубину) – быстрый, но не обязательно кратчайший. + - **A*** (с эвристикой, например, манхэттенское расстояние) – компромисс между скоростью и оптимальностью. + - (Опционально) **Дейкстра** – полезна для взвешенных лабиринтов, но в базовом варианте все шаги имеют вес 1, тогда она совпадает с BFS. + +Каждая стратегия возвращает путь. Для BFS/DFS используйте очередь/стек, для A* – приоритетную очередь (heapq). Важно: алгоритмы не должны модифицировать сам лабиринт, только читать состояние клеток. + +Strategy позволяет легко переключать алгоритмы во время выполнения, не меняя код остальной программы. Новый алгоритм можно добавить, реализовав интерфейс. + +#### Этап 4. Класс-оркестратор – **MazeSolver** (использует Strategy) +**Задача:** Создать класс, который принимает лабиринт и стратегию, выполняет поиск и собирает статистику. +- `MazeSolver` содержит поля `maze` и `strategy`. +- Метод `setStrategy(strategy)` для динамической смены алгоритма. +- Метод `solve()` вызывает `strategy.findPath(...)` и возвращает объект `SearchStats` (время выполнения в миллисекундах, количество посещённых клеток, длина найденного пути). +- Для замера времени используйте `time.perf_counter()` до и после вызова стратегии. + +#### Этап 5. Визуализация и пошаговое управление – паттерны **Observer** и **Command** (по желанию) +**5.1. Наблюдатель (Observer)** – обновление консольного интерфейса. +- Создать интерфейс `Observer` с методом `update(event)`, где `event` может быть строкой или объектом с типом события (`"path_found"`, `"move"`, `"maze_loaded"`). +- Реализовать класс `ConsoleView`, который отображает лабиринт, текущее положение игрока (если реализован пошаговый режим) и найденный путь. Метод `render(maze, player_position, path)` рисует карту в консоли. +- `MazeSolver` (или отдельный контроллер) может иметь список наблюдателей и уведомлять их при изменении состояния. + +**5.2. Команда (Command)** – для пошагового перемещения игрока по найденному пути (или ручного управления). +- Создать интерфейс `Command` с методами `execute()` и `undo()`. +- Реализовать `MoveCommand`, который принимает игрока (`Player`), направление и изменяет его позицию, сохраняя предыдущую для отмены. +- Создать класс `Player`, хранящий текущую клетку. +- Консольное меню позволяет вводить команды (W/A/S/D), выполнять `MoveCommand`, при необходимости отменять последний ход (Ctrl+Z). Это опционально, но очень наглядно демонстрирует паттерн. + +*Observer можно реализовать только для вывода сообщений о начале/конце поиска, а Command – для демонстрации undo при ручном исследовании лабиринта.* + +#### Этап 6. Экспериментальная часть (аналогично заданию со структурами данных) +**Задача:** Сравнить эффективность реализованных стратегий на лабиринтах разной сложности. +1. **Подготовка тестовых лабиринтов:** + - Маленький (10×10) с простым путём. + - Средний (50×50) с тупиками. + - Большой (100×100) с запутанной структурой. + - «Пустой» лабиринт (без стен) – для демонстрации максимальной производительности. + - «Без выхода» – чтобы проверить обработку отсутствия пути. +2. **Замеры:** + - Для каждого лабиринта и каждой стратегии запустить `solve()` 5–10 раз, усреднить время, количество посещённых клеток, длину пути. + - Записать результаты в CSV: `лабиринт,стратегия,время_мс,посещено_клеток,длина_пути`. +3. **Анализ:** + - Построить графики для каждого лабиринта. + - Проанализировать и написать выводы по итогам (эффективность того или иного алгоритма в разных случаях). + +4. **Дополнительное задание:** Реализовать взвешенные клетки (например, болото – вес 3, песок – вес 2, асфальт – вес 1) и сравнить Дейкстру с A* на взвешенном графе. + +#### Этап 7. Отчёт +**Структура отчёта:** +1. Описание задачи и выбранных паттернов (с диаграммой классов из Mermaid). +2. Листинги ключевых классов (можно выборочно) или ссылка на репозиторий. +3. Результаты экспериментов (таблицы, графики). +4. Анализ эффективности алгоритмов и применимости паттернов. +5. Выводы: как ООП и паттерны помогли сделать код гибким и расширяемым. Что было бы сложно изменить без них. + +### Советы +- Для A* самая простая эвристика: `abs(x1 - x2) + abs(y1 - y2)`. +- При поиске пути надо хранить предшественников (`parent` для каждой посещённой клетки), чтобы восстановить путь. +- Для BFS/DFS используй `deque` (очередь) и `list` (стек). +- Визуализацию в консоли можно сделать с помощью `os.system('cls' if os.name == 'nt' else 'clear')` для перерисовки. -- 2.43.0