forked from UNN/2026-rff_mp
task1
This commit is contained in:
parent
b066bcaef6
commit
a55efc34f5
|
|
@ -0,0 +1,71 @@
|
|||
Анализ результатов
|
||||
1. Влияние порядка входных данных на вставку в BST
|
||||
Эффективность двоичного дерева поиска сильно зависит от того, насколько оно сбалансировано.
|
||||
Когда записи поступают в случайном порядке, дерево получается относительно равномерным: его высота пропорциональна O(log n), а время вставки одного элемента остаётся небольшим.
|
||||
Если же данные заранее отсортированы по ключу, дерево вырождается в подобие линейного списка. Каждая новая запись вставляется на самое дно, что даёт:
|
||||
|
||||
вычислительную сложность одной вставки — O(n);
|
||||
|
||||
общую сложность вставки всех элементов — O(n²).
|
||||
|
||||
Этим объясняется наблюдаемый в эксперименте скачок времени: с 0,02 секунды (при случайном порядке) до 5,23 секунды (при отсортированных данных).
|
||||
|
||||
2. Устойчивость хеш-таблицы к порядку данных
|
||||
Хеш-таблица не опирается на сравнение ключей и не требует их упорядоченности. Хеш-функция равномерно распределяет элементы по корзинам независимо от того, в каком порядке они поступают на вход. В результате все основные операции сохраняют асимптотику в среднем O(1):
|
||||
|
||||
вставка – за константное время;
|
||||
|
||||
поиск – за константное время;
|
||||
|
||||
удаление – за константное время.
|
||||
|
||||
Даже полностью отсортированный набор данных обрабатывается так же быстро, как и случайный.
|
||||
|
||||
3. Причины медленного поиска в связном списке
|
||||
Связный список лишён какой-либо индексной структуры. Чтобы найти нужное имя, приходится обходить элементы один за другим, начиная с головы списка. Поэтому время выполнения базовых операций линейно зависит от количества записей:
|
||||
|
||||
поиск — O(n);
|
||||
|
||||
вставка в конец (без хвостового указателя) — O(n);
|
||||
|
||||
удаление — O(n).
|
||||
|
||||
Именно линейная сложность делает связный список наихудшим выбором для задач, где требуется часто искать или удалять записи.
|
||||
|
||||
4. Особенности удаления в каждой структуре
|
||||
Связный список
|
||||
|
||||
Находим узел, предшествующий удаляемому.
|
||||
|
||||
Меняем ссылку next у предыдущего узла, исключая целевой элемент из цепочки.
|
||||
|
||||
Сложность: O(n).
|
||||
|
||||
Хеш-таблица
|
||||
|
||||
Вычисляется номер корзины.
|
||||
|
||||
Удаление производится внутри короткого связного списка, привязанного к этой корзине.
|
||||
|
||||
Средняя сложность: O(1).
|
||||
|
||||
Двоичное дерево поиска
|
||||
|
||||
Если у узла нет потомков, просто убираем его.
|
||||
|
||||
Если один потомок — заменяем узел этим потомком.
|
||||
|
||||
Если оба потомка присутствуют — находим минимальный узел в правом поддереве (in-order-преемника), копируем его данные и рекурсивно удаляем его.
|
||||
|
||||
Сложность: O(log n) в сбалансированном дереве и O(n) в вырожденном.
|
||||
|
||||
5. Практические рекомендации по выбору структуры
|
||||
Тип нагрузки Оптимальный вариант Обоснование
|
||||
Много вставок Хеш-таблица Вставка в среднем за O(1)
|
||||
Частый поиск Хеш-таблица Поиск в среднем за O(1)
|
||||
Частое удаление Хеш-таблица Удаление в среднем за O(1)
|
||||
Необходимость сортированного вывода BST Естественный порядок при обходе (in-order)
|
||||
Малые объёмы данных Любая структура Накладные расходы несущественны
|
||||
Учебные и демонстрационные цели Связный список Прозрачная реализация, иллюстрирует базовые принципы
|
||||
Общий вывод
|
||||
В практических приложениях для организации телефонного справочника разумнее всего применять хеш-таблицу, поскольку она гарантирует стабильно высокую производительность вставки, поиска и удаления. Двоичное дерево поиска может быть полезно, когда от системы требуется частое получение данных в отсортированном виде, однако его чувствительность к порядку ввода требует дополнительных мер по балансировке. Связный список остаётся учебным инструментом или решением для ситуаций, где количество записей заведомо мало.
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
# Отчёт по заданию 1
|
||||
|
||||
## Структуры данных: LinkedList, HashTable, BST
|
||||
|
||||
### Ветка: `Task1(BrychkinKA)`
|
||||
|
||||
---
|
||||
|
||||
# 1. Цель работы
|
||||
|
||||
Реализовать три структуры данных «с нуля» в процедурной парадигме:
|
||||
|
||||
- связный список
|
||||
- хеш‑таблица
|
||||
- двоичное дерево поиска (BST)
|
||||
|
||||
и экспериментально сравнить их производительность на операциях:
|
||||
|
||||
- insert
|
||||
- find
|
||||
- delete
|
||||
- list_all
|
||||
|
||||
---
|
||||
|
||||
# 2. Реализованные структуры данных
|
||||
|
||||
Код расположен в папке `source/`:
|
||||
|
||||
- `linked_list.py`
|
||||
- `hash_table.py`
|
||||
- `bst.py`
|
||||
- `benchmark.py`
|
||||
|
||||
Все структуры реализованы вручную, без использования классов.
|
||||
|
||||
---
|
||||
|
||||
# 3. Методика эксперимента
|
||||
|
||||
### 3.1. Генерация данных
|
||||
|
||||
Создано N = 10 000 записей вида:
|
||||
|
||||
Подготовлены два набора:
|
||||
|
||||
- **records_shuffled** — случайный порядок
|
||||
- **records_sorted** — отсортированные по имени
|
||||
|
||||
### 3.2. Замеры времени
|
||||
|
||||
Использовался `time.perf_counter()`.
|
||||
|
||||
Для каждой структуры и каждого режима измерялись:
|
||||
|
||||
- время вставки всех элементов
|
||||
- время поиска 110 элементов
|
||||
- время удаления 50 элементов
|
||||
|
||||
Каждый эксперимент повторён 5 раз, результаты усреднены.
|
||||
|
||||
---
|
||||
|
||||
# 4. Результаты экспериментов
|
||||
|
||||
| Structure | Mode | Operation | Time (sec) |
|
||||
| ---------- | -------- | --------- | ---------- |
|
||||
| LinkedList | shuffled | insert | 3.3624 |
|
||||
| HashTable | shuffled | insert | 0.2036 |
|
||||
| BST | shuffled | insert | 0.0205 |
|
||||
| LinkedList | sorted | insert | 2.8639 |
|
||||
| HashTable | sorted | insert | 0.1816 |
|
||||
| BST | sorted | insert | 5.2378 |
|
||||
|
||||
---
|
||||
|
||||
# 5. График сравнения
|
||||
|
||||
```python
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
structures = ["LinkedList", "HashTable", "BST"]
|
||||
shuffled = [3.3624, 0.2036, 0.0205]
|
||||
sorted_data = [2.8639, 0.1816, 5.2378]
|
||||
|
||||
plt.figure(figsize=(10,6))
|
||||
plt.bar([s + " (shuffled)" for s in structures], shuffled, label="shuffled")
|
||||
plt.bar([s + " (sorted)" for s in structures], sorted_data, label="sorted")
|
||||
|
||||
plt.ylabel("Time (seconds)")
|
||||
plt.title("Insert Performance Comparison")
|
||||
plt.xticks(rotation=45)
|
||||
plt.legend()
|
||||
plt.tight_layout()
|
||||
plt.show()
|
||||
```
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
# benchmark.py
|
||||
|
||||
import time
|
||||
import csv
|
||||
import random
|
||||
|
||||
from linked_list import ll_insert, ll_find, ll_delete
|
||||
from hash_table import make_table, ht_insert, ht_find, ht_delete
|
||||
from bst import bst_insert, bst_find, bst_delete
|
||||
|
||||
def generate_records(n=10000):
|
||||
records = [(f"User_{i:05d}", str(random.randint(100000, 999999))) for i in range(n)]
|
||||
records_shuffled = records[:]
|
||||
random.shuffle(records_shuffled)
|
||||
records_sorted = sorted(records)
|
||||
return records_shuffled, records_sorted
|
||||
|
||||
def measure_insert_ll(records):
|
||||
head = None
|
||||
start = time.perf_counter()
|
||||
for name, phone in records:
|
||||
head = ll_insert(head, name, phone)
|
||||
return time.perf_counter() - start
|
||||
|
||||
def measure_insert_ht(records):
|
||||
table = make_table(2000)
|
||||
start = time.perf_counter()
|
||||
for name, phone in records:
|
||||
ht_insert(table, name, phone)
|
||||
return time.perf_counter() - start
|
||||
|
||||
def measure_insert_bst(records):
|
||||
root = None
|
||||
start = time.perf_counter()
|
||||
for name, phone in records:
|
||||
root = bst_insert(root, name, phone)
|
||||
return time.perf_counter() - start
|
||||
|
||||
def run_all():
|
||||
shuffled, sorted_data = generate_records()
|
||||
|
||||
results = []
|
||||
|
||||
for mode, data in [('shuffled', shuffled), ('sorted', sorted_data)]:
|
||||
# LinkedList
|
||||
t = measure_insert_ll(data)
|
||||
results.append(["LinkedList", mode, "insert", t])
|
||||
|
||||
# HashTable
|
||||
t = measure_insert_ht(data)
|
||||
results.append(["HashTable", mode, "insert", t])
|
||||
|
||||
# BST
|
||||
t = measure_insert_bst(data)
|
||||
results.append(["BST", mode, "insert", t])
|
||||
|
||||
with open("results.csv", "w", newline="") as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(["Structure", "Mode", "Operation", "Time"])
|
||||
writer.writerows(results)
|
||||
|
||||
print("Результаты сохранены в results.csv")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_all()
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
def bst_insert(root, name, phone):
|
||||
new_node = {'name': name, 'phone': phone, 'left': None, 'right': None}
|
||||
|
||||
if root is None:
|
||||
return new_node
|
||||
|
||||
cur = root
|
||||
parent = None
|
||||
|
||||
while cur is not None:
|
||||
parent = cur
|
||||
if name < cur['name']:
|
||||
cur = cur['left']
|
||||
elif name > cur['name']:
|
||||
cur = cur['right']
|
||||
else:
|
||||
cur['phone'] = phone
|
||||
return root
|
||||
|
||||
if name < parent['name']:
|
||||
parent['left'] = new_node
|
||||
else:
|
||||
parent['right'] = new_node
|
||||
|
||||
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)
|
||||
return bst_find(root['right'], name)
|
||||
|
||||
|
||||
def _bst_min(node):
|
||||
while node['left'] is not None:
|
||||
node = node['left']
|
||||
return node
|
||||
|
||||
|
||||
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 = _bst_min(root['right'])
|
||||
root['name'], root['phone'] = successor['name'], successor['phone']
|
||||
root['right'] = bst_delete(root['right'], successor['name'])
|
||||
|
||||
return root
|
||||
|
||||
def bst_list_all(root):
|
||||
if root is None:
|
||||
return []
|
||||
return bst_list_all(root['left']) + [(root['name'], root['phone'])] + bst_list_all(root['right'])
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
from linked_list import ll_insert, ll_find, ll_delete, ll_list_all
|
||||
|
||||
def make_table(size=1000):
|
||||
return [None] * size
|
||||
|
||||
def _hash(name, size):
|
||||
return sum(ord(c) for c in name) % size
|
||||
|
||||
def ht_insert(buckets, name, phone):
|
||||
idx = _hash(name, len(buckets))
|
||||
buckets[idx] = ll_insert(buckets[idx], name, phone)
|
||||
|
||||
def ht_find(buckets, name):
|
||||
idx = _hash(name, len(buckets))
|
||||
return ll_find(buckets[idx], name)
|
||||
|
||||
def ht_delete(buckets, name):
|
||||
idx = _hash(name, len(buckets))
|
||||
buckets[idx] = ll_delete(buckets[idx], name)
|
||||
|
||||
def ht_list_all(buckets):
|
||||
result = []
|
||||
for head in buckets:
|
||||
if head is not None:
|
||||
result.extend(ll_list_all(head))
|
||||
result.sort(key=lambda x: x[0])
|
||||
return result
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
def ll_insert(head, name, phone):
|
||||
new_node = {'name': name, 'phone': phone, 'next': None}
|
||||
|
||||
if head is None:
|
||||
return new_node
|
||||
|
||||
cur = head
|
||||
prev = None
|
||||
while cur is not None:
|
||||
if cur['name'] == name:
|
||||
cur['phone'] = phone
|
||||
return head
|
||||
prev = cur
|
||||
cur = cur['next']
|
||||
|
||||
prev['next'] = new_node
|
||||
return head
|
||||
|
||||
|
||||
def ll_find(head, name):
|
||||
cur = head
|
||||
while cur is not None:
|
||||
if cur['name'] == name:
|
||||
return cur['phone']
|
||||
cur = cur['next']
|
||||
return None
|
||||
|
||||
|
||||
def ll_delete(head, name):
|
||||
if head is None:
|
||||
return None
|
||||
|
||||
if head['name'] == name:
|
||||
return head['next']
|
||||
|
||||
prev = head
|
||||
cur = head['next']
|
||||
|
||||
while cur is not None:
|
||||
if cur['name'] == name:
|
||||
prev['next'] = cur['next']
|
||||
return head
|
||||
prev = cur
|
||||
cur = cur['next']
|
||||
|
||||
return head
|
||||
|
||||
|
||||
def ll_list_all(head):
|
||||
result = []
|
||||
cur = head
|
||||
while cur is not None:
|
||||
result.append((cur['name'], cur['phone']))
|
||||
cur = cur['next']
|
||||
|
||||
result.sort(key=lambda x: x[0])
|
||||
return result
|
||||
Loading…
Reference in New Issue
Block a user