for 1-st ex
This commit is contained in:
parent
838c2afa68
commit
ee0aecd7ca
137
fomichevks/docs/отчет.txt
Normal file
137
fomichevks/docs/отчет.txt
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
1. Цель работы
|
||||||
|
Реализовать три базовые структуры данных без использования объектно-ориентированных механизмов, применить их для хранения записей телефонного справочника, экспериментально измерить производительность операций вставки, поиска и удаления, а также проанализировать влияние порядка входных данных на время выполнения.
|
||||||
|
|
||||||
|
Связный список (LinkedListPhoneBook)
|
||||||
|
Узел: {'name': str, 'phone': str, 'next': dict | None}
|
||||||
|
Операции:
|
||||||
|
ll_insert: линейный проход до конца, обновление при совпадении имени, вставка нового узла в хвост. Возвращает голову списка.
|
||||||
|
ll_find: последовательный перебор до первого совпадения.
|
||||||
|
ll_delete: поиск предшественника удаляемого узла, переназначение ссылки next.
|
||||||
|
ll_list_all: сбор записей в список, явная сортировка по имени.
|
||||||
|
|
||||||
|
Хеш-таблица с цепочками (HashTable)
|
||||||
|
Структура: список из BUCKET_COUNT = 1024 элементов, каждый элемент — голова связного списка.
|
||||||
|
Хеширование: idx = hash(name) % BUCKET_COUNT
|
||||||
|
Операции: делегируют соответствующим ll_* функциям для конкретного бакета.
|
||||||
|
|
||||||
|
Узел: {'name': str, 'phone': str, 'left': dict | None, 'right': dict | None}
|
||||||
|
Операции:
|
||||||
|
bst_insert: рекурсивное сравнение имён, создание листа при достижении None.
|
||||||
|
bst_find: рекурсивный спуск влево/вправо в зависимости от результата сравнения.
|
||||||
|
bst_delete: три случая: 0 потомков, 1 потомок, 2 потомка. При двух потомках используется inorder-преемник (минимальный элемент правого поддерева).
|
||||||
|
bst_list_all: центрированный (in-order) обход, гарантирующий отсортированный вывод без дополнительной сортировки.
|
||||||
|
|
||||||
|
Влияние порядка входных данных на скорость вставки в BST
|
||||||
|
Двоичное дерево поиска (BST) поддерживает инвариант: left.name < root.name < right.name. При вставке новых узлов алгоритм рекурсивно спускается по дереву, выбирая левую или правую ветвь в зависимости от результата сравнения.
|
||||||
|
|
||||||
|
Случай 1: Случайный порядок данных
|
||||||
|
Ключи распределяются по дереву хаотично
|
||||||
|
Левые и правые поддеревья заполняются примерно равномерно
|
||||||
|
Высота дерева: h ≈ log₂(N) ≈ 14 для N=10 000
|
||||||
|
Сложность вставки одного элемента: O(log N)
|
||||||
|
Общая сложность вставки всех N элементов: O(N log N)
|
||||||
|
|
||||||
|
Случай 2: Отсортированный порядок данных
|
||||||
|
Каждый следующий ключ больше всех предыдущих
|
||||||
|
Алгоритм всегда выбирает правую ветвь
|
||||||
|
Дерево вырождается в линейную цепочку
|
||||||
|
|
||||||
|
Почему хеш-таблица почти не чувствительна к порядку?
|
||||||
|
|
||||||
|
Функция hash() в Python:
|
||||||
|
Детерминирована: один и тот же ключ → один и тот же хеш
|
||||||
|
Равномерно распределяет значения по пространству хешей
|
||||||
|
Не зависит от порядка вызова: hash("User_00001") всегда одинаков
|
||||||
|
|
||||||
|
Распределение по бакетам
|
||||||
|
При N=10 000 записей и 1024 бакетах:
|
||||||
|
Ожидаемая загрузка: α = N / BUCKET_COUNT ≈ 9.77 элементов на бакет
|
||||||
|
Даже если все ключи отсортированы, их хеши «размазываются» по всему диапазону
|
||||||
|
Внутри каждого бакета хранится короткий связный список (~10 элементов)
|
||||||
|
|
||||||
|
Почему связный список всегда медленен при поиске?
|
||||||
|
|
||||||
|
Связный список хранит элементы последовательно, без индексации
|
||||||
|
Для поиска элемента с именем X:
|
||||||
|
Начать с головы списка
|
||||||
|
Сравнить curr['name'] == X
|
||||||
|
Если не совпало → перейти к curr['next']
|
||||||
|
Повторять до нахождения или конца списка
|
||||||
|
Связный список не подходит для задач с частым поиском. Его удел очереди, стеки, или вспомогательная роль внутри других структур.
|
||||||
|
|
||||||
|
Как удаление работает в каждой структуре?
|
||||||
|
Связный список
|
||||||
|
def ll_delete(head, name):
|
||||||
|
if head['name'] == name:
|
||||||
|
return head['next']
|
||||||
|
curr = head
|
||||||
|
while curr['next']:
|
||||||
|
if curr['next']['name'] == name:
|
||||||
|
curr['next'] = curr['next']['next']
|
||||||
|
return head
|
||||||
|
curr = curr['next']
|
||||||
|
return head
|
||||||
|
|
||||||
|
Поиск узла (или его предшественника) — O(N)
|
||||||
|
Переназначение ссылки next — O(1)
|
||||||
|
Сборка мусора (автоматически в Python)
|
||||||
|
|
||||||
|
Хеш-таблица
|
||||||
|
def ht_delete(buckets, name):
|
||||||
|
idx = hash(name) % BUCKET_COUNT
|
||||||
|
buckets[idx] = ll_delete(buckets[idx], name)
|
||||||
|
|
||||||
|
Вычисление индекса бакета — O(1)
|
||||||
|
Поиск и удаление в связном списке бакета — O(L), где L ≈ 10
|
||||||
|
Итого: O(1) в среднем
|
||||||
|
|
||||||
|
Двоичное дерево поиска
|
||||||
|
def bst_delete(root, name):
|
||||||
|
# 1. Поиск узла
|
||||||
|
if name < root['name']:
|
||||||
|
root['left'] = bst_delete(root['left'], name)
|
||||||
|
elif name > root['name']:
|
||||||
|
root['right'] = bst_delete(root['right'], name)
|
||||||
|
else:
|
||||||
|
# 2. Три случая удаления
|
||||||
|
if root['left'] is None:
|
||||||
|
return root['right'] # 0 или 1 потомок
|
||||||
|
elif root['right'] is None:
|
||||||
|
return root['left']
|
||||||
|
else:
|
||||||
|
# 2 потомка: найти inorder-преемника
|
||||||
|
successor = _bst_find_min(root['right'])
|
||||||
|
root['name'] = successor['name']
|
||||||
|
root['phone'] = successor['phone']
|
||||||
|
root['right'] = bst_delete(root['right'], successor['name'])
|
||||||
|
return root
|
||||||
|
|
||||||
|
Поиск удаляемого узла — O(h)
|
||||||
|
Обработка случая:
|
||||||
|
0 потомков: просто удалить узел
|
||||||
|
1 потомок: «поднять» потомка на место удаляемого
|
||||||
|
2 потомка: найти минимум в правом поддереве (inorder-преемник), скопировать его данные, рекурсивно удалить преемника
|
||||||
|
Возврат обновлённого корня поддерева
|
||||||
|
|
||||||
|
Когда какую структуру использовать?
|
||||||
|
|
||||||
|
| Сценарий | Рекомендация |
|
||||||
|
|---|---|
|
||||||
|
| **Частый поиск** по имени | HashTable или BST (случайные данные) |
|
||||||
|
| **Данные приходят отсортированными** | HashTable (BST деградирует!) |
|
||||||
|
| **Нужен отсортированный список** | BST (in-order обход — бесплатный) |
|
||||||
|
| **Частые вставки/удаления + поиск** | HashTable |
|
||||||
|
| **Минимальная память, простота** | LinkedList (для малых N) |
|
||||||
|
| **Диапазонные запросы** (все имена A–M) | BST |
|
||||||
|
|
||||||
|
### Сложности операций
|
||||||
|
|
||||||
|
| Структура | Insert | Find | Delete | List (sorted) |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| LinkedList | O(n) | O(n) | O(n) | O(n log n) |
|
||||||
|
| HashTable | O(1) avg | O(1) avg | O(1) avg | O(n log n) |
|
||||||
|
| BST (сбалансированный) | O(log n) | O(log n) | O(log n) | O(n) |
|
||||||
|
| BST (вырожденный) | O(n) | O(n) | O(n) | O(n) |
|
||||||
|
|
||||||
|
|
||||||
|
HashTable — лучший выбор для телефонного справочника при частых вставках и поисках. BST лучше HashTable только если нужен отсортированный вывод без дополнительной сортировки — но при условии случайного порядка вставки или использования самобалансирующегося дерева (AVL, Red-Black).
|
||||||
Loading…
Reference in New Issue
Block a user