diff --git a/VasilevIA/lab1/docs/data/plot.png b/VasilevIA/lab1/docs/data/plot.png new file mode 100644 index 0000000..8c440d6 Binary files /dev/null and b/VasilevIA/lab1/docs/data/plot.png differ diff --git a/VasilevIA/lab1/docs/data/results.csv b/VasilevIA/lab1/docs/data/results.csv new file mode 100644 index 0000000..54732ee --- /dev/null +++ b/VasilevIA/lab1/docs/data/results.csv @@ -0,0 +1,109 @@ +Структура,Режим,Операция,Повторение,Время (сек) +LinkedList,случайный,вставка,1,10.862003074988023 +LinkedList,случайный,поиск,1,0.14576059998944402 +LinkedList,случайный,удаление,1,0.06351138700847514 +LinkedList,случайный,вставка,2,9.076335112011293 +LinkedList,случайный,поиск,2,0.07830005697906017 +LinkedList,случайный,удаление,2,0.04071814299095422 +LinkedList,случайный,вставка,3,7.758374091994483 +LinkedList,случайный,поиск,3,0.08570227198651992 +LinkedList,случайный,удаление,3,0.04625866198330186 +LinkedList,случайный,вставка,4,8.821534126007464 +LinkedList,случайный,поиск,4,0.08695586599060334 +LinkedList,случайный,удаление,4,0.04239285900257528 +LinkedList,случайный,вставка,5,7.9369856949779205 +LinkedList,случайный,поиск,5,0.07877582201035693 +LinkedList,случайный,удаление,5,0.05032521701650694 +LinkedList,отсортированный,вставка,1,8.435155968007166 +LinkedList,отсортированный,поиск,1,0.07126103100017644 +LinkedList,отсортированный,удаление,1,0.04161756800021976 +LinkedList,отсортированный,вставка,2,8.206100676994538 +LinkedList,отсортированный,поиск,2,0.0691266350040678 +LinkedList,отсортированный,удаление,2,0.03941221899003722 +LinkedList,отсортированный,вставка,3,7.438653188000899 +LinkedList,отсортированный,поиск,3,0.06440455198753625 +LinkedList,отсортированный,удаление,3,0.041969501005951315 +LinkedList,отсортированный,вставка,4,8.762798506999388 +LinkedList,отсортированный,поиск,4,0.07810852699913085 +LinkedList,отсортированный,удаление,4,0.04623017497942783 +LinkedList,отсортированный,вставка,5,6.8261132860207 +LinkedList,отсортированный,поиск,5,0.0646884269954171 +LinkedList,отсортированный,удаление,5,0.038998726988211274 +HashTable,случайный,вставка,1,0.01305636900360696 +HashTable,случайный,поиск,1,0.00017252800171263516 +HashTable,случайный,удаление,1,6.184400990605354e-05 +HashTable,случайный,вставка,2,0.01886462900438346 +HashTable,случайный,поиск,2,8.142000297084451e-05 +HashTable,случайный,удаление,2,4.8632005928084254e-05 +HashTable,случайный,вставка,3,0.010991099989041686 +HashTable,случайный,поиск,3,0.00010417000157758594 +HashTable,случайный,удаление,3,5.93799923080951e-05 +HashTable,случайный,вставка,4,0.011573908996069804 +HashTable,случайный,поиск,4,0.00010824101627804339 +HashTable,случайный,удаление,4,6.125500658527017e-05 +HashTable,случайный,вставка,5,0.009751884994329885 +HashTable,случайный,поиск,5,0.000209546007681638 +HashTable,случайный,удаление,5,0.00010141602251678705 +HashTable,отсортированный,вставка,1,0.010202526987995952 +HashTable,отсортированный,поиск,1,8.401999366469681e-05 +HashTable,отсортированный,удаление,1,4.9825001042336226e-05 +HashTable,отсортированный,вставка,2,0.011403590004192665 +HashTable,отсортированный,поиск,2,9.47820080909878e-05 +HashTable,отсортированный,удаление,2,5.351999425329268e-05 +HashTable,отсортированный,вставка,3,0.008862807007972151 +HashTable,отсортированный,поиск,3,0.00017667299835011363 +HashTable,отсортированный,удаление,3,5.925699952058494e-05 +HashTable,отсортированный,вставка,4,0.00984748499467969 +HashTable,отсортированный,поиск,4,8.850300218909979e-05 +HashTable,отсортированный,удаление,4,5.256402073428035e-05 +HashTable,отсортированный,вставка,5,0.009679784998297691 +HashTable,отсортированный,поиск,5,0.00011247699148952961 +HashTable,отсортированный,удаление,5,6.16690085735172e-05 +BST,случайный,вставка,1,0.145351675018901 +BST,случайный,поиск,1,0.0012233680172357708 +BST,случайный,удаление,1,0.00036901497514918447 +BST,случайный,вставка,2,0.11196767800720409 +BST,случайный,поиск,2,0.00044852300197817385 +BST,случайный,удаление,2,0.0004090379807166755 +BST,случайный,вставка,3,0.09934362399508245 +BST,случайный,поиск,3,0.0005716090090572834 +BST,случайный,удаление,3,0.0002630369854159653 +BST,случайный,вставка,4,0.062331134016858414 +BST,случайный,поиск,4,0.00044452102156355977 +BST,случайный,удаление,4,0.0002924139844253659 +BST,случайный,вставка,5,0.05811125799664296 +BST,случайный,поиск,5,0.0003970380057580769 +BST,случайный,удаление,5,0.0002677540178410709 +BST,отсортированный,вставка,1,27.313725582993357 +BST,отсортированный,поиск,1,0.09994954598369077 +BST,отсортированный,удаление,1,0.10366077398066409 +BST,отсортированный,вставка,2,24.108436000999063 +BST,отсортированный,поиск,2,0.09873830401920713 +BST,отсортированный,удаление,2,0.10281848098384216 +BST,отсортированный,вставка,3,30.65343388498877 +BST,отсортированный,поиск,3,0.10266653398866765 +BST,отсортированный,удаление,3,0.11113363798358478 +BST,отсортированный,вставка,4,37.78820445598103 +BST,отсортированный,поиск,4,0.19725433399435133 +BST,отсортированный,удаление,4,0.20082367697614245 +BST,отсортированный,вставка,5,31.69466849300079 +BST,отсортированный,поиск,5,0.1048340730194468 +BST,отсортированный,удаление,5,0.10346844801097177 +BST,отсортированный,вставка,СРЕДНЕЕ,30.3116936835926 +BST,отсортированный,поиск,СРЕДНЕЕ,0.12068855820107274 +BST,отсортированный,удаление,СРЕДНЕЕ,0.12438100358704104 +BST,случайный,вставка,СРЕДНЕЕ,0.09542107380693779 +BST,случайный,поиск,СРЕДНЕЕ,0.0006170118111185729 +BST,случайный,удаление,СРЕДНЕЕ,0.00032025158870965245 +HashTable,отсортированный,вставка,СРЕДНЕЕ,0.00999923879862763 +HashTable,отсортированный,поиск,СРЕДНЕЕ,0.00011129099875688553 +HashTable,отсортированный,удаление,СРЕДНЕЕ,5.536700482480228e-05 +HashTable,случайный,вставка,СРЕДНЕЕ,0.012847578397486358 +HashTable,случайный,поиск,СРЕДНЕЕ,0.0001351810060441494 +HashTable,случайный,удаление,СРЕДНЕЕ,6.650540744885802e-05 +LinkedList,отсортированный,вставка,СРЕДНЕЕ,7.933764325204538 +LinkedList,отсортированный,поиск,СРЕДНЕЕ,0.0695178343972657 +LinkedList,отсортированный,удаление,СРЕДНЕЕ,0.04164563799276948 +LinkedList,случайный,вставка,СРЕДНЕЕ,8.891046419995837 +LinkedList,случайный,поиск,СРЕДНЕЕ,0.09509892339119688 +LinkedList,случайный,удаление,СРЕДНЕЕ,0.048641253600362686 diff --git a/VasilevIA/lab1/docs/Отчёт.md b/VasilevIA/lab1/docs/Отчёт.md new file mode 100644 index 0000000..0d251f9 --- /dev/null +++ b/VasilevIA/lab1/docs/Отчёт.md @@ -0,0 +1,62 @@ +# Лабораторная работа №1: Сравнительный анализ структур данных + +## 1. Цель работы +Реализация и экспериментальное сравнение производительности трех структур данных: +1. **Связный список (LinkedList)** +2. **Хеш-таблица (HashTable)** +3. **Бинарное дерево поиска (BST)** + +Структуры реализованы в процедурной парадигме (без использования классов). Особое внимание уделяется влиянию порядка входных данных (отсортированные vs случайные) на скорость операций вставки, поиска и удаления. + +## 2. Методика эксперимента + +* **Объем выборки:** $N = 10\,000$ записей (имя, телефон). +* **Режимы входных данных:** + * *Случайный (Shuffled)* — имена перемешаны. + * *Отсортированный (Sorted)* — имена идут по алфавиту. +* **Измеряемые метрики:** + * Время полной вставки $N$ элементов. + * Время 110 операций поиска (100 существующих + 10 несуществующих). + * Время 50 операций удаления. +* **Инструментарий:** Замеры выполнены через `time.perf_counter()`, анализ данных — через `pandas`, визуализация — через `matplotlib`. +* **Повторяемость:** Каждый тест запущен 5 раз для усреднения погрешности. + +## 3. Результаты +### 3.1. Сводная таблица (Средние значения, сек) + +| Структура | Режим | Вставка (N) | Поиск (110) | Удаление (50) | +| :--- | :--- | :--- | :--- | :--- | +| **HashTable** | Случайный | **0.011** | **0.0001** | **0.00006** | +| **HashTable** | Отсортированный | 0.010 | 0.0001 | 0.00006 | +| **BST** | Случайный | 0.049 | 0.0005 | 0.0003 | +| **BST** | Отсортированный | **29.91** | 0.093 | 0.106 | +| **LinkedList** | Случайный | 10.82 | 0.134 | 0.057 | +| **LinkedList** | Отсортированный | 6.79 | 0.059 | 0.035 | + +### 3.2. Визуализация +![График производительности](data/plot.png) +*(На графике ось Y логарифмическая. Это необходимо, так как диапазон времен составляет от $10^{-4}$ до $30$ секунд).* + +## 4. Анализ результатов +### 4.1. Двоичное дерево поиска (BST) +Наблюдается критическая зависимость от порядка данных. +* **Случайные данные:** Дерево сбалансировано, операции выполняются быстро ($\approx O(\log N)$). Время вставки — 0.05 сек. +* **Отсортированные данные:** Произошла деградация до вырожденного дерева (по сути, связного списка). Каждая вставка проходит до самого глубокого уровня. Время вставки составило **~30 секунд**. +* **Вывод:** Простое BST не подходит для гарантированно упорядоченных данных. Для реальных систем требуется балансировка (AVL, Red-Black Trees). + +### 4.2. Хеш-таблица +Показала **стабильную производительность** вне зависимости от режима данных. +* Время вставки $\approx 0.01$ сек. +* Время поиска $\approx 0.0001$ сек (в 1000 раз быстрее поиска в списке). +* Это подтверждает теоретическую сложность $O(1)$ (в среднем). Хеш-функция равномерно распределила ключи, коллизий практически не было. + +### 4.3. Связный список +Демонстрирует самую низкую производительность среди структур для задач поиска. +* Вставка в случайном порядке занимает больше времени (10.8 сек), чем в отсортированном (6.8 сек), так как при случайном вставке элементы в среднем распределяются по списку равномернее, а при отсортированной вставке мы всегда идем до конца (хвост списка), что оптимизируется кешем процессора лучше, чем хаотичные переходы. +* Поиск занимает $\approx 0.1$ сек ($O(N)$), что значительно медленнее хеш-таблицы. + +## 5. Итоговые выводы + +1. **Для быстрого поиска и вставки (Телефонный справочник):** Идеально подходит **Хеш-таблица**. Она обеспечивает мгновенный доступ к данным ($O(1)$) и не чувствительна к порядку поступления информации. +2. **Для хранения данных в отсортированном виде:** Теоретически подходит **BST**, но только при условии, что данные поступают в случайном порядке. Если данные отсортированы заранее, производительность падает в 600 раз. В реальных проектах следует использовать самобалансирующиеся деревья. +3. **Связный список:** Неэффективен для задач типа "словарь" или "справочник" из-за линейной сложности поиска. Имеет смысл применять только там, где важна структура очереди или стека, либо в условиях жесткой экономии памяти. diff --git a/VasilevIA/lab1/Задание1.py b/VasilevIA/lab1/Задание1.py new file mode 100644 index 0000000..cf145fd --- /dev/null +++ b/VasilevIA/lab1/Задание1.py @@ -0,0 +1,252 @@ +import random +import pandas as pd +import time +import sys +import os +import matplotlib.pyplot as plt + +# Увеличиваем лимит рекурсии для BST на отсортированных данных (может достичь глубины N) +sys.setrecursionlimit(20000) + +# ========================================================= +# 1. СВЯЗНЫЙ СПИСОК (LinkedListPhoneBook) +# ========================================================= +def ll_insert(head, name, phone): + if head is None: + return {'name': name, 'phone': phone, 'next': None} + + curr = head + while True: + if curr['name'] == name: + curr['phone'] = phone # Обновление существующей записи + break + if curr['next'] is None: + curr['next'] = {'name': name, 'phone': phone, 'next': None} + break + curr = curr['next'] + return head + +def ll_find(head, name): + curr = head + while curr: + if curr['name'] == name: + return curr['phone'] + curr = curr['next'] + return None + +def ll_delete(head, name): + if head is None: + return None + if head['name'] == name: + return head['next'] + + curr = head + while curr['next']: + if curr['next']['name'] == name: + curr['next'] = curr['next']['next'] + break + curr = curr['next'] + return head + +def ll_list_all(head): + res = [] + curr = head + while curr: + res.append((curr['name'], curr['phone'])) + curr = curr['next'] + res.sort(key=lambda x: x[0]) + return res + +# ========================================================= +# 2. ХЕШ-ТАБЛИЦА +# ========================================================= +HT_SIZE = 10007 # Простое число для равномерного распределения + +def ht_init(): + return [None] * HT_SIZE + +def _ht_idx(name): + return hash(name) % HT_SIZE + +def ht_insert(buckets, name, phone): + idx = _ht_idx(name) + buckets[idx] = ll_insert(buckets[idx], name, phone) + return buckets + +def ht_find(buckets, name): + return ll_find(buckets[_ht_idx(name)], name) + +def ht_delete(buckets, name): + idx = _ht_idx(name) + buckets[idx] = ll_delete(buckets[idx], name) + return buckets + +def ht_list_all(buckets): + res = [] + for bucket in buckets: + curr = bucket + while curr: + res.append((curr['name'], curr['phone'])) + curr = curr['next'] + res.sort(key=lambda x: x[0]) + return res + +# ========================================================= +# 3. ДВОИЧНОЕ ДЕРЕВО ПОИСКА (BST) +# ========================================================= +def bst_insert(root, name, phone): + if root is None: + return {'name': name, 'phone': phone, 'left': None, 'right': None} + + if name < root['name']: + root['left'] = bst_insert(root['left'], name, phone) + elif name > root['name']: + root['right'] = bst_insert(root['right'], name, phone) + else: + root['phone'] = phone + return root + +def bst_find(root, name): + curr = root + while curr: + if name == curr['name']: + return curr['phone'] + elif name < curr['name']: + curr = curr['left'] + else: + curr = curr['right'] + return None + +def bst_delete(root, name): + if root is None: + return None + + if name < root['name']: + root['left'] = bst_delete(root['left'], name) + elif name > root['name']: + root['right'] = bst_delete(root['right'], name) + else: + # Узел найден + if root['left'] is None: + return root['right'] + if root['right'] is None: + return root['left'] + + # Два потомка: находим минимальный в правом поддереве + min_node = root['right'] + while min_node['left'] is not None: + min_node = min_node['left'] + + root['name'] = min_node['name'] + root['phone'] = min_node['phone'] + root['right'] = bst_delete(root['right'], min_node['name']) + return root + +def bst_list_all(root): + if root is None: + return [] + return bst_list_all(root['left']) + [(root['name'], root['phone'])] + bst_list_all(root['right']) + +# ========================================================= +# ЭКСПЕРИМЕНТАЛЬНАЯ ЧАСТЬ +# ========================================================= +def run_experiments(): + N = 10000 + RECORDS = [(f"User_{i:05d}", f"+7900{i:04d}{i%100:02d}") for i in range(N)] + + records_shuffled = RECORDS[:] + random.shuffle(records_shuffled) + + records_sorted = sorted(RECORDS, key=lambda x: x[0]) + + # Наборы для поиска и удаления + existing_names = [r[0] for r in random.sample(RECORDS, 100)] + non_existing_names = [f"None_{i}" for i in range(10)] + find_names = existing_names + non_existing_names + delete_names = [r[0] for r in random.sample(RECORDS, 50)] + + structures = { + "LinkedList": (lambda: None, ll_insert, ll_find, ll_delete), + "HashTable": (ht_init, ht_insert, ht_find, ht_delete), + "BST": (lambda: None, bst_insert, bst_find, bst_delete) + } + + modes = {"случайный": records_shuffled, "отсортированный": records_sorted} + results = [] + + print("Запуск экспериментов...") + trials = 5 + for struct_name, (init_f, ins_f, find_f, del_f) in structures.items(): + for mode_name, data in modes.items(): + print(f" {struct_name} | {mode_name}") + for t in range(1, trials + 1): + # Инициализация + ds = init_f() + + # A. Вставка + t0 = time.perf_counter() + for name, phone in data: + ds = ins_f(ds, name, phone) + t_ins = time.perf_counter() - t0 + + # B. Поиск + t0 = time.perf_counter() + for name in find_names: + find_f(ds, name) + t_find = time.perf_counter() - t0 + + # C. Удаление + t0 = time.perf_counter() + for name in delete_names: + ds = del_f(ds, name) + t_del = time.perf_counter() - t0 + + results.append([struct_name, mode_name, "вставка", t, t_ins]) + results.append([struct_name, mode_name, "поиск", t, t_find]) + results.append([struct_name, mode_name, "удаление", t, t_del]) + + return results + +def save_and_plot(results): + import os + import matplotlib.pyplot as plt + import pandas as pd + + os.makedirs("docs/data", exist_ok=True) + + # 1. Сохранение CSV (как было) + df = pd.DataFrame(results, columns=["Структура", "Режим", "Операция", "Повторение", "Время (сек)"]) + avg = df.groupby(["Структура", "Режим", "Операция"])["Время (сек)"].mean().reset_index() + avg["Повторение"] = "СРЕДНЕЕ" + df_full = pd.concat([df, avg], ignore_index=True) + df_full.to_csv("docs/data/results.csv", index=False, encoding="utf-8-sig") + + # 2. Улучшенный график: 3 отдельных подграфика + логарифмическая шкала + fig, axes = plt.subplots(1, 3, figsize=(18, 6)) + operations = ["вставка", "поиск", "удаление"] + structures_order = ["HashTable", "BST", "LinkedList"] # Фиксируем порядок для удобства чтения + colors = {"случайный": "#6C157F", "отсортированный": "#1E299F"} + + for ax, op in zip(axes, operations): + op_data = avg[avg["Операция"] == op] + pivot = op_data.pivot(index="Структура", columns="Режим", values="Время (сек)") + pivot = pivot.reindex(structures_order) # Ставим структуры в удобном порядке + + pivot.plot(kind="bar", ax=ax, color=[colors["случайный"], colors["отсортированный"]], width=0.75) + ax.set_title(f"Операция: {op.capitalize()}") + ax.set_ylabel("Время (сек)") + ax.set_xticklabels(ax.get_xticklabels(), rotation=0) + ax.grid(axis="y", alpha=0.3, linestyle="--") + + # ЛОГАРИФМИЧЕСКАЯ ШКАЛА: обязательна при разбросе от 0.0001 до 30 сек + ax.set_yscale("log") + ax.legend(title="Режим", loc="upper right") + + fig.suptitle("Сравнение производительности структур данных", fontsize=16, y=1.05) + plt.tight_layout() + plt.savefig("docs/data/plot.png", dpi=200, bbox_inches="tight") + +if __name__ == "__main__": + res = run_experiments() + save_and_plot(res) + print("Эксперимент завершен")