diff --git a/nikolaevda/docs/data/experiment_results.csv b/nikolaevda/docs/data/experiment_results.csv new file mode 100644 index 0000000..70872e1 --- /dev/null +++ b/nikolaevda/docs/data/experiment_results.csv @@ -0,0 +1,19 @@ +Структура;Режим;Операция;Замер1(с);Замер2(с);Замер3(с);Замер4(с);Замер5(с);Среднее(с) +linked_list;случайный;вставка;4.743085900001461;4.702243700005056;4.426778699998977;4.3052682999987155;4.301903599996876;4.495856040000217 +linked_list;случайный;поиск;0.040070499999274034;0.03833870000380557;0.038309099996695295;0.038068900001235306;0.037999300002411474;0.03855730000068434 +linked_list;случайный;удаление;0.03337140000076033;0.03520829999615671;0.03318629999557743;0.03670069999498082;0.03511889999936102;0.03471711999736726 +hash_table;случайный;вставка;0.054787300003226846;0.038778399997681845;0.038185400000656955;0.03906660000211559;0.040834699997503776;0.042330480000237 +hash_table;случайный;поиск;0.00048270000115735456;0.0003393000006326474;0.00034130000130971894;0.0003389000048628077;0.0003389000048628077;0.00036822000256506724 +hash_table;случайный;удаление;0.00018000000272877514;0.0001720000000204891;0.0001720000000204891;0.0001764999979059212;0.0001747999995131977;0.00017506000003777444 +bst;случайный;вставка;0.04329969999525929;0.04011429999809479;0.0377946999942651;0.03973660000337986;0.03843010000127833;0.03987507999845548 +bst;случайный;поиск;0.0005353999949875288;0.0004243000003043562;0.00040499999886378646;0.00041709999641170725;0.00041870000131893903;0.00044009999837726357 +bst;случайный;удаление;0.08770270000240998;0.08755029999883845;0.09487290000106441;0.08564219999971101;0.08784590000141179;0.08872280000068714 +linked_list;отсортированный;вставка;5.82706280000275;5.942067100004351;6.058909300001687;5.410613899999589;5.423316100001102;5.732393840001896 +linked_list;отсортированный;поиск;0.05126659999950789;0.04912999999942258;0.04894649999914691;0.048823999997694045;0.0484264999977313;0.04931871999870054 +linked_list;отсортированный;удаление;0.03424879999511177;0.03367250000155764;0.03369569999631494;0.03390580000268528;0.034035600001516286;0.033911679999437186 +hash_table;отсортированный;вставка;0.03484389999357518;0.03386820000014268;0.033041399998182897;0.03465739999955986;0.035284899997350294;0.03433915999776218 +hash_table;отсортированный;поиск;0.0005273999995552003;0.00044519999937620014;0.00037960000190651044;0.000374099996406585;0.0003724999987753108;0.00041975999920396134 +hash_table;отсортированный;удаление;0.00017210000078193843;0.00017210000078193843;0.00017300000035902485;0.00017389999993611127;0.0001722999950288795;0.00017267999937757849 +bst;отсортированный;вставка;17.43330440000136;17.245424400003685;17.230704699999478;17.4216249999954;17.25258659999963;17.31672901999991 +bst;отсортированный;поиск;0.15691709999373415;0.15601930000411812;0.15765989999636076;0.15630209999653744;0.1590829000051599;0.15719625999918208 +bst;отсортированный;удаление;0.08944690000498667;0.086433999997098;0.08745249999628868;0.08608390000154031;0.09040470000036294;0.08796440000005532 diff --git a/nikolaevda/docs/data/graphs.png b/nikolaevda/docs/data/graphs.png new file mode 100644 index 0000000..34a065f Binary files /dev/null and b/nikolaevda/docs/data/graphs.png differ diff --git a/nikolaevda/docs/report_laba1.ipynb b/nikolaevda/docs/report_laba1.ipynb new file mode 100644 index 0000000..17be36c --- /dev/null +++ b/nikolaevda/docs/report_laba1.ipynb @@ -0,0 +1,245 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "7cbca316", + "metadata": {}, + "outputs": [], + "source": [ + "{\n", + " \"cells\": [\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"id\": \"start\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"# Отчёт по лабораторной работе\\n\",\n", + " \"## Тема: Сравнение производительности структур данных для телефонного справочника\\n\",\n", + " \"\\n\",\n", + " \"---\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"id\": \"goal\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## 1. Цель работы\\n\",\n", + " \"\\n\",\n", + " \"Реализовать три различные структуры данных «с нуля», применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций (вставка, поиск, удаление).\\n\",\n", + " \"\\n\",\n", + " \"---\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"id\": \"conditions\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## 2. Условия эксперимента\\n\",\n", + " \"\\n\",\n", + " \"| Параметр | Значение |\\n\",\n", + " \"|----------|----------|\\n\",\n", + " \"| Общее число записей | 10 000 |\\n\",\n", + " \"| Каждый замер повторялся | 5 раз |\\n\",\n", + " \"| Количество существующих записей для поиска | 100 |\\n\",\n", + " \"| Количество несуществующих записей для поиска | 10 |\\n\",\n", + " \"| Количество элементов для удаления | 50 |\\n\",\n", + " \"| Размер хеш-таблицы | 2003 (простое число) |\\n\",\n", + " \"| Режимы тестирования | Случайный / Отсортированный |\\n\",\n", + " \"\\n\",\n", + " \"---\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"id\": \"graphs\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## 3. Практические графики\\n\",\n", + " \"\\n\",\n", + " \"### Информация о тестировании\\n\",\n", + " \"- Общее число записей: 10 000\\n\",\n", + " \"- Каждый замер повторялся: 5 раз\\n\",\n", + " \"- Количество существующих записей для случайного поиска: 100\\n\",\n", + " \"- Количество несуществующих записей для поиска: 10\\n\",\n", + " \"- Количество элементов для удаления: 50\\n\",\n", + " \"\\n\",\n", + " \"![График вставки](graphs.png)\\n\",\n", + " \"\\n\",\n", + " \"**Рис. 1 – Тестирование вставки (логарифмическая шкала)**\\n\",\n", + " \"\\n\",\n", + " \"---\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"id\": \"analysis_bst\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## 4. Анализ результатов\\n\",\n", + " \"\\n\",\n", + " \"### Как порядок входных данных влияет на скорость вставки в BST (деградация до O(n) на отсортированных данных)?\\n\",\n", + " \"\\n\",\n", + " \"По определению, при вставке отсортированных данных, структура бинарного дерева поиска вырождается в связный список.\\n\",\n", + " \"\\n\",\n", + " \"**Результаты тестирования:**\\n\",\n", + " \"- На случайных данных: время вставки ~0.037 секунд\\n\",\n", + " \"- На отсортированных данных: время вставки ~18.34 секунд (деградация в ~470 раз!)\\n\",\n", + " \"\\n\",\n", + " \"Заметим, что при случайных данных скорость вставки в бинарное дерево почти лишь немного уступает по скорости хеш-таблице. При отсортированных данных дерево фактически превращается в связный список, и из-за рекурсивной реализации вставки бинарное дерево становится даже медленнее связного списка.\\n\",\n", + " \"\\n\",\n", + " \"---\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"id\": \"analysis_hash\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"### Почему хеш-таблица почти не чувствительна к порядку?\\n\",\n", + " \"\\n\",\n", + " \"Хеш-таблица не чувствительна к порядку данных, так как:\\n\",\n", + " \"1. Использует для распределения элементов хеш-значения данных (сложность операции одинакова для любых однотипных данных)\\n\",\n", + " \"2. Хеш-функция равномерно распределяет ключи по корзинам независимо от их порядка\\n\",\n", + " \"3. Вставка в конкретную корзину не зависит от соседних элементов\\n\",\n", + " \"\\n\",\n", + " \"**Экспериментальное подтверждение:**\\n\",\n", + " \"- Случайный порядок: вставка = 0.0369 сек, поиск = 0.000355 сек\\n\",\n", + " \"- Отсортированный порядок: вставка = 0.0356 сек, поиск = 0.000380 сек\\n\",\n", + " \"\\n\",\n", + " \"Разница незначительна и находится в пределах погрешности измерений.\\n\",\n", + " \"\\n\",\n", + " \"---\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"id\": \"analysis_list\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"### Почему связный список всегда медленен при поиске?\\n\",\n", + " \"\\n\",\n", + " \"Операция поиска в связном списке имеет линейную сложность **O(n)** независимо от порядка данных, так как:\\n\",\n", + " \"- Нет индексов для прямого доступа\\n\",\n", + " \"- Нет сортировки, позволяющей применять бинарный поиск\\n\",\n", + " \"- Приходится последовательно перебирать все элементы до найденного\\n\",\n", + " \"\\n\",\n", + " \"| Структура | Сложность поиска | Время поиска (случайный) |\\n\",\n", + " \"|-----------|-----------------|--------------------------|\\n\",\n", + " \"| Связный список | O(n) | 0.0427 сек |\\n\",\n", + " \"| Хеш-таблица | O(1) средняя | 0.000355 сек |\\n\",\n", + " \"| BST (случайный) | O(log n) | 0.000527 сек |\\n\",\n", + " \"\\n\",\n", + " \"---\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"id\": \"analysis_delete\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"### Как удаление работает в каждой структуре?\\n\",\n", + " \"\\n\",\n", + " \"#### Связный список\\n\",\n", + " \"Находим элемент перед удаляемым элементом и заменяем его поле `next` на `next.next`:\\n\",\n", + " \"```python\\n\",\n", + " \"current = head\\n\",\n", + " \"while current['next'] is not None:\\n\",\n", + " \" if current['next']['name'] == name:\\n\",\n", + " \" current['next'] = current['next']['next']\\n\",\n", + " \" return head\\n\",\n", + " \" current = current['next']\\n\",\n", + " \"```\\n\",\n", + " \"\\n\",\n", + " \"#### Двоичное дерево поиска\\n\",\n", + " \"После того, как мы нашли узел, который необходимо удалить, возможны три случая:\\n\",\n", + " \"\\n\",\n", + " \"**Случай 1:** У удаляемого узла нет детей → просто удаляем узел.\\n\",\n", + " \"\\n\",\n", + " \"**Случай 2:** У удаляемого узла есть только один ребёнок → ребёнок занимает место удалённого узла.\\n\",\n", + " \"\\n\",\n", + " \"**Случай 3:** У удаляемого узла есть оба ребёнка → находим минимальный элемент в правом поддереве (самый левый узел) и заменяем им удаляемый узел.\\n\",\n", + " \"\\n\",\n", + " \"#### Хеш-таблица\\n\",\n", + " \"1. Вычисляем хеш-индекс: `index = hash_func(name, len(buckets))`\\n\",\n", + " \"2. Находим нужную корзину: `buckets[index]`\\n\",\n", + " \"3. Удаляем элемент из связного списка в этой корзине\\n\",\n", + " \"\\n\",\n", + " \"**Сравнение времени удаления:**\\n\",\n", + " \"\\n\",\n", + " \"| Структура | Время удаления (случайный) | Сложность |\\n\",\n", + " \"|-----------|---------------------------|-----------|\\n\",\n", + " \"| Связный список | 0.0341 сек | O(n) |\\n\",\n", + " \"| Хеш-таблица | 0.00018 сек | O(1) средняя |\\n\",\n", + " \"| BST | 0.0793 сек | O(log n) средняя |\\n\",\n", + " \"\\n\",\n", + " \"---\"\n", + " ]\n", + " },\n", + " {\n", + " \"cell_type\": \"markdown\",\n", + " \"id\": \"conclusion\",\n", + " \"metadata\": {},\n", + " \"source\": [\n", + " \"## 5. Вывод\\n\",\n", + " \"\\n\",\n", + " \"Мы реализовали и протестировали три различные структуры хранения данных: связный список, хеш-таблицу и двоичное дерево поиска. Сравнили скорость операций вставки, удаления и поиска для каждой структуры.\\n\",\n", + " \"\\n\",\n", + " \"### Итоговая таблица производительности (случайный порядок):\\n\",\n", + " \"\\n\",\n", + " \"| Структура | Вставка (сек) | Поиск (сек) | Удаление (сек) |\\n\",\n", + " \"|-----------|---------------|-------------|----------------|\\n\",\n", + " \"| Связный список | 4.6031 | 0.0427 | 0.0341 |\\n\",\n", + " \"| Хеш-таблица | **0.0369** | **0.00036** | **0.00018** |\\n\",\n", + " \"| BST | 0.0369 | 0.00053 | 0.0793 |\\n\",\n", + " \"\\n\",\n", + " \"### Рекомендации по выбору структуры данных:\\n\",\n", + " \"\\n\",\n", + " \"1. **Хеш-таблица** – лучший выбор для телефонного справочника:\\n\",\n", + " \" - Не важен порядок хранения и извлечения данных\\n\",\n", + " \" - Требуется максимальная скорость поиска и вставки\\n\",\n", + " \" - Результат: **победитель по всем параметрам**\\n\",\n", + " \"\\n\",\n", + " \"2. **Двоичное дерево поиска** – выбираем, если:\\n\",\n", + " \" - Нужно хранить данные с возможностью быстрого отсортированного обхода\\n\",\n", + " \" - Данные поступают в случайном порядке (иначе будет деградация)\\n\",\n", + " \" - Можно использовать сбалансированную версию (AVL, красно-чёрное)\\n\",\n", + " \"\\n\",\n", + " \"3. **Связный список** – выбираем, если:\\n\",\n", + " \" - Нужно хранить данные в порядке поступления (очередь, стек)\\n\",\n", + " \" - Объём данных очень маленький (< 100 записей)\\n\",\n", + " \" - Простота реализации важнее производительности\\n\",\n", + " \"\\n\",\n", + " \"---\\n\",\n", + " \"\\n\",\n", + " \"**Заключение:** Для реализации телефонного справочника оптимальнее всего использовать **хеш-таблицу**, так как она обеспечивает наилучшую производительность для всех операций и не чувствительна к порядку входных данных.\"\n", + " ]\n", + " }\n", + " ],\n", + " \"metadata\": {\n", + " \"kernelspec\": {\n", + " \"display_name\": \"Python 3\",\n", + " \"language\": \"python\",\n", + " \"name\": \"python3\"\n", + " },\n", + " \"language_info\": {\n", + " \"name\": \"python\",\n", + " \"version\": \"3.14.0\"\n", + " }\n", + " },\n", + " \"nbformat\": 4,\n", + " \"nbformat_minor\": 5\n", + "}" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/nikolaevda/task1/Zadanie1.py b/nikolaevda/task1/Zadanie1.py new file mode 100644 index 0000000..4558814 --- /dev/null +++ b/nikolaevda/task1/Zadanie1.py @@ -0,0 +1,649 @@ +import random +import time +import csv +import os +import traceback +import sys + +sys.setrecursionlimit(30000) + +def ll_insert(head, name, phone): + + """ + проходит до конца (или сразу добавляет в конец) + возвращает новую голову (если вставка в начало) или изменяет список по ссылке. + """ + new_node = {'name': name, 'phone': phone, 'next': None} + + # в случае пустого списка, новый узел становится головой + if head is None: + return new_node + + # в противном случае проходим в конец списка + current = head + while current['next'] is not None: + if current['name'] == name: + current['phone'] = phone + return head + current = current['next'] + + # проверяем последний узел + if current['name'] == name: + current['phone'] = phone + else: + current['next'] = new_node + + return head + + + +def ll_find(head, name): + + """ + ищет узел по имени. + возвращает телефон или None + """ + current = head + + while current is not None: + if current['name'] == name: + return current['phone'] + current = current['next'] + + return None + + +def ll_delete(head, name): + + """ + удаляет узел по имени. + возвращает новую голову. + """ + # если список пуст + if head is None: + return None + + # если удаляем голову + if head['name'] == name: + new_head = head['next'] + head['next'] = None + return new_head + + # ищем узел для удаления + current = head + while current['next'] is not None: + if current['next']['name'] == name: + target = current['next'] + current['next'] = target['next'] + target['next'] = None # разрываем связь у удаляемого узла (иными словами, обнуление ссылки) + return head + current = current['next'] + + return head + +def ll_collect(head, result_list): + """собирает все данные из связного списка в result_list""" + current = head + while current is not None: + result_list.append((current['name'], current['phone'])) + current = current['next'] + + +def ll_list_all(head): + + """ + собирает все записи в список и сортирует + """ + result = [] + current = head + + while current is not None: + result.append((current['name'], current['phone'])) + current = current['next'] + + # ручная сортировка пузырьком + n = len(result) + for i in range(n): + for j in range(0, n - i - 1): + if result[j][0] > result[j + 1][0]: + result[j], result[j + 1] = result[j + 1], result[j] + + return result + + +def hash_table(size): + """создание хеш-таблицы""" + return [None] * size + + +def hash_func(name, buckets_count): + """ + использует умножение на простое число для лучшего распределения + """ + h = 0 + multiplier = 1 + for char in name: + h = (h + ord(char) * multiplier) % buckets_count + multiplier = (multiplier * 31) % buckets_count + return h + + +def ht_insert(buckets, name, phone): + """добавить или обновить запись""" + if buckets is None: + return + + index = hash_func(name, len(buckets)) + buckets[index] = ll_insert(buckets[index], name, phone) + + +def ht_find(buckets, name): + """найти телефон по имени""" + idx = hash_func(name, len(buckets)) + return ll_find(buckets[idx], name) + + +def ht_delete(buckets, name): + """ + удалить запись + """ + idx = hash_func(name, len(buckets)) + buckets[idx] = ll_delete(buckets[idx], name) + + +def bubble_sort(records): + """пузырьковая сортировка""" + n = len(records) + for i in range(n - 1): + swapped = False + for j in range(n - 1 - i): + if records[j][0] > records[j + 1][0]: + records[j], records[j + 1] = records[j + 1], records[j] + swapped = True + if not swapped: + break + return records + + +def ht_list_all(buckets): + """ + собрание всех записей и сортировка + """ + # Собираем все записи + full_data = [] + for head in buckets: + ll_collect(head, full_data) + + # Сортируем пузырьком + bubble_sort(full_data) + return full_data + +#Hash_table1 = hash_table(3) + +#ht_insert(Hash_table1, 'Alena', '010') +#ht_insert(Hash_table1, 'Helena', '111') +#ht_insert(Hash_table1, 'Gena', '222') + + +#print(ht_list_all(Hash_table1)) + +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): + """ + рекурсивный поиск телефона по имени + возвращает phone или None + """ + # не нашли + if root is None: + return None + + # нашли + if root['name'] == name: + return root['phone'] + + # ищем в левом поддереве + elif name < root['name']: + return bst_find(root['left'], name) + + # ищем в правом поддереве + else: + return bst_find(root['right'], name) + + +def bst_find_min(node): + """вспомогательная функция: поиск узла с минимальным ключом""" + current = node + while current['left'] is not None: + current = current['left'] + return current + + +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 and root['right'] is None: + return None + + + elif root['left'] is None: + return root['right'] + elif root['right'] is None: + return root['left'] + + + # находим минимальный элемент в правом поддереве + successor = bst_find_min(root['right']) + + # копируем данные из преемника в текущий узел + root['name'] = successor['name'] + root['phone'] = successor['phone'] + + # удаляем преемника + root['right'] = bst_delete(root['right'], successor['name']) + + return root + + +def bst_list_all(root, result=None): + """ + центрированный (in-order) обход дерева + рекурсивно собирает записи в отсортированном порядке + """ + if result is None: + result = [] + + if root is not None: + # сначала обходим левое поддерево (все меньшие ключи) + bst_list_all(root['left'], result) + + # затем текущий узел + result.append((root['name'], root['phone'])) + + # затем правое поддерево (все большие ключи) + bst_list_all(root['right'], result) + + return result + +def generate_records(count=10000): + """ + генерация тестовых данных + 70% уникальных имён, 30% повторяющихся (для коллизий) + """ + records = [] + base_names = ["Алексей", "Борис", "Владимир", "Дмитрий", "Елена", + "Иван", "Мария", "Николай", "Ольга", "Павел"] + + for i in range(count): + if random.random() < 0.7: + name = f"User_{i:05d}" + else: + name = random.choice(base_names) + f"_{random.randint(1, 100)}" + + phone = f"+7-{random.randint(100,999)}-{random.randint(100,999)}-{random.randint(1000,9999)}" + records.append((name, phone)) + + shuffled = records.copy() + random.shuffle(shuffled) + sorted_records = sorted(records, key=lambda x: x[0]) + + return shuffled, sorted_records + + +def measure_insertion(structure_name, records): + """ + замер времени вставки + возвращает список замеров и заполненную структуру + """ + times = [] + filled_structure = None + + for run in range(5): + if structure_name == "linked_list": + structure = None + elif structure_name == "hash_table": + structure = hash_table(2003) + elif structure_name == "bst": + structure = None + + start = time.perf_counter() + + for name, phone in records: + if structure_name == "linked_list": + structure = ll_insert(structure, name, phone) + elif structure_name == "hash_table": + ht_insert(structure, name, phone) + elif structure_name == "bst": + structure = bst_insert(structure, name, phone) + + end = time.perf_counter() + times.append(end - start) + + if run == 4: # Сохраняем после последнего замера + filled_structure = structure + + return times, filled_structure + + +def measure_search(structure_name, structure, search_names): + """ + замер времени поиска + возвращает список замеров + """ + times = [] + + for run in range(5): + start = time.perf_counter() + + for name in search_names: + if structure_name == "linked_list": + ll_find(structure, name) + elif structure_name == "hash_table": + ht_find(structure, name) + elif structure_name == "bst": + bst_find(structure, name) + + end = time.perf_counter() + times.append(end - start) + + return times + + +def measure_deletion(structure_name, original_structure, delete_names): + """ + замер времени удаления + возвращает список замеров + """ + times = [] + + for run in range(5): + # создаём копию структуры + if structure_name == "linked_list": + all_records = ll_list_all(original_structure) + test_structure = None + for name, phone in all_records: + test_structure = ll_insert(test_structure, name, phone) + + elif structure_name == "hash_table": + all_records = ht_list_all(original_structure) + test_structure = hash_table(2003) + for name, phone in all_records: + ht_insert(test_structure, name, phone) + + elif structure_name == "bst": + all_records = bst_list_all(original_structure) + test_structure = None + for name, phone in all_records: + test_structure = bst_insert(test_structure, name, phone) + + start = time.perf_counter() + + for name in delete_names: + if structure_name == "linked_list": + test_structure = ll_delete(test_structure, name) + elif structure_name == "hash_table": + ht_delete(test_structure, name) + elif structure_name == "bst": + test_structure = bst_delete(test_structure, name) + + end = time.perf_counter() + times.append(end - start) + + return times + +print(f"Текущая рабочая директория: {os.getcwd()}") +print(f"Путь к файлу: {os.path.abspath(__file__)}") + +def run_experiment(): + """ + запуск всех экспериментов и сохранение результатов + """ + + current_dir = os.path.dirname(__file__) + docs_dir = os.path.dirname(current_dir) + csv_file = os.path.join(docs_dir, "experiment_results.csv") + + + + os.makedirs(docs_dir, exist_ok=True) + + print("ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ СТРУКТУР ДАННЫХ") + print("Телефонный справочник - 10000 записей") + print(f"\nРезультаты будут сохранены в: {csv_file}") + + # генерация данных + print("\n1. Генерация тестовых данных...") + shuffled_records, sorted_records = generate_records(10000) + print(f"Сгенерировано 10000 записей") + print(f"Уникальных имён: {len(set([r[0] for r in shuffled_records]))}") + + # подготовка имён для поиска и удаления + random.seed(42) + existing_names = [shuffled_records[i][0] for i in random.sample(range(10000), 100)] + nonexisting_names = [f"NotExist_{i}" for i in range(10)] + search_names = existing_names + nonexisting_names + delete_names = [shuffled_records[i][0] for i in random.sample(range(10000), 50)] + + results = [["Структура", "Режим", "Операция", + "Замер1(с)", "Замер2(с)", "Замер3(с)", "Замер4(с)", "Замер5(с)", + "Среднее(с)"]] + + # тестирование для каждого режима + for mode_name, records in [("случайный", shuffled_records), + ("отсортированный", sorted_records)]: + + print(f"\n2. Тестирование режима: {mode_name}") + + + for struct_name in ["linked_list", "hash_table", "bst"]: + print(f"\n {struct_name.upper()}:") + + # вставка + print("Вставка 10000 записей...") + insert_times, filled_struct = measure_insertion(struct_name, records) + avg_insert = sum(insert_times) / 5 + print(f"Время: {avg_insert:.4f} сек (среднее)") + + # поиск + print("Поиск 110 записей (100 существующих + 10 которых нет)...") + search_times = measure_search(struct_name, filled_struct, search_names) + avg_search = sum(search_times) / 5 + print(f"Время: {avg_search:.4f} сек (среднее)") + + # удаление + print("Удаление 50 случайных записей...") + delete_times = measure_deletion(struct_name, filled_struct, delete_names) + avg_delete = sum(delete_times) / 5 + print(f"Время: {avg_delete:.4f} сек (среднее)") + + # сохраняем результаты + results.append([struct_name, mode_name, "вставка"] + insert_times + [avg_insert]) + results.append([struct_name, mode_name, "поиск"] + search_times + [avg_search]) + results.append([struct_name, mode_name, "удаление"] + delete_times + [avg_delete]) + + # сохранение CSV + print("\n3. Сохранение результатов...") + try: + with open(csv_file, "w", newline="", encoding="utf-8-sig") as f: + writer = csv.writer(f, delimiter=';') + writer.writerows(results) + print(f"Результаты сохранены в: {csv_file}") + except Exception as e: + print(f"Ошибка сохранения: {e}") + + # вывод табл. + print("СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ") + print(f"{'Структура':<15} {'Режим':<12} {'Операция':<10} {'Среднее время (сек)':<20}") + + for row in results[1:]: + struct, mode, op, t1, t2, t3, t4, t5, avg = row + print(f"{struct:<15} {mode:<12} {op:<10} {avg:<20.6f}") + + return results + +def create_report_table(results): + """Создание сводной таблицы""" + + print("СВОДНАЯ ТАБЛИЦА (среднее время в секундах)") + + print(f"{'Структура':<12} {'Режим':<12} {'Вставка':<12} {'Поиск':<12} {'Удаление':<12}") + + summary = {} + for row in results[1:]: + struct, mode, op, _, _, _, _, _, avg = row + key = (struct, mode) + if key not in summary: + summary[key] = {} + summary[key][op] = avg + + names = {'linked_list': 'LinkedList', 'hash_table': 'HashTable', 'bst': 'BST'} + for (struct, mode), ops in summary.items(): + print(f"{names[struct]:<12} {mode:<12} {ops.get('вставка', 0):<12.6f} {ops.get('поиск', 0):<12.6f} {ops.get('удаление', 0):<12.6f}") + + +def print_analysis(): + """вывод краткого анализа""" + + print("АНАЛИЗ РЕЗУЛЬТАТОВ") + + print(""" +1. Влияние порядка данных на BST: + - На случайных данных: быстро O(log n) + - На отсортированных: деградация до O(n) (дерево вырождается в список) + +2. Хеш-таблица не чувствительна к порядку: + - Хеш-функция случайно распределяет данные по bucket'ам + - Порядок вставки не влияет на время операций + +3. Связный список всегда медленен при поиске: + - Поиск требует последовательного прохода O(n) + - Нет индексов или сортировки для ускорения + +4. Сравнение удаления: + - Связный список: O(n) — нужен поиск элемента + - Хеш-таблица: O(1) — прямой доступ по индексу + - BST: O(log n) в среднем, O(n) на отсортированных + +5. Рекомендация для реальных задач: + - Хеш-таблица: частый поиск, словари, кэши + - BST (сбалансированный): нужны отсортированные данные + - Связный список: маленькие объёмы, очереди/стеки + - Для телефонного справочника ЛУЧШЕ: ХЕШ-ТАБЛИЦА +""") + +def create_graphs(results): + """Построение столбчатых диаграмм""" + import matplotlib.pyplot as plt + import numpy as np + + data = {} + for row in results[1:]: + struct = row[0] + mode = row[1] + op = row[2] + avg = row[8] + + if struct not in data: + data[struct] = {} + if mode not in data[struct]: + data[struct][mode] = {} + data[struct][mode][op] = avg + + # Настройки + struct_names = {'linked_list': 'LinkedList', 'hash_table': 'HashTable', 'bst': 'BST'} + colors = {'linked_list': '#3498db', 'hash_table': '#2ecc71', 'bst': '#e74c3c'} + modes = ['случайный', 'отсортированный'] + operations = ['вставка', 'поиск', 'удаление'] + op_titles = ['Вставка (10000 записей)', 'Поиск (110 запросов)', 'Удаление (50 записей)'] + + fig, axes = plt.subplots(1, 3, figsize=(14, 5)) + fig.suptitle('Сравнение производительности структур данных', fontsize=14, fontweight='bold') + + for idx, (op, title) in enumerate(zip(operations, op_titles)): + ax = axes[idx] + x = np.arange(len(modes)) + width = 0.25 + multiplier = 0 + + for struct in ['linked_list', 'hash_table', 'bst']: + values = [data[struct][mode][op] for mode in modes] + bars = ax.bar(x + multiplier * width, values, width, + label=struct_names[struct], color=colors[struct]) + + for bar, val in zip(bars, values): + if val < 0.001: + ax.text(bar.get_x() + bar.get_width()/2, bar.get_height(), + f'{val:.6f}', ha='center', va='bottom', fontsize=7) + elif val < 0.01: + ax.text(bar.get_x() + bar.get_width()/2, bar.get_height(), + f'{val:.5f}', ha='center', va='bottom', fontsize=7) + else: + ax.text(bar.get_x() + bar.get_width()/2, bar.get_height(), + f'{val:.3f}', ha='center', va='bottom', fontsize=8) + multiplier += 1 + + ax.set_title(title) + ax.set_ylabel('Время (сек)') + ax.set_yscale('log') + ax.set_ylim(1e-5, 10) + ax.set_xticks(x + width) + ax.set_xticklabels(['Случайный', 'Отсортированный']) + ax.legend() + ax.grid(True, alpha=0.3, axis='y') + + plt.tight_layout() + + current_dir = os.path.dirname(__file__) + docs_dir = os.path.dirname(current_dir) + path = os.path.join(docs_dir, 'graphs.png') + plt.savefig(path, dpi=150) + plt.close() + print(f"\nГрафики сохранены: {path}") + return path + +if __name__ == "__main__": + results = run_experiment() + create_report_table(results) + create_graphs(results) + print_analysis() + + print("ЭКСПЕРИМЕНТ ВЫПОЛНЕН ПОЛНОСТЬЮ!")