forked from UNN/2026-rff_mp
Merge pull request '[1] lab1' (#190) from shalovsa/2026-rff_mp:lab1 into develop
Reviewed-on: UNN/2026-rff_mp#190
This commit is contained in:
commit
3b1c21a8b1
BIN
shalovsa/lab1/docs/data/comparison_by_operation.png
Normal file
BIN
shalovsa/lab1/docs/data/comparison_by_operation.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 97 KiB |
210
shalovsa/lab1/docs/data/laba.py
Normal file
210
shalovsa/lab1/docs/data/laba.py
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
import time
|
||||
import random
|
||||
import csv
|
||||
import os
|
||||
|
||||
from phone_book import (
|
||||
ll_insert, ll_find, ll_delete, ll_list_all,
|
||||
ht_make, ht_insert, ht_find, ht_delete, ht_list_all,
|
||||
bst_insert, bst_find, bst_delete, bst_list_all,
|
||||
)
|
||||
|
||||
N = 10_000
|
||||
REPEATS = 5
|
||||
SEARCH_COUNT = 110
|
||||
DELETE_COUNT = 50
|
||||
HT_SIZE = 256
|
||||
|
||||
RANDOM_SEED = 42
|
||||
random.seed(RANDOM_SEED)
|
||||
|
||||
OUTPUT_DIR = os.path.dirname(__file__)
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
CSV_PATH = os.path.join(OUTPUT_DIR, 'results.csv')
|
||||
|
||||
def generate_records(n):
|
||||
records = [(f"User_{i:05d}", f"+7{random.randint(1000000000, 9999999999)}")
|
||||
for i in range(n)]
|
||||
return records
|
||||
|
||||
|
||||
records_base = generate_records(N)
|
||||
|
||||
records_shuffled = records_base[:]
|
||||
random.shuffle(records_shuffled)
|
||||
|
||||
records_sorted = sorted(records_base, key=lambda x: x[0])
|
||||
|
||||
existing_names = [r[0] for r in random.sample(records_base, 100)]
|
||||
missing_names = [f"None_{i}" for i in range(10)]
|
||||
search_names = existing_names + missing_names
|
||||
|
||||
delete_names = [r[0] for r in random.sample(records_base, DELETE_COUNT)]
|
||||
|
||||
def measure(func, *args, **kwargs):
|
||||
start = time.perf_counter()
|
||||
result = func(*args, **kwargs)
|
||||
end = time.perf_counter()
|
||||
return end - start, result
|
||||
|
||||
def bench_linked_list(records, mode_label):
|
||||
times = {'insert': [], 'find': [], 'delete': []}
|
||||
|
||||
for _ in range(REPEATS):
|
||||
head = None
|
||||
t_start = time.perf_counter()
|
||||
for name, phone in records:
|
||||
head = ll_insert(head, name, phone)
|
||||
times['insert'].append(time.perf_counter() - t_start)
|
||||
|
||||
t_start = time.perf_counter()
|
||||
for name in search_names:
|
||||
ll_find(head, name)
|
||||
times['find'].append(time.perf_counter() - t_start)
|
||||
|
||||
t_start = time.perf_counter()
|
||||
for name in delete_names:
|
||||
head = ll_delete(head, name)
|
||||
times['delete'].append(time.perf_counter() - t_start)
|
||||
|
||||
return times
|
||||
|
||||
|
||||
def bench_hash_table(records, mode_label):
|
||||
times = {'insert': [], 'find': [], 'delete': []}
|
||||
|
||||
for _ in range(REPEATS):
|
||||
buckets = ht_make(HT_SIZE)
|
||||
t_start = time.perf_counter()
|
||||
for name, phone in records:
|
||||
ht_insert(buckets, name, phone)
|
||||
times['insert'].append(time.perf_counter() - t_start)
|
||||
|
||||
t_start = time.perf_counter()
|
||||
for name in search_names:
|
||||
ht_find(buckets, name)
|
||||
times['find'].append(time.perf_counter() - t_start)
|
||||
|
||||
t_start = time.perf_counter()
|
||||
for name in delete_names:
|
||||
ht_delete(buckets, name)
|
||||
times['delete'].append(time.perf_counter() - t_start)
|
||||
|
||||
return times
|
||||
|
||||
|
||||
def bench_bst(records, mode_label):
|
||||
times = {'insert': [], 'find': [], 'delete': []}
|
||||
|
||||
for _ in range(REPEATS):
|
||||
root = None
|
||||
t_start = time.perf_counter()
|
||||
for name, phone in records:
|
||||
root = bst_insert(root, name, phone)
|
||||
times['insert'].append(time.perf_counter() - t_start)
|
||||
|
||||
t_start = time.perf_counter()
|
||||
for name in search_names:
|
||||
bst_find(root, name)
|
||||
times['find'].append(time.perf_counter() - t_start)
|
||||
|
||||
t_start = time.perf_counter()
|
||||
for name in delete_names:
|
||||
root = bst_delete(root, name)
|
||||
times['delete'].append(time.perf_counter() - t_start)
|
||||
|
||||
return times
|
||||
|
||||
def avg(lst):
|
||||
return sum(lst) / len(lst)
|
||||
|
||||
|
||||
def run_all():
|
||||
print(f"Запуск: N={N}, повторений={REPEATS}\n")
|
||||
print(f"{'Структура':<15} {'Режим':<12} {'Операция':<10} "
|
||||
f"{'Среднее (с)':<14} {'Все замеры'}")
|
||||
print("-" * 80)
|
||||
|
||||
all_results = [["Структура", "Режим", "Операция", "Среднее (с)"] +
|
||||
[f"Замер_{i+1}" for i in range(REPEATS)]]
|
||||
|
||||
datasets = [
|
||||
(records_shuffled, "случайный"),
|
||||
(records_sorted, "сортированный"),
|
||||
]
|
||||
|
||||
benchmarks = [
|
||||
("LinkedList", bench_linked_list),
|
||||
("HashTable", bench_hash_table),
|
||||
("BST", bench_bst),
|
||||
]
|
||||
|
||||
for ds_records, ds_mode in datasets:
|
||||
for struct_name, bench_func in benchmarks:
|
||||
print(f"\n [{struct_name}] режим: {ds_mode}")
|
||||
if struct_name == "BST" and ds_mode == "сортированный":
|
||||
import sys
|
||||
sys.setrecursionlimit(50_000)
|
||||
|
||||
times = bench_func(ds_records, ds_mode)
|
||||
|
||||
for op, op_times in times.items():
|
||||
mean = avg(op_times)
|
||||
row = [struct_name, ds_mode, op, f"{mean:.6f}"] + \
|
||||
[f"{t:.6f}" for t in op_times]
|
||||
all_results.append(row)
|
||||
|
||||
print(f" {op:<10} среднее={mean:.6f}с "
|
||||
f"замеры={[f'{t:.4f}' for t in op_times]}")
|
||||
|
||||
with open(CSV_PATH, 'w', newline='', encoding='utf-8') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerows(all_results)
|
||||
|
||||
print(f"\n✅ Результаты сохранены в: {CSV_PATH}")
|
||||
return all_results
|
||||
|
||||
def smoke_test():
|
||||
print("=== Smoke Test ===\n")
|
||||
|
||||
test_data = [("Alice", "111"), ("Bob", "222"), ("Charlie", "333")]
|
||||
|
||||
head = None
|
||||
for name, phone in test_data:
|
||||
head = ll_insert(head, name, phone)
|
||||
assert ll_find(head, "Alice") == "111"
|
||||
assert ll_find(head, "Bob") == "222"
|
||||
assert ll_find(head, "Nobody") is None
|
||||
head = ll_delete(head, "Bob")
|
||||
assert ll_find(head, "Bob") is None
|
||||
sorted_ll = ll_list_all(head)
|
||||
assert sorted_ll == [("Alice", "111"), ("Charlie", "333")]
|
||||
print("✅ LinkedList — OK")
|
||||
|
||||
buckets = ht_make(16)
|
||||
for name, phone in test_data:
|
||||
ht_insert(buckets, name, phone)
|
||||
assert ht_find(buckets, "Charlie") == "333"
|
||||
assert ht_find(buckets, "Nobody") is None
|
||||
ht_delete(buckets, "Alice")
|
||||
assert ht_find(buckets, "Alice") is None
|
||||
sorted_ht = ht_list_all(buckets)
|
||||
assert sorted_ht == [("Bob", "222"), ("Charlie", "333")]
|
||||
print("✅ HashTable — OK")
|
||||
|
||||
root = None
|
||||
for name, phone in test_data:
|
||||
root = bst_insert(root, name, phone)
|
||||
assert bst_find(root, "Alice") == "111"
|
||||
assert bst_find(root, "Nobody") is None
|
||||
root = bst_delete(root, "Alice")
|
||||
assert bst_find(root, "Alice") is None
|
||||
sorted_bst = bst_list_all(root)
|
||||
assert sorted_bst == [("Bob", "222"), ("Charlie", "333")]
|
||||
print("✅ BST — OK")
|
||||
|
||||
print("\nВсе тесты пройдены!\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
smoke_test()
|
||||
results = run_all()
|
||||
168
shalovsa/lab1/docs/data/phone_book.py
Normal file
168
shalovsa/lab1/docs/data/phone_book.py
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
def ll_make_node(name, phone):
|
||||
return {'name': name, 'phone': phone, 'next': None}
|
||||
|
||||
|
||||
def ll_insert(head, name, phone):
|
||||
if head is None:
|
||||
return ll_make_node(name, phone)
|
||||
|
||||
current = head
|
||||
while current is not None:
|
||||
if current['name'] == name:
|
||||
current['phone'] = phone
|
||||
return head
|
||||
current = current['next']
|
||||
|
||||
new_node = ll_make_node(name, phone)
|
||||
new_node['next'] = head
|
||||
return new_node
|
||||
|
||||
|
||||
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']
|
||||
|
||||
current = head
|
||||
while current['next'] is not None:
|
||||
if current['next']['name'] == name:
|
||||
current['next'] = current['next']['next']
|
||||
return head
|
||||
current = current['next']
|
||||
|
||||
return head
|
||||
|
||||
|
||||
def ll_list_all(head):
|
||||
result = []
|
||||
current = head
|
||||
while current is not None:
|
||||
result.append((current['name'], current['phone']))
|
||||
current = current['next']
|
||||
return sorted(result, key=lambda x: x[0])
|
||||
|
||||
def ht_make(size=256):
|
||||
return [None] * size
|
||||
|
||||
|
||||
def ht_hash(buckets, name):
|
||||
return hash(name) % len(buckets)
|
||||
|
||||
|
||||
def ht_insert(buckets, name, phone):
|
||||
idx = ht_hash(buckets, name)
|
||||
buckets[idx] = ll_insert(buckets[idx], name, phone)
|
||||
|
||||
|
||||
def ht_find(buckets, name):
|
||||
idx = ht_hash(buckets, name)
|
||||
return ll_find(buckets[idx], name)
|
||||
|
||||
|
||||
def ht_delete(buckets, name):
|
||||
idx = ht_hash(buckets, name)
|
||||
buckets[idx] = ll_delete(buckets[idx], name)
|
||||
|
||||
|
||||
def ht_list_all(buckets):
|
||||
result = []
|
||||
for bucket_head in buckets:
|
||||
current = bucket_head
|
||||
while current is not None:
|
||||
result.append((current['name'], current['phone']))
|
||||
current = current['next']
|
||||
return sorted(result, key=lambda x: x[0])
|
||||
|
||||
def bst_make_node(name, phone):
|
||||
return {'name': name, 'phone': phone, 'left': None, 'right': None}
|
||||
|
||||
|
||||
def bst_insert(root, name, phone):
|
||||
new_node = bst_make_node(name, phone)
|
||||
|
||||
if root is None:
|
||||
return new_node
|
||||
|
||||
current = root
|
||||
while True:
|
||||
if name == current['name']:
|
||||
current['phone'] = phone
|
||||
return root
|
||||
elif name < current['name']:
|
||||
if current['left'] is None:
|
||||
current['left'] = new_node
|
||||
return root
|
||||
current = current['left']
|
||||
else:
|
||||
if current['right'] is None:
|
||||
current['right'] = new_node
|
||||
return root
|
||||
current = current['right']
|
||||
|
||||
|
||||
def bst_find(root, name):
|
||||
current = root
|
||||
while current is not None:
|
||||
if name == current['name']:
|
||||
return current['phone']
|
||||
elif name < current['name']:
|
||||
current = current['left']
|
||||
else:
|
||||
current = current['right']
|
||||
return None
|
||||
|
||||
|
||||
def _bst_min_node(node):
|
||||
current = node
|
||||
while current['left'] is not None:
|
||||
current = current['left']
|
||||
return current
|
||||
|
||||
|
||||
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']
|
||||
elif root['right'] is None:
|
||||
return root['left']
|
||||
else:
|
||||
successor = _bst_min_node(root['right'])
|
||||
root['name'] = successor['name']
|
||||
root['phone'] = successor['phone']
|
||||
root['right'] = bst_delete(root['right'], successor['name'])
|
||||
|
||||
return root
|
||||
|
||||
|
||||
def bst_list_all(root):
|
||||
result = []
|
||||
stack = []
|
||||
current = root
|
||||
|
||||
while current is not None or stack:
|
||||
while current is not None:
|
||||
stack.append(current)
|
||||
current = current['left']
|
||||
current = stack.pop()
|
||||
result.append((current['name'], current['phone']))
|
||||
current = current['right']
|
||||
|
||||
return result
|
||||
128
shalovsa/lab1/docs/data/plot_results.py
Normal file
128
shalovsa/lab1/docs/data/plot_results.py
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
import csv
|
||||
import os
|
||||
|
||||
try:
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.patches as mpatches
|
||||
HAS_MPL = True
|
||||
except ImportError:
|
||||
HAS_MPL = False
|
||||
print("⚠️ matplotlib не установлен. Установите: pip install matplotlib")
|
||||
print(" Графики будут пропущены, таблица результатов выведена в терминал.\n")
|
||||
|
||||
CSV_PATH = os.path.join(os.path.dirname(__file__), 'results.csv')
|
||||
PLOTS_DIR = os.path.dirname(__file__)
|
||||
|
||||
|
||||
def load_results(path):
|
||||
data = {}
|
||||
with open(path, newline='', encoding='utf-8') as f:
|
||||
reader = csv.reader(f)
|
||||
header = next(reader)
|
||||
for row in reader:
|
||||
struct, mode, op = row[0], row[1], row[2]
|
||||
mean = float(row[3])
|
||||
data[(struct, mode, op)] = mean
|
||||
return data
|
||||
|
||||
STRUCTS = ["LinkedList", "HashTable", "BST"]
|
||||
MODES = ["случайный", "сортированный"]
|
||||
OPS = ["insert", "find", "delete"]
|
||||
COLORS = {"LinkedList": "#4E9AF1", "HashTable": "#F4845F", "BST": "#6BCB77"}
|
||||
|
||||
|
||||
def plot_by_operation(data):
|
||||
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
||||
fig.suptitle("Сравнение структур данных\n(телефонный справочник, N=10 000)",
|
||||
fontsize=14, fontweight='bold')
|
||||
|
||||
for ax, op in zip(axes, OPS):
|
||||
x_labels = []
|
||||
values = []
|
||||
colors = []
|
||||
|
||||
for mode in MODES:
|
||||
for struct in STRUCTS:
|
||||
key = (struct, mode, op)
|
||||
val = data.get(key, 0)
|
||||
x_labels.append(f"{struct}\n({mode[:4]})")
|
||||
values.append(val)
|
||||
colors.append(COLORS[struct])
|
||||
|
||||
bars = ax.bar(range(len(values)), values, color=colors,
|
||||
edgecolor='white', linewidth=0.8)
|
||||
|
||||
ax.set_xticks(range(len(x_labels)))
|
||||
ax.set_xticklabels(x_labels, fontsize=8, rotation=15, ha='right')
|
||||
ax.set_ylabel("Время (с)", fontsize=9)
|
||||
ax.set_title(f"Операция: {op}", fontweight='bold')
|
||||
ax.grid(axis='y', alpha=0.3)
|
||||
|
||||
for bar, val in zip(bars, values):
|
||||
ax.text(bar.get_x() + bar.get_width() / 2,
|
||||
bar.get_height() + max(values) * 0.01,
|
||||
f"{val:.4f}",
|
||||
ha='center', va='bottom', fontsize=7)
|
||||
|
||||
patches = [mpatches.Patch(color=c, label=s) for s, c in COLORS.items()]
|
||||
fig.legend(handles=patches, loc='lower center', ncol=3,
|
||||
bbox_to_anchor=(0.5, -0.05))
|
||||
|
||||
plt.tight_layout()
|
||||
out_path = os.path.join(PLOTS_DIR, 'comparison_by_operation.png')
|
||||
plt.savefig(out_path, dpi=150, bbox_inches='tight')
|
||||
print(f"✅ График сохранён: {out_path}")
|
||||
plt.show()
|
||||
|
||||
|
||||
def plot_sorted_vs_random(data):
|
||||
fig, axes = plt.subplots(1, 3, figsize=(14, 5))
|
||||
fig.suptitle("Влияние порядка данных на время операций",
|
||||
fontsize=13, fontweight='bold')
|
||||
|
||||
for ax, struct in zip(axes, STRUCTS):
|
||||
rand_vals = [data.get((struct, "случайный", op), 0) for op in OPS]
|
||||
sort_vals = [data.get((struct, "сортированный", op), 0) for op in OPS]
|
||||
|
||||
x = range(len(OPS))
|
||||
w = 0.35
|
||||
bars1 = ax.bar([i - w/2 for i in x], rand_vals, width=w,
|
||||
label="случайный", color="#4E9AF1", edgecolor='white')
|
||||
bars2 = ax.bar([i + w/2 for i in x], sort_vals, width=w,
|
||||
label="сортированный", color="#F4845F", edgecolor='white')
|
||||
|
||||
ax.set_xticks(list(x))
|
||||
ax.set_xticklabels(OPS)
|
||||
ax.set_title(struct, fontweight='bold')
|
||||
ax.set_ylabel("Время (с)", fontsize=9)
|
||||
ax.legend(fontsize=8)
|
||||
ax.grid(axis='y', alpha=0.3)
|
||||
|
||||
plt.tight_layout()
|
||||
out_path = os.path.join(PLOTS_DIR, 'sorted_vs_random.png')
|
||||
plt.savefig(out_path, dpi=150, bbox_inches='tight')
|
||||
print(f"✅ График сохранён: {out_path}")
|
||||
plt.show()
|
||||
|
||||
|
||||
def print_table(data):
|
||||
print(f"\n{'Структура':<12} {'Режим':<16} {'Операция':<10} {'Время (с)':<12}")
|
||||
print("-" * 52)
|
||||
for (struct, mode, op), mean in sorted(data.items()):
|
||||
print(f"{struct:<12} {mode:<16} {op:<10} {mean:.6f}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if not os.path.exists(CSV_PATH):
|
||||
print(f"❌ Файл результатов не найден: {CSV_PATH}")
|
||||
print(" Сначала запустите: python benchmark.py")
|
||||
exit(1)
|
||||
|
||||
data = load_results(CSV_PATH)
|
||||
print_table(data)
|
||||
|
||||
if HAS_MPL:
|
||||
plot_by_operation(data)
|
||||
plot_sorted_vs_random(data)
|
||||
else:
|
||||
print("\n💡 Установите matplotlib для графиков:")
|
||||
print(" pip install matplotlib")
|
||||
19
shalovsa/lab1/docs/data/results.csv
Normal file
19
shalovsa/lab1/docs/data/results.csv
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
Структура,Режим,Операция,Среднее (с),Замер_1,Замер_2,Замер_3,Замер_4,Замер_5
|
||||
LinkedList,случайный,insert,2.013381,2.030341,1.985393,2.000117,2.000130,2.050923
|
||||
LinkedList,случайный,find,0.026258,0.026263,0.027186,0.026271,0.026350,0.025217
|
||||
LinkedList,случайный,delete,0.015552,0.017207,0.014387,0.015304,0.015317,0.015547
|
||||
HashTable,случайный,insert,0.014448,0.014376,0.014608,0.015115,0.013931,0.014212
|
||||
HashTable,случайный,find,0.000161,0.000162,0.000161,0.000161,0.000158,0.000161
|
||||
HashTable,случайный,delete,0.000088,0.000089,0.000089,0.000088,0.000086,0.000086
|
||||
BST,случайный,insert,0.015575,0.015822,0.015653,0.015507,0.015398,0.015496
|
||||
BST,случайный,find,0.000133,0.000135,0.000133,0.000131,0.000133,0.000133
|
||||
BST,случайный,delete,0.000100,0.000104,0.000100,0.000100,0.000099,0.000099
|
||||
LinkedList,сортированный,insert,1.890415,1.937600,1.916341,1.863388,1.872563,1.862181
|
||||
LinkedList,сортированный,find,0.023136,0.030373,0.021794,0.021670,0.020861,0.020980
|
||||
LinkedList,сортированный,delete,0.014986,0.017694,0.014155,0.014365,0.014293,0.014424
|
||||
HashTable,сортированный,insert,0.017723,0.015734,0.019191,0.019721,0.015843,0.018128
|
||||
HashTable,сортированный,find,0.000223,0.000263,0.000206,0.000226,0.000154,0.000264
|
||||
HashTable,сортированный,delete,0.000125,0.000144,0.000112,0.000132,0.000094,0.000141
|
||||
BST,сортированный,insert,3.535999,3.577140,3.570114,3.581931,3.485064,3.465746
|
||||
BST,сортированный,find,0.030666,0.030318,0.033654,0.030108,0.029537,0.029713
|
||||
BST,сортированный,delete,0.038312,0.037349,0.040109,0.037735,0.036555,0.039810
|
||||
|
BIN
shalovsa/lab1/docs/data/sorted_vs_random.png
Normal file
BIN
shalovsa/lab1/docs/data/sorted_vs_random.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
130
shalovsa/lab1/docs/report.md
Normal file
130
shalovsa/lab1/docs/report.md
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
# Отчёт: Задание 1 — Структуры данных
|
||||
|
||||
## Цель работы
|
||||
|
||||
Разработать три структуры данных «с нуля» в процедурном стиле (без ООП), применить их для хранения записей телефонной книги и провести экспериментальное сравнение производительности ключевых операций.
|
||||
|
||||
**Структуры данных:**
|
||||
- Связный список (LinkedList)
|
||||
- Хеш-таблица (HashTable)
|
||||
- Двоичное дерево поиска (BST)
|
||||
|
||||
---
|
||||
|
||||
## Реализация
|
||||
|
||||
### Основные технические решения
|
||||
|
||||
#### 1. Связный список
|
||||
|
||||
Узел реализован как Python-словарь: `{'name': 'Имя', 'phone': '123', 'next': None}`.
|
||||
|
||||
Новые элементы добавляются **в начало** списка за O(1) (при условии отсутствия имени), обновление требует прохода по списку O(n). Поиск и удаление работают за линейное время из-за отсутствия прямого доступа по индексу.
|
||||
|
||||
#### 2. Хеш-таблица
|
||||
|
||||
Фиксированный массив на 256 корзин. Каждая корзина — указатель на связный список (метод цепочек). Хеш-функция: стандартный `hash(name) % size`. Среднее время операций O(1), при коллизиях — O(k), где k — длина цепочки.
|
||||
|
||||
#### 3. Двоичное дерево поиска (BST)
|
||||
|
||||
Узел: `{'name': 'Имя', 'phone': '123', 'left': None, 'right': None}`. Сравнение ключей — лексикографическое по полю name. Вставка и поиск реализованы итеративно. Удаление — рекурсивное с заменой на минимальный узел правого поддерева. Обход в глубину даёт отсортированный список.
|
||||
|
||||
---
|
||||
|
||||
## Экспериментальная часть
|
||||
|
||||
### Условия проведения замеров
|
||||
|
||||
| Параметр | Значение |
|
||||
|---|---|
|
||||
| Количество записей (N) | 10 000 |
|
||||
| Количество замеров на операцию | 5 |
|
||||
| Поисковых запросов | 110 (100 существующих + 10 отсутствующих) |
|
||||
| Удалений | 50 |
|
||||
| Размер хеш-таблицы | 256 корзин |
|
||||
|
||||
**Два набора данных:**
|
||||
- `records_shuffled` — случайный порядок записей
|
||||
- `records_sorted` — упорядоченный по имени (алфавитный порядок)
|
||||
|
||||
---
|
||||
|
||||
## Результаты
|
||||
|
||||
### Среднее время выполнения (секунды)
|
||||
|
||||
| Структура | Режим | Вставка (с) | Поиск 110 (с) | Удаление 50 (с) |
|
||||
|---|---|---|---|---|
|
||||
| LinkedList | случайный | 2.541985 | 0.034289 | 0.020349 |
|
||||
| LinkedList | сортированный | 2.208557 | 0.025340 | 0.016424 |
|
||||
| HashTable | случайный | 0.018235 | 0.000214 | 0.000120 |
|
||||
| HashTable | сортированный | 0.016163 | 0.000207 | 0.000124 |
|
||||
| BST | случайный | 0.017192 | 0.000145 | 0.000104 |
|
||||
| **BST** | **сортированный** | **3.854338** | **0.033498** | **0.045823** |
|
||||
|
||||
### Визуализация
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Анализ результатов
|
||||
|
||||
### 1. Связный список — стабильно низкая скорость
|
||||
|
||||
Вставка требует **~2.5 секунд** на 10 000 элементов, поскольку каждая операция при наличии дубликатов имени вынуждена сканировать весь список O(n). При случайных уникальных именах вставка выполняется в начало за O(1), но **поиск** всё равно линейный.
|
||||
|
||||
**Вывод:** связный список непригоден для частого поиска в больших объёмах данных, но удобен как вспомогательный элемент (например, для цепочек в хеш-таблице).
|
||||
|
||||
### 2. Хеш-таблица — устойчивость к порядку входных данных
|
||||
|
||||
Хеш-таблица продемонстрировала **практически идентичные результаты** в обоих режимах:
|
||||
- Вставка: ~0.017 с (быстрее списка в ~150 раз)
|
||||
- Поиск: ~0.0002 с (быстрее списка в ~160 раз)
|
||||
|
||||
Хеш-функция равномерно распределяет ключи независимо от порядка их поступления, поэтому производительность остаётся стабильной.
|
||||
|
||||
### 3. BST катастрофически деградирует на упорядоченных данных
|
||||
|
||||
Наиболее показательный результат эксперимента:
|
||||
|
||||
| | Случайный | Сортированный | Ухудшение |
|
||||
|---|---|---|---|
|
||||
| BST insert | 0.017 с | **3.854 с** | **×225** |
|
||||
| BST find | 0.000145 с | **0.033 с** | **×231** |
|
||||
|
||||
**Причина:** при вставке отсортированных данных дерево вырождается в линейный список — каждый новый элемент оказывается больше предыдущего и помещается только в правую ветку. Высота дерева становится O(n) вместо O(log n), что превращает все операции в линейные.
|
||||
|
||||
### 4. Операция delete
|
||||
|
||||
На случайных данных BST удаляет за **~0.0001 с** (логарифмическая сложность). На сортированных — **~0.046 с** (линейная деградация). HashTable показывает стабильные **~0.00012 с** независимо от порядка.
|
||||
|
||||
---
|
||||
|
||||
## Выводы и практические рекомендации
|
||||
|
||||
### Выбор структуры в зависимости от задачи
|
||||
|
||||
| Сценарий | Рекомендация |
|
||||
|---|---|
|
||||
| **Частый поиск по ключу** | HashTable или BST (случайный порядок) |
|
||||
| **Данные поступают упорядоченно** | HashTable (BST непригоден) |
|
||||
| **Требуется отсортированный вывод** | BST (обход даёт порядок за O(n)) |
|
||||
| **Интенсивные вставки/удаления + поиск** | HashTable |
|
||||
| **Ограниченный объём данных, простота** | LinkedList (до сотен элементов) |
|
||||
| **Диапазонные запросы** (например, A–M) | BST |
|
||||
|
||||
### Теоретическая сложность операций
|
||||
|
||||
| Структура | Insert | Find | Delete | Обход (отсорт.) |
|
||||
|---|---|---|---|---|
|
||||
| LinkedList | O(n) | O(n) | O(n) | O(n log n) |
|
||||
| HashTable | O(1) в среднем | O(1) в среднем | O(1) в среднем | 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) |
|
||||
|
||||
### Ключевой вывод
|
||||
|
||||
Для телефонного справочника с частыми поисками и обновлениями оптимальный выбор — **хеш-таблица**. BST выигрывает только при необходимости получать отсортированные данные без дополнительной сортировки, но требует либо случайного порядка вставки, либо использования самобалансирующихся вариантов (AVL, красно-чёрное дерево).
|
||||
Loading…
Reference in New Issue
Block a user