forked from UNN/2026-rff_mp
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 чувствителен
|
|||
|
|
к порядку входных данных, поэтому на практике лучше использовать
|
|||
|
|
самобалансирующееся дерево или готовую структуру из библиотеки.
|
|||
|
|
|
|||
|
|
Связный список подходит только для маленьких наборов данных или учебных задач.
|
|||
|
|
Для телефонного справочника с частым поиском он неудачен, потому что каждая
|
|||
|
|
операция поиска требует последовательного прохода по элементам.
|