add report
This commit is contained in:
parent
d232cc536d
commit
b515024d07
267
svetlakovkyu/docs/report.md
Normal file
267
svetlakovkyu/docs/report.md
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
# Задание 1: Структуры данных
|
||||
|
||||
|
||||
>Выполнил: Светлаков Кирилл
|
||||
>
|
||||
>Студент 426 группы
|
||||
|
||||
|
||||
|
||||
## Цель работы
|
||||
|
||||
Реализовать три различные структуры данных «с нуля», применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций. Необходимо собственными руками написать код, чтобы понять внутреннее устройство связного списка, хеш-таблицы и двоичного дерева поиска, а также осознать их сильные и слабые стороны на практике.
|
||||
|
||||
---
|
||||
|
||||
## 1. Реализация структур данных
|
||||
|
||||
### 1.1 Связный список (Linked List)
|
||||
|
||||
```python
|
||||
def ll_insert(head, name, phone):
|
||||
current = head
|
||||
while current is not None:
|
||||
if current['name'] == name:
|
||||
current['phone'] = phone
|
||||
return head
|
||||
current = current['next']
|
||||
return {'name': name, 'phone': phone, 'next': head}
|
||||
|
||||
def ll_find(head, name):
|
||||
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
|
||||
current = head
|
||||
if head['name'] == name:
|
||||
return head['next']
|
||||
while current['next'] is not None:
|
||||
if current['next']['name'] == name:
|
||||
current['next'] = current['next']['next']
|
||||
break
|
||||
current = current['next']
|
||||
return head
|
||||
|
||||
def ll_list_all(head):
|
||||
res = []
|
||||
current = head
|
||||
while current is not None:
|
||||
res.append((current['name'], current['phone']))
|
||||
current = current['next']
|
||||
res.sort()
|
||||
return res
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.2 Хеш-таблица (Hash Table)
|
||||
|
||||
```python
|
||||
def ht_insert(buckets, name, phone):
|
||||
index = hash(name) % len(buckets)
|
||||
current_head = buckets[index]
|
||||
new_head = ll_insert(current_head, name, phone)
|
||||
buckets[index] = new_head
|
||||
|
||||
def ht_find(buckets, name):
|
||||
index = hash(name)%len(buckets)
|
||||
slot_head = buckets[index]
|
||||
res_ph = ll_find(slot_head, name)
|
||||
return res_ph
|
||||
|
||||
def ht_delete(buskets, name):
|
||||
index = hash(name)%len(buskets)
|
||||
buskets[index] = ll_delete(buskets[index], name)
|
||||
|
||||
def ht_list_all(buckets):
|
||||
all_rec = []
|
||||
for head in buckets:
|
||||
current = head
|
||||
while current is not None:
|
||||
all_rec.append((current['name'], current['phone']))
|
||||
current = current['next']
|
||||
all_rec.sort()
|
||||
return all_rec
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.3 Двоичное дерево поиска (BST)
|
||||
```python
|
||||
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):
|
||||
if root is None:
|
||||
return None
|
||||
if name == root['name']:
|
||||
return root['phone']
|
||||
|
||||
if name < root['name']:
|
||||
return bst_find(root['left'], name)
|
||||
else:
|
||||
return bst_find(root['right'], name)
|
||||
|
||||
def get_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:
|
||||
return root['right']
|
||||
if root['right'] is None:
|
||||
return root['left']
|
||||
successor = get_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, res = None):
|
||||
if res is None:
|
||||
res = []
|
||||
if root is not None:
|
||||
bst_list_all(root['left'], res)
|
||||
res.append({'name': root['name'], 'phone': root['phone']})
|
||||
bst_list_all(root['right'], res)
|
||||
#сортировка уже сделана
|
||||
return res
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Эксперимент
|
||||
|
||||
Для экспериментального сравнения были сгенерированы 10 000 записей вида `(User_XXXXX, номер_телефона)`.
|
||||
|
||||
Каждый тест проводился в двух режимах входных данных:
|
||||
- Случайный (shuffled) - записи перемешаны в произвольном порядке
|
||||
- Отсортированный (sorted) - записи отсортированы по имени
|
||||
|
||||
Для каждой структуры и режима измерялось время выполнения трёх операций:
|
||||
- Вставка - 10 000 элементов
|
||||
- Поиск - 110 запросов (100 существующих + 10 несуществующих)
|
||||
- Удаление - 50 элементов
|
||||
|
||||
Каждый замер повторялся 5 раз, итоговое значение - среднее арифметическое.
|
||||
|
||||
В файл `results.py` записывались в следующем виде
|
||||
| Structure | Mode | Operation | Time |
|
||||
|---|---|---|---|
|
||||
| Название структуры: LL, BST, HT | Режим данных: shufled, sorted | Операция и номер попытки или среднее | Время выполнения в секундах |
|
||||
| LL | shufled | Вставка (попытка 1) | 2.730272 |
|
||||
| LL | shufled | Вставка (попытка 2) | 2.675253 |
|
||||
| LL | shufled | Вставка (попытка 3) | 2.628982 |
|
||||
| LL | shufled | Вставка (попытка 4) | 2.673355 |
|
||||
| LL | shufled | Вставка (попытка 5) | 2.636129 |
|
||||
| LL | shufled | Вставка СРЕДНЕЕ | 2.668798 |
|
||||
| ... | ... | ... | ... |
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 3. Результаты
|
||||
|
||||
### 3.1 Вставка
|
||||
|
||||

|
||||
|
||||
|
||||
### 3.2 Поиск
|
||||
|
||||

|
||||
|
||||
|
||||
### 3.3 Удаление
|
||||
|
||||

|
||||
|
||||
|
||||
---
|
||||
|
||||
## 4. Анализ результатов
|
||||
|
||||
### 4.1 Влияние порядка данных на BST: деградация до O(n)
|
||||
|
||||
При вставке случайных данных BST ведёт себя как сбалансированное дерево: каждый новый ключ с равной вероятностью уходит влево или вправо, глубина дерева составляет ~log₂(10000) ≈ 13. Операция вставки 10 000 записей заняла 0.025 сек.
|
||||
|
||||
При вставке отсортированных данных каждый новый ключ оказывается больше предыдущего и всегда уходит вправо. Дерево вырождается в линейный список глубиной 10 000. Каждая следующая вставка проходит на один шаг дальше, итоговая сложность становится O(1 + 2 + ... + n) = O(n²). Именно поэтому вставка отсортированных данных заняла 13.16 сек - более чем в 500 раз медленнее случайных.
|
||||
|
||||
Это фундаментальный недостаток, реализованного BST.
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Нечувствительность хеш-таблицы к порядку данных
|
||||
|
||||
Хеш-таблица вычисляет позицию элемента через `hash(name) % len(buckets)`. Функция `hash()` в Python зависит только от значения ключа, но никак не от порядка вставки. Независимо от того, отсортированы данные или перемешаны, каждый ключ попадает в тот же бакет с той же скоростью.
|
||||
|
||||
Это подтверждается полученными результатами: время вставки для случайных данных - 0.0281 сек, для отсортированных - 0.0286 сек, что практически одинаково.
|
||||
|
||||
В графике для поиска и удаления, моожно заметить примено такой же результат - время работы программы для отсортированных и не для неотсортированных данных одинаково.
|
||||
|
||||
---
|
||||
|
||||
### 4.3 Медленный поиск в связном списке
|
||||
|
||||
Связный список не имеет структуры, позволяющей перейти к нужному элементу. При поиске приходится последовательно проходить узел за узлом от головы до нужного элемента - это линейный поиск O(n).
|
||||
|
||||
При N = 10 000 и 110 запросах среднее время поиска составило 0.03 сек, в ~150 раз медленнее поиска в BST и в ~75 раз медленнее поиска в HT на случайных данных.
|
||||
|
||||
Порядок данных на LL практически не влияет: в любом случае нужно проходить примерно половину списка для найденных и весь список для несуществующих записей.
|
||||
|
||||
---
|
||||
|
||||
### 4.4 Удаление в каждой структуре
|
||||
|
||||
Связный список: удаление требует сначала найти удаляемый элемент - O(n). Затем достаточно переключить одну ссылку у предшественника. Итог: O(n) за счёт поиска. При 50 удалениях время составило ~0.02 сек.
|
||||
|
||||
Хеш-таблица: вычисляем бакет за O(1), затем удаляем элемент из короткой цепочки. Итог: O(1) амортизированно. При 50 удалениях время составило 0.00003 сек для случайных и 0.00004 для отсортированых данных, что значительно быстрее связного списка, но медленее BST(для неотсортированных данных).
|
||||
|
||||
BST (случайные данные): находим узел за O(log n). Если у него два потомка - находим минимум правого поддерева, копируем его значение и рекурсивно удаляем его. Итог: O(log n). При 50 удалениях время составило 0.00012 сек.
|
||||
|
||||
BST (отсортированные данные): дерево вырождено в список, глубина O(n). Каждое удаление - O(n), 50 удалений - 0.060 сек, что медленнее даже связного списка.
|
||||
|
||||
---
|
||||
|
||||
## 5. Выводы
|
||||
|
||||
Из результатов эксперимента, можно сделать вывод, что нет универсальной структуры данных, и под конкретную задачу надо выбирать определенную.
|
||||
|
||||
|
||||
Хеш-таблица - лучший выбор, если нужны быстрые вставка, поиск и удаление и порядок хранения данных не важен. Идеальна для кешей, словарей, телефонных справочников, где операции выполняются в O(1). Не подходит для задач, требующих обхода данных в отсортированном порядке.
|
||||
|
||||
BST - лучший выбор, когда важен порядок данных: обход дерева in-order даёт отсортированную последовательность за O(n), можно быстро найти минимум/максимум или диапазон ключей. Подходит для задач типа «найти все записи от A до B». Критически важно использовать только на случайных или специально перемешанных данных.
|
||||
|
||||
Связный список - подходит для задач, где данные постоянно добавляются и удаляются с известной позиции (начало/конец списка), а поиск по значению происходит редко. В телефонном справочнике с 10 000 записей является наихудшим вариантом из трёх.
|
||||
|
||||
| Задача | Лучшая структура |
|
||||
|---|---|
|
||||
| Частые вставки и удаления по ключу | Хеш-таблица |
|
||||
| Частый поиск по ключу | Хеш-таблица |
|
||||
| Обход данных в отсортированном порядке | BST |
|
||||
| Поиск по диапазону ключей | BST |
|
||||
| Встава/удаление в начало/конец | Связный список |
|
||||
| Данные приходят отсортированными, нужен быстрый поиск | Хеш-таблица |
|
||||
Loading…
Reference in New Issue
Block a user