forked from UNN/2026-rff_mp
Merge pull request '[1] Task' (#283) from Anton_Vinichuk/2026-rff_mp_ViniuchukAN:Task1(vinichukan) into develop
Reviewed-on: UNN/2026-rff_mp#283
This commit is contained in:
commit
210ea8ece8
0
vinichukan/427.md
Normal file
0
vinichukan/427.md
Normal file
83
vinichukan/docs/data/ conclusion.md
Normal file
83
vinichukan/docs/data/ conclusion.md
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
## Анализ результатов
|
||||||
|
|
||||||
|
### **1. Как порядок входных данных влияет на скорость вставки в BST**
|
||||||
|
|
||||||
|
BST работает быстро только если дерево сбалансировано.
|
||||||
|
При случайном порядке глубина дерева ≈ `O(log n)`, поэтому вставка быстрая.
|
||||||
|
Но при отсортированных данных дерево вырождается в цепочку.
|
||||||
|
Вставка становится:
|
||||||
|
|
||||||
|
- **O(n)** на одну операцию
|
||||||
|
- **O(n²)** на вставку всех элементов
|
||||||
|
|
||||||
|
Это приводит к резкому росту времени (в эксперименте: **0.02 сек → 5.23 сек**).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **2. Почему хеш‑таблица почти не чувствительна к порядку**
|
||||||
|
|
||||||
|
Хеш‑функция распределяет элементы по бакетам независимо от порядка входа.
|
||||||
|
Поэтому:
|
||||||
|
|
||||||
|
- вставка ≈ **O(1)**
|
||||||
|
- поиск ≈ **O(1)**
|
||||||
|
- удаление ≈ **O(1)**
|
||||||
|
|
||||||
|
Даже если данные отсортированы, хеш‑таблица работает одинаково быстро.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **3. Почему связный список всегда медленный при поиске**
|
||||||
|
|
||||||
|
Связный список не имеет индексов.
|
||||||
|
Поиск идёт последовательно: head → next → next → ...
|
||||||
|
|
||||||
|
Поэтому:
|
||||||
|
|
||||||
|
- поиск = **O(n)**
|
||||||
|
- вставка в конец = **O(n)**
|
||||||
|
- удаление = **O(n)**
|
||||||
|
|
||||||
|
Это делает его самой медленной структурой для телефонного справочника.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **4. Как работает удаление в каждой структуре**
|
||||||
|
|
||||||
|
#### Связный список
|
||||||
|
- ищем предыдущий узел
|
||||||
|
- перенаправляем ссылки
|
||||||
|
- сложность: **O(n)**
|
||||||
|
|
||||||
|
#### Хеш‑таблица
|
||||||
|
- вычисляем бакет
|
||||||
|
- удаляем элемент в маленьком списке внутри бакета
|
||||||
|
- сложность: **O(1)** в среднем
|
||||||
|
|
||||||
|
#### BST
|
||||||
|
3 случая:
|
||||||
|
1. лист
|
||||||
|
2. один потомок
|
||||||
|
3. два потомка (замена на inorder‑преемника)
|
||||||
|
|
||||||
|
Сложность:
|
||||||
|
|
||||||
|
- **O(log n)** в среднем
|
||||||
|
- **O(n)** в худшем случае (несбалансированное дерево)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **5. Какую структуру выбирать в реальной жизни**
|
||||||
|
|
||||||
|
| Задача | Лучшая структура | Причина |
|
||||||
|
|--------|------------------|---------|
|
||||||
|
| Частые вставки | **HashTable** | O(1) |
|
||||||
|
| Частый поиск | **HashTable** | O(1) |
|
||||||
|
| Частое удаление | **HashTable** | O(1) |
|
||||||
|
| Нужен отсортированный вывод | **BST** | in‑order обход |
|
||||||
|
| Маленькие данные | Любая | Разницы нет |
|
||||||
|
| Учебные цели | LinkedList | Простая структура |
|
||||||
|
|
||||||
|
### **Общий вывод:**
|
||||||
|
> В реальных приложениях телефонный справочник почти всегда реализуют через **хеш‑таблицу**, так как она обеспечивает лучшую производительность для вставки, поиска и удаления.
|
||||||
|
|
||||||
95
vinichukan/docs/report_task1.md
Normal file
95
vinichukan/docs/report_task1.md
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
# Отчёт по заданию 1
|
||||||
|
## Структуры данных: LinkedList, HashTable, BST
|
||||||
|
### Ветка: `Task1(vinichukan)`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 1. Цель работы
|
||||||
|
|
||||||
|
Реализовать три структуры данных «с нуля» в процедурной парадигме:
|
||||||
|
|
||||||
|
- связный список
|
||||||
|
- хеш‑таблица
|
||||||
|
- двоичное дерево поиска (BST)
|
||||||
|
|
||||||
|
и экспериментально сравнить их производительность на операциях:
|
||||||
|
|
||||||
|
- insert
|
||||||
|
- find
|
||||||
|
- delete
|
||||||
|
- list_all
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 2. Реализованные структуры данных
|
||||||
|
|
||||||
|
Код расположен в папке `src/`:
|
||||||
|
|
||||||
|
- `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
vinichukan/src/benchmark.py
Normal file
65
vinichukan/src/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
vinichukan/src/bst.py
Normal file
66
vinichukan/src/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
vinichukan/src/hash_table.py
Normal file
27
vinichukan/src/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
vinichukan/src/linked_list.py
Normal file
57
vinichukan/src/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
vinichukan/src/results.csv
Normal file
7
vinichukan/src/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