113 lines
7.4 KiB
Markdown
113 lines
7.4 KiB
Markdown
# Отчет по заданию 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` - столбчатая диаграмма средних значений.
|
||
|
||

|
||
|
||
## Средние результаты
|
||
|
||
Время указано в секундах.
|
||
|
||
| Структура | Режим | Вставка | Поиск | Удаление |
|
||
|---|---:|---:|---:|---:|
|
||
| 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 чувствителен
|
||
к порядку входных данных, поэтому на практике лучше использовать
|
||
самобалансирующееся дерево или готовую структуру из библиотеки.
|
||
|
||
Связный список подходит только для маленьких наборов данных или учебных задач.
|
||
Для телефонного справочника с частым поиском он неудачен, потому что каждая
|
||
операция поиска требует последовательного прохода по элементам.
|