# Отчет по заданию 1: структуры данных

## Цель

Реализовать три структуры данных с нуля в процедурной парадигме и сравнить
скорость основных операций телефонного справочника:

- `insert(name, phone)` - добавить или обновить запись;
- `find(name)` - найти телефон по имени;
- `delete(name)` - удалить запись;
- `list_all()` - получить все записи, отсортированные по имени.

Классы не использовались. Узлы связного списка и дерева представлены
словарями, хеш-таблица представлена списком бакетов.

## Реализация

Код находится в файле `phonebook.py`.

Реализованы функции:

- связный список: `ll_insert`, `ll_find`, `ll_delete`, `ll_list_all`;
- хеш-таблица: `create_hash_table`, `ht_insert`, `ht_find`, `ht_delete`, `ht_list_all`;
- двоичное дерево поиска: `bst_insert`, `bst_find`, `bst_delete`, `bst_list_all`.

Для хеш-таблицы используется метод цепочек: каждый бакет хранит голову
связного списка. Хеш-функция написана вручную, чтобы результат не зависел от
рандомизации встроенной функции `hash()` в Python.

Для BST вставка, поиск, удаление и обход написаны без классов. Обход
`bst_list_all` реализован итеративно, чтобы отсортированный вход на 10000
элементов не приводил к переполнению стека рекурсии.

## Методика эксперимента

Скрипт эксперимента находится в файле `benchmark.py`.

Параметры запуска:

- количество записей: `N = 10000`;
- число повторов каждого эксперимента: `5`;
- имена: `User_00000`, `User_00001`, ..., `User_09999`;
- два режима входных данных: `shuffled` и `sorted`;
- поиск: 100 существующих имен и 10 отсутствующих;
- удаление: 50 случайных существующих имен;
- размер хеш-таблицы: `20011` бакетов.

После вставки структура не пересоздается: поиск и удаление выполняются на той
же заполненной структуре. Для каждого режима и каждой структуры создается новая
структура.

Файлы с результатами:

- `docs/data/results.csv` - все отдельные замеры;
- `docs/data/summary.csv` - среднее время и список всех пяти замеров;
- `docs/data/performance.svg` - столбчатая диаграмма средних значений.

![График производительности](data/performance.svg)

## Средние результаты

Время указано в секундах.

| Структура | Режим | Вставка | Поиск | Удаление |
|---|---:|---:|---:|---:|
| LinkedList | shuffled | 1.555942 | 0.014180 | 0.006345 |
| LinkedList | sorted | 1.513740 | 0.012993 | 0.006041 |
| HashTable | shuffled | 0.005934 | 0.000056 | 0.000031 |
| HashTable | sorted | 0.006140 | 0.000056 | 0.000029 |
| BST | shuffled | 0.011223 | 0.000093 | 0.000069 |
| BST | sorted | 2.250442 | 0.019857 | 0.010803 |

## Анализ

Связный список оказался самым медленным на вставке и поиске. Причина в том, что
для корректной операции `insert` нужно проверить, есть ли уже запись с таким
именем. При уникальных именах почти каждая вставка проходит по всему текущему
списку, поэтому суммарная сложность вставки всех записей становится `O(n^2)`.
Порядок входных данных почти не влияет на результат, потому что структура не
использует порядок ключей.

Хеш-таблица показала лучшие результаты почти во всех операциях. При хорошем
распределении по бакетам вставка, поиск и удаление близки к `O(1)`. Порядок
входных данных почти не влияет на время, так как индекс бакета определяется
хешем имени, а не расположением записи во входном списке.

BST хорошо работает на перемешанных данных: дерево получается сравнительно
сбалансированным, поэтому операции близки к `O(log n)`. На отсортированном
входе обычное двоичное дерево поиска вырождается в цепочку: каждый новый ключ
становится правым потомком предыдущего. Из-за этого вставка всех записей
становится `O(n^2)`, а поиск и удаление приближаются к поведению связного
списка.

Удаление у хеш-таблицы быстрое по той же причине, что и поиск: сначала
вычисляется бакет, затем просматривается короткая цепочка. В BST удаление
быстрое на перемешанном дереве, но на вырожденном дереве оно замедляется.
В связном списке удаление требует линейного поиска удаляемого элемента.

## Вывод

Для частого поиска, обновления и удаления по точному имени лучше выбирать
хеш-таблицу. Она быстрее всего в эксперименте и почти не зависит от порядка
вставки.

Если нужно часто получать данные в отсортированном порядке, дерево поиска дает
удобный `in-order` обход без отдельной сортировки. Но обычный BST чувствителен
к порядку входных данных, поэтому на практике лучше использовать
самобалансирующееся дерево или готовую структуру из библиотеки.

Связный список подходит только для маленьких наборов данных или учебных задач.
Для телефонного справочника с частым поиском он неудачен, потому что каждая
операция поиска требует последовательного прохода по элементам.
