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 0000000..957c750 Binary files /dev/null and b/stepinim/docs/data/grafik.png differ 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