forked from UNN/2026-rff_mp
123 lines
8.5 KiB
Markdown
123 lines
8.5 KiB
Markdown
|
|
## Практические графики
|
|||
|
|
### Информация о тестировании
|
|||
|
|
- Общее число записей: 20000
|
|||
|
|
- Каждый замер повторялся: 20 раз
|
|||
|
|
- Количество существующих записей для случайного поиска: 1000
|
|||
|
|
- Количество несуществующих записей для поиска: 500
|
|||
|
|
- Количество элементов для удаления: 1000
|
|||
|
|
|
|||
|
|
![[insert.pdf]]
|
|||
|
|
**Тестирование вставки (рис. 1)**
|
|||
|
|
|
|||
|
|
![[search.pdf]]
|
|||
|
|
**Тестирование поиска (рис. 2)**
|
|||
|
|
|
|||
|
|
![[delete.pdf]]
|
|||
|
|
**Тестирование удаления (рис. 3)**
|
|||
|
|
## Анализ результатов
|
|||
|
|
|
|||
|
|
### Как порядок входных данных влияет на скорость вставки в BST (деградация до O(n) на отсортированных данных)?
|
|||
|
|
|
|||
|
|
По определению, при вставке отсортированных данных, структура бинарного дерева поиска вырождается в связный список.
|
|||
|
|
Для визуализации этого в тесте выводятся высота и количество элементов в дереве:
|
|||
|
|
Для случайных данных вывод выглядит примерно так:
|
|||
|
|
```
|
|||
|
|
Высота дерева: 28, элементов: 8634
|
|||
|
|
```
|
|||
|
|
Для сортированных данных же:
|
|||
|
|
```
|
|||
|
|
Высота дерева: 8634, элементов: 8634
|
|||
|
|
```
|
|||
|
|
Заметим, что при случайных данных скорость вставки в бинарное дерево почти лишь немного уступает по скорости хеш-таблице. При сортированных данных из-за рекурсивной реализации вставки бинарное дерево проигрывает связному списку(который имеет линейную сложность вставки)
|
|||
|
|
|
|||
|
|
### Почему хеш-таблица почти не чувствительна к порядку.
|
|||
|
|
Хеш-таблица не чувствительна к порядку данных, так как использует для распределения элементов хеш значения данных (сложность операции одинакова для любых однотипных данных) и после производит вставку в связный список(в моей реализации проходит по списку и вставляет данные в конец). Поэтому хеш-таблица ни на одном из этапов не сравнивает данные, следовательно их порядок не влияет на скорость.
|
|||
|
|
|
|||
|
|
### Почему связный список всегда медленен при поиске.
|
|||
|
|
Операция поиска в связном списке имеет линейную сложность $O(n)$ не зависимо от порядка данных, что можно видеть на графике (см. рис. 2). Для бинарного дерева поиска эта сложность в лучшем случае $O(\log(N))$, а в худшем $O(N)$. Для хеш-таблицы сложность вставки $O(1)$, с хорошей хеш-функцией и низким заполнением.
|
|||
|
|
|
|||
|
|
### Как удаление работает в каждой структуре.
|
|||
|
|
#### Связный список
|
|||
|
|
Находим элемент перед удаляем элементом, и заменяем его поле `next` на `next.next`, то есть теперь он указывает на элемент, который идёт после удаляемого элемента
|
|||
|
|
``` Go
|
|||
|
|
current := ll.head
|
|||
|
|
|
|||
|
|
for current.next != nil {
|
|||
|
|
if current.next.data.Name == targetName {
|
|||
|
|
current.next = current.next.next
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
current = current.next
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Бинарное дерево поиска
|
|||
|
|
После того, как мы нашли узел, который необходимо удалить, у нас возможны три случая.
|
|||
|
|
|
|||
|
|
Случай 1: У удаляемого узла нет правого ребенка.
|
|||
|
|
В этом случае мы просто перемещаем левого ребенка (3) на место удаляемого узла(5). В результате дерево будет выглядеть так:
|
|||
|
|
```
|
|||
|
|
Удаляем элемент со значением 5
|
|||
|
|
ДО УДАЛЕНИЯ: ПОСЛЕ УДАЛЕНИЯ:
|
|||
|
|
|
|||
|
|
[8] [8]
|
|||
|
|
/ \ / \
|
|||
|
|
[5] [10] [3] [10]
|
|||
|
|
/ / \
|
|||
|
|
[3] [1] [4]
|
|||
|
|
/ \
|
|||
|
|
[1] [4]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
|
|||
|
|
Случай 2: У удаляемого узла есть только правый ребенок, у которого, в свою очередь нет левого ребенка.
|
|||
|
|
В этом случае нам надо переместить правого ребенка(8) удаляемого узла (5) на его место.
|
|||
|
|
```
|
|||
|
|
Удаляем элемент со значением 5
|
|||
|
|
До удаления: После удаления:
|
|||
|
|
|
|||
|
|
[10] [10]
|
|||
|
|
/ \ / \
|
|||
|
|
[5] [12] [8] [12]
|
|||
|
|
/ \ / \
|
|||
|
|
[1] [8] [1] [9]
|
|||
|
|
\
|
|||
|
|
[9]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
|
|||
|
|
Случай 3: У удаляемого узла есть первый ребенок, у которого есть левый ребенок.
|
|||
|
|
В этом случае место удаляемого узла занимает крайний левый ребенок правого ребенка удаляемого узла.
|
|||
|
|
Давайте посмотрим, почему это так. Мы знаем о поддереве, начинающемся с удаляемого узла следующее:
|
|||
|
|
|
|||
|
|
- Все значения справа от него больше или равны значению самого узла.
|
|||
|
|
- Наименьшее значение правого поддерева — крайнее левое.
|
|||
|
|
|
|||
|
|
Мы должны поместить на место удаляемого узел со значением, меньшим или равным любому узлу справа от него. Для этого нам необходимо найти наименьшее значение в правом поддереве. Поэтому мы берем крайний левый узел правого поддерева.
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Удаляем элемент со значением 5
|
|||
|
|
До удаления: После удаления:
|
|||
|
|
|
|||
|
|
[10] [10]
|
|||
|
|
/ \ / \
|
|||
|
|
[5] [12] [7] [12]
|
|||
|
|
/ \ / \
|
|||
|
|
[1] [9] [1] [9]
|
|||
|
|
/ /
|
|||
|
|
[7] [8]
|
|||
|
|
\
|
|||
|
|
[8]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Хеш-таблица
|
|||
|
|
Находим индекс элемента в таблица, далее производим удаление элемента в связном списке, который соответствует этому индексу.
|
|||
|
|
|
|||
|
|
|
|||
|
|
# Вывод
|
|||
|
|
Мы реализовали и протестировали три различные структуры хранения данных: связный список, бинарное дерево поиска и хеш-таблица. Сравнили скорость операций вставки, удаления и поиска для каждой структуры.
|
|||
|
|
Если не важен порядок хранения и извлечения данных, то хеш-таблица лучший выбор для быстрых вставки, удаления и поиска.
|
|||
|
|
Если нужно хранить данные с возможностью быстрого отсортированного обхода, то стоит выбрать бинарное дерево поиска.
|
|||
|
|
Если нужно хранить данные в порядке поступления(например очередь), то стоит выбрать связный список.
|
|||
|
|
|