Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a55efc34f5 | |||
| b066bcaef6 |
0
BrychkinKA/427.md
Normal file
0
BrychkinKA/427.md
Normal file
71
BrychkinKA/docs/data/conclusion.txt
Normal file
71
BrychkinKA/docs/data/conclusion.txt
Normal file
|
|
@ -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)
|
||||||
|
Малые объёмы данных Любая структура Накладные расходы несущественны
|
||||||
|
Учебные и демонстрационные цели Связный список Прозрачная реализация, иллюстрирует базовые принципы
|
||||||
|
Общий вывод
|
||||||
|
В практических приложениях для организации телефонного справочника разумнее всего применять хеш-таблицу, поскольку она гарантирует стабильно высокую производительность вставки, поиска и удаления. Двоичное дерево поиска может быть полезно, когда от системы требуется частое получение данных в отсортированном виде, однако его чувствительность к порядку ввода требует дополнительных мер по балансировке. Связный список остаётся учебным инструментом или решением для ситуаций, где количество записей заведомо мало.
|
||||||
96
BrychkinKA/docs/data/report_task1.md
Normal file
96
BrychkinKA/docs/data/report_task1.md
Normal file
|
|
@ -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()
|
||||||
|
```
|
||||||
65
BrychkinKA/docs/source/benchmark.py
Normal file
65
BrychkinKA/docs/source/benchmark.py
Normal file
|
|
@ -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()
|
||||||
66
BrychkinKA/docs/source/bst.py
Normal file
66
BrychkinKA/docs/source/bst.py
Normal file
|
|
@ -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'])
|
||||||
27
BrychkinKA/docs/source/hash_table.py
Normal file
27
BrychkinKA/docs/source/hash_table.py
Normal file
|
|
@ -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
|
||||||
57
BrychkinKA/docs/source/linked_list.py
Normal file
57
BrychkinKA/docs/source/linked_list.py
Normal file
|
|
@ -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
|
||||||
7
BrychkinKA/docs/source/results.csv
Normal file
7
BrychkinKA/docs/source/results.csv
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
Structure,Mode,Operation,Time
|
||||||
|
LinkedList,shuffled,insert,3.3623596000015823
|
||||||
|
HashTable,shuffled,insert,0.2035665000003064
|
||||||
|
BST,shuffled,insert,0.020500900000115507
|
||||||
|
LinkedList,sorted,insert,2.8638613000002806
|
||||||
|
HashTable,sorted,insert,0.18161420000069484
|
||||||
|
BST,sorted,insert,5.237768099999812
|
||||||
|
Loading…
Reference in New Issue
Block a user