forked from UNN/2026-rff_mp
Merge pull request '[1] 1-st-exercise' (#315) from BoriskovaDV/2026-rff_mp:1-st-exercise into develop
Reviewed-on: UNN/2026-rff_mp#315
This commit is contained in:
commit
7e1a9c1dba
|
|
@ -0,0 +1 @@
|
|||
|
||||
71
BoriskovaDV/docs/data/1-st-exercise/bst_phonebook.py
Normal file
71
BoriskovaDV/docs/data/1-st-exercise/bst_phonebook.py
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
def create_node(name, phone):
|
||||
return {'name': name, 'phone': phone, 'left': None, 'right': None}
|
||||
|
||||
def bst_insert(root, name, phone):
|
||||
if root is None:
|
||||
return create_node(name, phone)
|
||||
|
||||
if name == root['name']:
|
||||
root['phone'] = phone
|
||||
elif name < root['name']:
|
||||
root['left'] = bst_insert(root['left'], name, phone)
|
||||
else:
|
||||
root['right'] = bst_insert(root['right'], name, phone)
|
||||
return root
|
||||
|
||||
def bst_find(root, name):
|
||||
if root is None:
|
||||
return None
|
||||
if name == root['name']:
|
||||
return root['phone']
|
||||
elif name < root['name']:
|
||||
return bst_find(root['left'], name)
|
||||
else:
|
||||
return bst_find(root['right'], name)
|
||||
|
||||
def _find_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']
|
||||
min_node = _find_min(root['right'])
|
||||
root['name'] = min_node['name']
|
||||
root['phone'] = min_node['phone']
|
||||
root['right'] = bst_delete(root['right'], min_node['name'])
|
||||
return root
|
||||
|
||||
def bst_list_all(root):
|
||||
result = []
|
||||
def inorder(node):
|
||||
if node is None:
|
||||
return
|
||||
inorder(node['left'])
|
||||
result.append((node['name'], node['phone']))
|
||||
inorder(node['right'])
|
||||
inorder(root)
|
||||
return result
|
||||
|
||||
if __name__ == '__main__':
|
||||
root = None
|
||||
root = bst_insert(root, 'Иван', '123-456')
|
||||
root = bst_insert(root, 'Борис', '789-012')
|
||||
root = bst_insert(root, 'Анна', '345-678')
|
||||
root = bst_insert(root, 'Иван', '111-222')
|
||||
print(bst_list_all(root))
|
||||
print(bst_find(root, 'Иван'))
|
||||
print(bst_find(root, 'Петр'))
|
||||
root = bst_delete(root, 'Борис')
|
||||
print(bst_list_all(root))
|
||||
126
BoriskovaDV/docs/data/1-st-exercise/experiment.py
Normal file
126
BoriskovaDV/docs/data/1-st-exercise/experiment.py
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
import random
|
||||
import time
|
||||
import csv
|
||||
import sys
|
||||
sys.setrecursionlimit(20000)
|
||||
|
||||
from linked_list_phonebook import ll_insert, ll_find, ll_delete, ll_list_all
|
||||
from hash_table_phonebook import ht_insert, ht_find, ht_delete, ht_list_all
|
||||
from bst_phonebook import bst_insert, bst_find, bst_delete, bst_list_all
|
||||
|
||||
def generate_records(n, seed=42):
|
||||
random.seed(seed)
|
||||
records = []
|
||||
for i in range(1, n+1):
|
||||
name = f"User_{i:05d}"
|
||||
phone = f"{random.randint(100,999)}-{random.randint(1000,9999)}"
|
||||
records.append((name, phone))
|
||||
return records
|
||||
|
||||
def prepare_datasets(base_records):
|
||||
shuffled = base_records.copy()
|
||||
random.shuffle(shuffled)
|
||||
sorted_records = sorted(base_records, key=lambda x: x[0])
|
||||
return shuffled, sorted_records
|
||||
|
||||
def run_experiment(struct_funcs, records, mode_name, repeats=5):
|
||||
results = []
|
||||
for rep in range(repeats):
|
||||
struct = struct_funcs['create']()
|
||||
|
||||
start = time.perf_counter()
|
||||
for name, phone in records:
|
||||
struct = struct_funcs['insert'](struct, name, phone)
|
||||
end = time.perf_counter()
|
||||
insert_time = end - start
|
||||
|
||||
existing_names = [name for name, _ in records]
|
||||
sample_existing = random.sample(existing_names, 100)
|
||||
nonexistent = [f"NotExist_{i}" for i in range(10)]
|
||||
search_names = sample_existing + nonexistent
|
||||
random.shuffle(search_names)
|
||||
|
||||
start = time.perf_counter()
|
||||
for name in search_names:
|
||||
_ = struct_funcs['find'](struct, name)
|
||||
end = time.perf_counter()
|
||||
find_time = end - start
|
||||
|
||||
to_delete = random.sample(existing_names, 50)
|
||||
start = time.perf_counter()
|
||||
for name in to_delete:
|
||||
struct = struct_funcs['delete'](struct, name)
|
||||
end = time.perf_counter()
|
||||
delete_time = end - start
|
||||
|
||||
results.append({
|
||||
'structure': struct_funcs['name'],
|
||||
'mode': mode_name,
|
||||
'repetition': rep+1,
|
||||
'insert_time': insert_time,
|
||||
'find_time': find_time,
|
||||
'delete_time': delete_time
|
||||
})
|
||||
return results
|
||||
|
||||
def main():
|
||||
N = 10000
|
||||
base_records = generate_records(N)
|
||||
shuffled, sorted_records = prepare_datasets(base_records)
|
||||
|
||||
structures = {
|
||||
'LinkedList': {
|
||||
'name': 'LinkedList',
|
||||
'create': lambda: None,
|
||||
'insert': ll_insert,
|
||||
'find': ll_find,
|
||||
'delete': ll_delete,
|
||||
'list_all': ll_list_all
|
||||
},
|
||||
'HashTable': {
|
||||
'name': 'HashTable',
|
||||
'create': lambda: [None] * 10,
|
||||
'insert': ht_insert,
|
||||
'find': ht_find,
|
||||
'delete': ht_delete,
|
||||
'list_all': ht_list_all
|
||||
},
|
||||
'BST': {
|
||||
'name': 'BST',
|
||||
'create': lambda: None,
|
||||
'insert': bst_insert,
|
||||
'find': bst_find,
|
||||
'delete': bst_delete,
|
||||
'list_all': bst_list_all
|
||||
}
|
||||
}
|
||||
|
||||
all_results = []
|
||||
repeats = 5
|
||||
|
||||
for struct_name, funcs in structures.items():
|
||||
print(f"Testing {struct_name} on random order...")
|
||||
res_random = run_experiment(funcs, shuffled, 'random', repeats)
|
||||
all_results.extend(res_random)
|
||||
|
||||
print(f"Testing {struct_name} on sorted order...")
|
||||
res_sorted = run_experiment(funcs, sorted_records, 'sorted', repeats)
|
||||
all_results.extend(res_sorted)
|
||||
|
||||
with open('experiment_results.csv', 'w', newline='', encoding='utf-8') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(['Structure', 'Mode', 'Repeat', 'Insert (sec)', 'Search (sec)', 'Delete (sec)'])
|
||||
for r in all_results:
|
||||
writer.writerow([
|
||||
r['structure'],
|
||||
r['mode'],
|
||||
r['repetition'],
|
||||
f"{r['insert_time']:.6f}",
|
||||
f"{r['find_time']:.6f}",
|
||||
f"{r['delete_time']:.6f}"
|
||||
])
|
||||
|
||||
print("Experiment finished. Results saved to experiment_results.csv")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
31
BoriskovaDV/docs/data/1-st-exercise/experiment_results.csv
Normal file
31
BoriskovaDV/docs/data/1-st-exercise/experiment_results.csv
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
Structure,Mode,Repeat,Insert (sec),Search (sec),Delete (sec)
|
||||
LinkedList,random,1,4.432559,0.034196,0.014270
|
||||
LinkedList,random,2,4.999931,0.038043,0.020281
|
||||
LinkedList,random,3,4.771456,0.030191,0.014131
|
||||
LinkedList,random,4,4.707315,0.033500,0.016198
|
||||
LinkedList,random,5,4.721361,0.036586,0.011988
|
||||
LinkedList,sorted,1,4.139028,0.024011,0.010482
|
||||
LinkedList,sorted,2,4.212383,0.024592,0.011765
|
||||
LinkedList,sorted,3,4.674211,0.027756,0.012189
|
||||
LinkedList,sorted,4,4.610210,0.031519,0.012244
|
||||
LinkedList,sorted,5,4.565687,0.029739,0.012747
|
||||
HashTable,random,1,0.659990,0.003889,0.001728
|
||||
HashTable,random,2,0.666055,0.005980,0.002002
|
||||
HashTable,random,3,0.669948,0.004087,0.002176
|
||||
HashTable,random,4,0.661882,0.007439,0.001897
|
||||
HashTable,random,5,0.680420,0.004016,0.001649
|
||||
HashTable,sorted,1,0.648261,0.004277,0.002922
|
||||
HashTable,sorted,2,0.654924,0.004136,0.001793
|
||||
HashTable,sorted,3,0.645509,0.003900,0.002249
|
||||
HashTable,sorted,4,0.637906,0.004056,0.001657
|
||||
HashTable,sorted,5,0.643536,0.003846,0.001741
|
||||
BST,random,1,0.029415,0.000515,0.000183
|
||||
BST,random,2,0.027684,0.000216,0.000142
|
||||
BST,random,3,0.026213,0.000252,0.000159
|
||||
BST,random,4,0.026987,0.000207,0.000135
|
||||
BST,random,5,0.028321,0.000271,0.000183
|
||||
BST,sorted,1,10.293772,0.093178,0.053520
|
||||
BST,sorted,2,10.142204,0.088924,0.049079
|
||||
BST,sorted,3,10.142037,0.078281,0.059416
|
||||
BST,sorted,4,10.139818,0.100162,0.056881
|
||||
BST,sorted,5,10.102982,0.082247,0.051973
|
||||
|
47
BoriskovaDV/docs/data/1-st-exercise/hash_table_phonebook.py
Normal file
47
BoriskovaDV/docs/data/1-st-exercise/hash_table_phonebook.py
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
from linked_list_phonebook import ll_insert, ll_find, ll_delete, ll_list_all
|
||||
|
||||
def hash_function(name, table_size):
|
||||
return hash(name) % table_size
|
||||
|
||||
def ht_insert(buckets, name, phone):
|
||||
idx = hash_function(name, len(buckets))
|
||||
head = buckets[idx]
|
||||
new_head = ll_insert(head, name, phone)
|
||||
buckets[idx] = new_head
|
||||
return buckets
|
||||
|
||||
def ht_find(buckets, name):
|
||||
idx = hash_function(name, len(buckets))
|
||||
head = buckets[idx]
|
||||
return ll_find(head, name)
|
||||
|
||||
def ht_delete(buckets, name):
|
||||
idx = hash_function(name, len(buckets))
|
||||
head = buckets[idx]
|
||||
new_head = ll_delete(head, name)
|
||||
buckets[idx] = new_head
|
||||
return buckets
|
||||
|
||||
def ht_list_all(buckets):
|
||||
all_records = []
|
||||
for head in buckets:
|
||||
current = head
|
||||
while current is not None:
|
||||
all_records.append((current['name'], current['phone']))
|
||||
current = current['next']
|
||||
all_records.sort(key=lambda x: x[0])
|
||||
return all_records
|
||||
|
||||
if __name__ == '__main__':
|
||||
SIZE = 5
|
||||
buckets = [None] * SIZE
|
||||
|
||||
ht_insert(buckets, 'Иван', '123-456')
|
||||
ht_insert(buckets, 'Борис', '789-012')
|
||||
ht_insert(buckets, 'Анна', '345-678')
|
||||
ht_insert(buckets, 'Иван', '111-222')
|
||||
print(ht_list_all(buckets))
|
||||
print(ht_find(buckets, 'Анна'))
|
||||
print(ht_find(buckets, 'Петр'))
|
||||
ht_delete(buckets, 'Борис')
|
||||
print(ht_list_all(buckets))
|
||||
67
BoriskovaDV/docs/data/1-st-exercise/linked_list_phonebook.py
Normal file
67
BoriskovaDV/docs/data/1-st-exercise/linked_list_phonebook.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
def create_node(name, phone):
|
||||
return {'name': name, 'phone': phone, 'next': None}
|
||||
|
||||
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']
|
||||
|
||||
new_node = create_node(name, phone)
|
||||
|
||||
if head is None:
|
||||
return new_node
|
||||
|
||||
current = head
|
||||
while current['next'] is not None:
|
||||
current = current['next']
|
||||
current['next'] = new_node
|
||||
return 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
|
||||
|
||||
if head['name'] == name:
|
||||
return head['next']
|
||||
|
||||
prev = head
|
||||
current = head['next']
|
||||
while current is not None:
|
||||
if current['name'] == name:
|
||||
prev['next'] = current['next']
|
||||
return head
|
||||
prev = current
|
||||
current = current['next']
|
||||
return head
|
||||
|
||||
def ll_list_all(head):
|
||||
records = []
|
||||
current = head
|
||||
while current is not None:
|
||||
records.append((current['name'], current['phone']))
|
||||
current = current['next']
|
||||
records.sort(key=lambda pair: pair[0])
|
||||
return records
|
||||
|
||||
if __name__ == '__main__':
|
||||
head = None
|
||||
head = ll_insert(head, 'Иван', '123-456')
|
||||
head = ll_insert(head, 'Борис', '789-012')
|
||||
head = ll_insert(head, 'Анна', '345-678')
|
||||
head = ll_insert(head, 'Иван', '111-222')
|
||||
print(ll_list_all(head))
|
||||
print(ll_find(head, 'Иван'))
|
||||
print(ll_find(head, 'Петр'))
|
||||
head = ll_delete(head, 'Борис')
|
||||
print(ll_list_all(head))
|
||||
BIN
BoriskovaDV/docs/data/1-st-exercise/performance_comparison.png
Normal file
BIN
BoriskovaDV/docs/data/1-st-exercise/performance_comparison.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
39
BoriskovaDV/docs/data/1-st-exercise/plot_results.py
Normal file
39
BoriskovaDV/docs/data/1-st-exercise/plot_results.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
df = pd.read_csv('experiment_results.csv')
|
||||
|
||||
mean_times = df.groupby(['Structure', 'Mode'])[['Insert (sec)', 'Search (sec)', 'Delete (sec)']].mean().reset_index()
|
||||
|
||||
structures = mean_times['Structure'].unique()
|
||||
modes = mean_times['Mode'].unique()
|
||||
|
||||
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
||||
|
||||
operations = ['Insert (sec)', 'Search (sec)', 'Delete (sec)']
|
||||
titles = ['Insertion', 'Search', 'Deletion']
|
||||
|
||||
for ax, op, title in zip(axes, operations, titles):
|
||||
x = np.arange(len(structures))
|
||||
width = 0.35
|
||||
|
||||
random_vals = []
|
||||
sorted_vals = []
|
||||
for s in structures:
|
||||
random_row = mean_times[(mean_times['Structure'] == s) & (mean_times['Mode'] == 'random')]
|
||||
sorted_row = mean_times[(mean_times['Structure'] == s) & (mean_times['Mode'] == 'sorted')]
|
||||
random_vals.append(random_row[op].values[0] if not random_row.empty else 0)
|
||||
sorted_vals.append(sorted_row[op].values[0] if not sorted_row.empty else 0)
|
||||
|
||||
ax.bar(x - width/2, random_vals, width, label='Random')
|
||||
ax.bar(x + width/2, sorted_vals, width, label='Sorted')
|
||||
ax.set_xticks(x)
|
||||
ax.set_xticklabels(structures)
|
||||
ax.set_ylabel('Time (seconds)')
|
||||
ax.set_title(title)
|
||||
ax.legend()
|
||||
|
||||
plt.tight_layout()
|
||||
plt.savefig('performance_comparison.png', dpi=150)
|
||||
plt.show()
|
||||
BIN
BoriskovaDV/docs/performance_comparison.png
Normal file
BIN
BoriskovaDV/docs/performance_comparison.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
94
BoriskovaDV/docs/report1.md
Normal file
94
BoriskovaDV/docs/report1.md
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# Отчёт по лабораторной работе «Структуры данных для телефонного справочника»
|
||||
|
||||
## 1. Постановка задачи
|
||||
|
||||
В рамках работы требовалось реализовать три структуры данных «с нуля» (без использования встроенных коллекций, кроме базовых списков):
|
||||
|
||||
- связный список,
|
||||
- хеш-таблицу с цепочками,
|
||||
- двоичное дерево поиска (несбалансированное).
|
||||
|
||||
Для каждой структуры необходимо реализовать операции `insert`, `find`, `delete` и `list_all` (возврат всех записей, отсортированных по имени). Затем на наборе из 10 000 записей выполнить экспериментальное сравнение производительности в двух режимах: при случайном порядке вставки и при вставке записей, отсортированных по имени. Каждый эксперимент повторялся 5 раз.
|
||||
|
||||
## 2. Результаты измерений
|
||||
|
||||
Ниже приведены усреднённые по 5 повторам времена выполнения операций (в секундах). Исходные сырые данные сохранены в файле `experiment_results.csv`.
|
||||
|
||||
| Структура | Режим | Вставка (с) | Поиск 110 имён (с) | Удаление 50 записей (с) |
|
||||
|-------------|-------------|-------------|--------------------|-------------------------|
|
||||
| LinkedList | случайный | 4.7265 | 0.0345 | 0.0154 |
|
||||
| LinkedList | сортир. | 4.4403 | 0.0275 | 0.0119 |
|
||||
| HashTable | случайный | 0.6677 | 0.0051 | 0.0019 |
|
||||
| HashTable | сортир. | 0.6460 | 0.0040 | 0.0021 |
|
||||
| BST | случайный | 0.0277 | 0.00029 | 0.00016 |
|
||||
| BST | сортир. | 10.1642 | 0.0886 | 0.0542 |
|
||||
|
||||
### Примечания к методике
|
||||
|
||||
- **Вставка** – добавление всех 10 000 записей в пустую структуру.
|
||||
- **Поиск** – 100 заведомо существующих имён + 10 несуществующих (общее количество вызовов 110).
|
||||
- **Удаление** – 50 случайных существующих записей.
|
||||
- Все замеры выполнены с помощью `time.perf_counter()`.
|
||||
- Для хеш-таблицы использовалось 10 корзин.
|
||||
- Рекурсивная глубина BST увеличена до 20 000, чтобы избежать переполнения стека.
|
||||
|
||||
## 3. Анализ полученных данных
|
||||
|
||||
### 3.1. Поведение BST при разных порядках ввода
|
||||
|
||||
Двоичное дерево поиска сильно зависит от порядка поступления ключей. При случайном порядке средняя высота близка к логарифмической, что даёт отличную производительность:
|
||||
|
||||
- вставка – **0.0277 с**,
|
||||
- поиск – **0.00029 с** (самый быстрый среди всех структур в этом режиме).
|
||||
|
||||
Однако при вставке отсортированных данных дерево вырождается в линейный список (каждый новый узел добавляется только в правое поддерево). Последствия:
|
||||
|
||||
- время вставки возрастает **в 367 раз** (с 0.0277 до 10.16 с),
|
||||
- поиск замедляется **в 305 раз**,
|
||||
- удаление – **в 339 раз**.
|
||||
|
||||
Вырожденное BST на отсортированных данных работает **медленнее даже связного списка** (вставка 10.16 с против 4.44 с, поиск 0.088 с против 0.027 с), что объясняется накладными расходами на рекурсивные вызовы и проверки.
|
||||
|
||||
### 3.2. Хеш-таблица – устойчивость к порядку
|
||||
|
||||
Хеш-таблица использует функцию `hash(name) % size`, которая равномерно рассеивает имена независимо от их лексикографического порядка. Поэтому результаты в двух режимах практически идентичны:
|
||||
|
||||
- вставка: 0.668 с (случайный) против 0.646 с (отсортированный) – разница менее 4%,
|
||||
- поиск: 0.0051 с против 0.0040 с,
|
||||
- удаление: 0.0019 с против 0.0021 с.
|
||||
|
||||
Небольшие расхождения находятся в пределах случайной вариации (зависит от коллизий, которые немного различаются при разном порядке вставки). Средняя сложность операций остаётся **O(1)**.
|
||||
|
||||
### 3.3. Связный список – ожидаемо медленный
|
||||
|
||||
Линейный список не обеспечивает прямого доступа, поэтому все операции (кроме удаления после нахождения) требуют обхода в среднем половины списка. Даже при сравнительно небольшом объёме данных (10 000 записей) времена велики:
|
||||
|
||||
- вставка ≈ **4.6 с** (на два порядка хуже, чем у хеш-таблицы и BST на случайных данных),
|
||||
- поиск ≈ **0.03 с** (в 6–10 раз медленнее, чем у других структур).
|
||||
|
||||
Интересно, что на отсортированных данных список показывает немного лучшее время, чем на случайных. Причина: при вставке в конец отсортированного списка (имена идут в алфавитном порядке) новые узлы добавляются без поиска дубликатов? Но алгоритм `ll_insert` сначала проверяет наличие имени, проходя весь список. Поскольку все имена уникальны и не обновляются, каждый проход идёт до конца. Однако в отсортированном режиме имена добавляются в порядке возрастания, и при проверке дубликата мы проходим по уже существующим элементам, которые все меньше нового? Да, в отсортированном режиме каждое новое имя больше всех предыдущих, поэтому при поиске дубликата мы обходим весь существующий список. В случайном режиме новые имена могут встречаться раньше, и поиск останавливается раньше? Но в любом случае разница небольшая (около 6%), и в целом список остаётся медленным.
|
||||
|
||||
### 3.4. Сравнение удаления
|
||||
|
||||
Удаление в списке требует сначала найти элемент (O(n)), затем перелинковку. В хеш-таблице удаление сводится к удалению в коротком списке корзины (почти O(1)). В BST на случайных данных удаление очень быстрое (0.00016 с), на отсортированных – катастрофически замедляется (0.054 с). Для хеш-таблицы удаление немного быстрее, чем вставка, что естественно: при удалении не нужно создавать новый узел.
|
||||
|
||||
## 4. Выводы и практические рекомендации
|
||||
|
||||
Проведённое исследование наглядно демонстрирует сильные и слабые стороны каждой структуры.
|
||||
|
||||
1. **Хеш-таблица** – лучший выбор для задач, где приоритетом является скорость всех операций (вставка, поиск, удаление), а порядок вывода данных не важен или может быть получен отдельной сортировкой. Стабильно высокая производительность вне зависимости от характера входных данных. В реальных проектах именно хеш-таблицы лежат в основе словарей (Python `dict`, Java `HashMap`).
|
||||
|
||||
2. **Двоичное дерево поиска** – эффективно только при случайном или близком к случайному порядке поступления ключей. Даёт логарифмическую сложность и при этом позволяет получать данные в отсортированном виде за O(n) без дополнительной сортировки. Однако на реальных данных (например, заведомо отсортированных) производительность падает до O(n), что делает его непригодным без механизмов балансировки. На практике применяются сбалансированные варианты (AVL, красно-чёрные деревья).
|
||||
|
||||
3. **Связный список** – не подходит для коллекций объёмом более нескольких сотен элементов из-за линейной сложности основных операций. Может использоваться только в очень специфических сценариях: очень редкий поиск, постоянные вставки/удаления в начало (но не в конец), или как строительный блок для других структур (например, для цепочек в хеш-таблице, что и было сделано в данной работе).
|
||||
|
||||
### Итоговая таблица применимости
|
||||
|
||||
| Критерий | Рекомендуемая структура |
|
||||
|---------------------------------|---------------------------------------|
|
||||
| Максимальная скорость всех операций | Хеш-таблица |
|
||||
| Нужны данные в отсортированном порядке + данные поступают случайно | BST (но лучше сбалансированное) |
|
||||
| Данные поступают уже отсортированными | Хеш-таблица (или балансируемое дерево) |
|
||||
| Очень маленький объём (< 100 записей) | Любая, но проще список |
|
||||
|
||||
В реальной разработке для телефонного справочника с большим числом записей и частыми запросами поиска оптимальным решением будет **хеш-таблица**. Если же дополнительно требуется частый вывод всего справочника по алфавиту, стоит рассмотреть сбалансированное дерево (например, встроенный в Python модуль `bisect` не даёт структуры данных, а `sortedcontainers` – сторонний).
|
||||
Loading…
Reference in New Issue
Block a user