2026-05-24 21:36:56 +00:00
|
|
|
|
# 1. СВЯЗНЫЙ СПИСОК
|
|
|
|
|
|
def ll_create_node(name, phone):
|
|
|
|
|
|
return {'name': name, 'phone': phone, 'next': None}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ll_insert(head, name, phone):
|
|
|
|
|
|
"""Добавить или обновить запись. Возвращает голову списка."""
|
|
|
|
|
|
node = head
|
|
|
|
|
|
while node is not None:
|
|
|
|
|
|
if node['name'] == name:
|
|
|
|
|
|
node['phone'] = phone # обновить
|
|
|
|
|
|
return head
|
|
|
|
|
|
node = node['next']
|
|
|
|
|
|
# Вставка в начало — O(1)
|
|
|
|
|
|
new_node = ll_create_node(name, phone)
|
|
|
|
|
|
new_node['next'] = head
|
|
|
|
|
|
return new_node
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ll_find(head, name):
|
|
|
|
|
|
"""Вернуть телефон или None."""
|
|
|
|
|
|
node = head
|
|
|
|
|
|
while node is not None:
|
|
|
|
|
|
if node['name'] == name:
|
|
|
|
|
|
return node['phone']
|
|
|
|
|
|
node = node['next']
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ll_delete(head, name):
|
|
|
|
|
|
"""Удалить узел, вернуть новую голову."""
|
|
|
|
|
|
if head is None:
|
|
|
|
|
|
return None
|
|
|
|
|
|
if head['name'] == name:
|
|
|
|
|
|
return head['next']
|
|
|
|
|
|
prev, node = head, head['next']
|
|
|
|
|
|
while node is not None:
|
|
|
|
|
|
if node['name'] == name:
|
|
|
|
|
|
prev['next'] = node['next']
|
|
|
|
|
|
return head
|
|
|
|
|
|
prev, node = node, node['next']
|
|
|
|
|
|
return head
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ll_list_all(head):
|
|
|
|
|
|
"""Собрать все записи и вернуть отсортированный список (name, phone)."""
|
|
|
|
|
|
result = []
|
|
|
|
|
|
node = head
|
|
|
|
|
|
while node is not None:
|
|
|
|
|
|
result.append((node['name'], node['phone']))
|
|
|
|
|
|
node = node['next']
|
|
|
|
|
|
result.sort(key=lambda x: x[0])
|
2026-05-24 21:42:24 +00:00
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
# 2. ХЕШ-ТАБЛИЦА (цепочки через связный список)
|
|
|
|
|
|
|
|
|
|
|
|
HT_SIZE = 1024 # число корзин (степень двойки)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ht_create(size=HT_SIZE):
|
|
|
|
|
|
return [None] * size
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _ht_hash(name, size):
|
|
|
|
|
|
h = 5381
|
|
|
|
|
|
for ch in name:
|
|
|
|
|
|
h = ((h << 5) + h) ^ ord(ch)
|
|
|
|
|
|
return h % size
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ht_insert(buckets, name, phone):
|
|
|
|
|
|
idx = _ht_hash(name, len(buckets))
|
|
|
|
|
|
buckets[idx] = ll_insert(buckets[idx], name, phone)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ht_find(buckets, name):
|
|
|
|
|
|
idx = _ht_hash(name, len(buckets))
|
|
|
|
|
|
return ll_find(buckets[idx], name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ht_delete(buckets, name):
|
|
|
|
|
|
idx = _ht_hash(name, len(buckets))
|
|
|
|
|
|
buckets[idx] = ll_delete(buckets[idx], name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ht_list_all(buckets):
|
|
|
|
|
|
result = []
|
|
|
|
|
|
for head in buckets:
|
|
|
|
|
|
result.extend(ll_list_all(head))
|
|
|
|
|
|
result.sort(key=lambda x: x[0])
|
2026-05-24 21:44:07 +00:00
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
# 3. ДВОИЧНОЕ ДЕРЕВО ПОИСКА (BST)
|
|
|
|
|
|
|
|
|
|
|
|
def bst_create_node(name, phone):
|
|
|
|
|
|
return {'name': name, 'phone': phone, 'left': None, 'right': None}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def bst_insert(root, name, phone):
|
|
|
|
|
|
"""Вставить / обновить. Возвращает корень."""
|
|
|
|
|
|
if root is None:
|
|
|
|
|
|
return bst_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):
|
|
|
|
|
|
"""Вернуть телефон или None."""
|
|
|
|
|
|
while root is not None:
|
|
|
|
|
|
if name == root['name']:
|
|
|
|
|
|
return root['phone']
|
|
|
|
|
|
elif name < root['name']:
|
|
|
|
|
|
root = root['left']
|
|
|
|
|
|
else:
|
|
|
|
|
|
root = root['right']
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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'] = successor['name']
|
|
|
|
|
|
root['phone'] = successor['phone']
|
|
|
|
|
|
root['right'] = bst_delete(root['right'], successor['name'])
|
|
|
|
|
|
return root
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def bst_list_all(root):
|
|
|
|
|
|
"""Центрированный (in-order) обход → отсортированный список."""
|
|
|
|
|
|
result = []
|
|
|
|
|
|
stack = []
|
|
|
|
|
|
node = root
|
|
|
|
|
|
while stack or node is not None:
|
|
|
|
|
|
while node is not None:
|
|
|
|
|
|
stack.append(node)
|
|
|
|
|
|
node = node['left']
|
|
|
|
|
|
node = stack.pop()
|
|
|
|
|
|
result.append((node['name'], node['phone']))
|
|
|
|
|
|
node = node['right']
|
2026-05-24 21:51:26 +00:00
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
Экспериментальная часть: замер производительности трёх структур данных.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
import time
|
|
|
|
|
|
import csv
|
|
|
|
|
|
import random
|
|
|
|
|
|
import os
|
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
|
|
sys.setrecursionlimit(30000) # BST с отсортированными данными — глубокая рекурсия
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── Параметры ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
N = 10_000 # размер набора
|
|
|
|
|
|
REPEATS = 5 # повторений каждого замера
|
|
|
|
|
|
SEARCH_N = 100 # запросов на поиск (существующих)
|
|
|
|
|
|
SEARCH_MISS = 10 # запросов на поиск (отсутствующих)
|
|
|
|
|
|
DELETE_N = 50 # удалений
|
|
|
|
|
|
|
|
|
|
|
|
random.seed(42)
|
|
|
|
|
|
|
|
|
|
|
|
# ── Генерация данных ───────────────────────────────────────────────────────
|
|
|
|
|
|
records_sorted = [(f"User_{i:05d}", f"+7-000-{i:07d}") for i in range(N)]
|
|
|
|
|
|
records_shuffled = records_sorted[:]
|
|
|
|
|
|
random.shuffle(records_shuffled)
|
|
|
|
|
|
|
|
|
|
|
|
search_names_hit = [records_sorted[i][0] for i in random.sample(range(N), SEARCH_N)]
|
|
|
|
|
|
search_names_miss = [f"None_{i:04d}" for i in range(SEARCH_MISS)]
|
|
|
|
|
|
search_names = search_names_hit + search_names_miss
|
|
|
|
|
|
|
|
|
|
|
|
delete_names = [records_sorted[i][0] for i in random.sample(range(N), DELETE_N)]
|
|
|
|
|
|
|
|
|
|
|
|
# ── Вспомогательные функции ────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
def build_ll(records):
|
|
|
|
|
|
head = None
|
|
|
|
|
|
for name, phone in records:
|
|
|
|
|
|
head = ll_insert(head, name, phone)
|
|
|
|
|
|
return head
|
|
|
|
|
|
|
|
|
|
|
|
def build_ht(records):
|
|
|
|
|
|
buckets = ht_create()
|
|
|
|
|
|
for name, phone in records:
|
|
|
|
|
|
ht_insert(buckets, name, phone)
|
|
|
|
|
|
return buckets
|
|
|
|
|
|
|
|
|
|
|
|
def build_bst(records):
|
|
|
|
|
|
root = None
|
|
|
|
|
|
for name, phone in records:
|
|
|
|
|
|
root = bst_insert(root, name, phone)
|
|
|
|
|
|
return root
|
|
|
|
|
|
|
|
|
|
|
|
STRUCTURES = {
|
|
|
|
|
|
'LinkedList': {
|
|
|
|
|
|
'build': build_ll,
|
|
|
|
|
|
'find': ll_find,
|
|
|
|
|
|
'delete': lambda ds, name: ll_delete(ds, name), # возвращает новый head
|
|
|
|
|
|
'list_all': ll_list_all,
|
|
|
|
|
|
'mutable': False, # ll_delete возвращает новую голову
|
|
|
|
|
|
},
|
|
|
|
|
|
'HashTable': {
|
|
|
|
|
|
'build': build_ht,
|
|
|
|
|
|
'find': ht_find,
|
|
|
|
|
|
'delete': lambda ds, name: ht_delete(ds, name), # in-place, returns None
|
|
|
|
|
|
'list_all': ht_list_all,
|
|
|
|
|
|
'mutable': True,
|
|
|
|
|
|
},
|
|
|
|
|
|
'BST': {
|
|
|
|
|
|
'build': build_bst,
|
|
|
|
|
|
'find': bst_find,
|
|
|
|
|
|
'delete': lambda ds, name: bst_delete(ds, name), # возвращает новый корень
|
|
|
|
|
|
'list_all': bst_list_all,
|
|
|
|
|
|
'mutable': False,
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MODES = {
|
|
|
|
|
|
'shuffled': records_shuffled,
|
|
|
|
|
|
'sorted': records_sorted,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# ── Замер ──────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
def measure(fn, *args, repeats=REPEATS):
|
|
|
|
|
|
times = []
|
|
|
|
|
|
for _ in range(repeats):
|
|
|
|
|
|
t0 = time.perf_counter()
|
|
|
|
|
|
fn(*args)
|
|
|
|
|
|
times.append(time.perf_counter() - t0)
|
|
|
|
|
|
return times
|
|
|
|
|
|
|
|
|
|
|
|
rows = [["structure", "mode", "operation", "run", "time_sec"]]
|
|
|
|
|
|
|
|
|
|
|
|
for struct_name, ops in STRUCTURES.items():
|
|
|
|
|
|
for mode_name, records in MODES.items():
|
|
|
|
|
|
print(f" {struct_name} / {mode_name} ...", flush=True)
|
|
|
|
|
|
|
|
|
|
|
|
# ── А. Вставка ──────────────────────────────────────────────────
|
|
|
|
|
|
insert_times = []
|
|
|
|
|
|
for run in range(REPEATS):
|
|
|
|
|
|
t0 = time.perf_counter()
|
|
|
|
|
|
ds = ops['build'](records)
|
|
|
|
|
|
insert_times.append(time.perf_counter() - t0)
|
|
|
|
|
|
rows.append([struct_name, mode_name, "insert", run + 1, insert_times[-1]])
|
|
|
|
|
|
|
|
|
|
|
|
# Строим структуру один раз для поиска и удаления
|
|
|
|
|
|
ds = ops['build'](records)
|
|
|
|
|
|
|
|
|
|
|
|
# ── Б. Поиск ────────────────────────────────────────────────────
|
|
|
|
|
|
def do_search(ds=ds):
|
|
|
|
|
|
for name in search_names:
|
|
|
|
|
|
ops['find'](ds, name)
|
|
|
|
|
|
|
|
|
|
|
|
search_times = measure(do_search)
|
|
|
|
|
|
for run, t in enumerate(search_times, 1):
|
|
|
|
|
|
rows.append([struct_name, mode_name, "find", run, t])
|
|
|
|
|
|
|
|
|
|
|
|
# ── В. Удаление ─────────────────────────────────────────────────
|
|
|
|
|
|
# Удаление изменяет структуру, поэтому каждый раз пересобираем
|
|
|
|
|
|
delete_times = []
|
|
|
|
|
|
for run in range(REPEATS):
|
|
|
|
|
|
ds2 = ops['build'](records)
|
|
|
|
|
|
t0 = time.perf_counter()
|
|
|
|
|
|
for name in delete_names:
|
|
|
|
|
|
result = ops['delete'](ds2, name)
|
|
|
|
|
|
if result is not None: # ll / bst возвращают новую голову/корень
|
|
|
|
|
|
ds2 = result
|
|
|
|
|
|
delete_times.append(time.perf_counter() - t0)
|
|
|
|
|
|
rows.append([struct_name, mode_name, "delete", run + 1, delete_times[-1]])
|
|
|
|
|
|
|
|
|
|
|
|
print(f" insert avg={sum(insert_times)/REPEATS:.4f}s "
|
|
|
|
|
|
f"find avg={sum(search_times)/REPEATS:.4f}s "
|
|
|
|
|
|
f"delete avg={sum(delete_times)/REPEATS:.4f}s")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with open("results.csv", "w", newline="", encoding="utf-8") as f:
|
|
|
|
|
|
csv.writer(f).writerows(rows)
|
|
|
|
|
|
|
|
|
|
|
|
print("\nРезультаты сохранены в docs/data/results.csv")
|