2026-09-02 22:45:10 +00:00
|
|
|
|
import random
|
2026-09-02 23:03:21 +00:00
|
|
|
|
import time
|
|
|
|
|
|
import sys
|
|
|
|
|
|
import csv
|
|
|
|
|
|
import os
|
|
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
|
|
|
|
|
|
|
sys.setrecursionlimit(10000)
|
2026-09-02 22:45:10 +00:00
|
|
|
|
|
|
|
|
|
|
ll_head = None
|
|
|
|
|
|
|
|
|
|
|
|
def ll_insert(name, phone):
|
|
|
|
|
|
global ll_head
|
|
|
|
|
|
cur = ll_head
|
|
|
|
|
|
while cur is not None:
|
|
|
|
|
|
if cur['name'] == name:
|
|
|
|
|
|
cur['phone'] = phone
|
|
|
|
|
|
return
|
|
|
|
|
|
cur = cur['next']
|
|
|
|
|
|
new_node = {'name': name, 'phone': phone, 'next': ll_head}
|
|
|
|
|
|
ll_head = new_node
|
|
|
|
|
|
|
|
|
|
|
|
def ll_find(name):
|
|
|
|
|
|
cur = ll_head
|
|
|
|
|
|
while cur is not None:
|
|
|
|
|
|
if cur['name'] == name:
|
|
|
|
|
|
return cur['phone']
|
|
|
|
|
|
cur = cur['next']
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def ll_delete(name):
|
|
|
|
|
|
global ll_head
|
|
|
|
|
|
if ll_head is None:
|
|
|
|
|
|
return
|
|
|
|
|
|
if ll_head['name'] == name:
|
|
|
|
|
|
ll_head = ll_head['next']
|
|
|
|
|
|
return
|
|
|
|
|
|
prev = ll_head
|
|
|
|
|
|
cur = ll_head['next']
|
|
|
|
|
|
while cur is not None:
|
|
|
|
|
|
if cur['name'] == name:
|
|
|
|
|
|
prev['next'] = cur['next']
|
|
|
|
|
|
return
|
|
|
|
|
|
prev = cur
|
|
|
|
|
|
cur = cur['next']
|
|
|
|
|
|
|
|
|
|
|
|
def ll_list_all():
|
|
|
|
|
|
records = []
|
|
|
|
|
|
cur = ll_head
|
|
|
|
|
|
while cur is not None:
|
|
|
|
|
|
records.append((cur['name'], cur['phone']))
|
|
|
|
|
|
cur = cur['next']
|
|
|
|
|
|
records.sort(key=lambda x: x[0])
|
|
|
|
|
|
return records
|
|
|
|
|
|
|
|
|
|
|
|
BUCKET_COUNT = 10
|
|
|
|
|
|
buckets = [None] * BUCKET_COUNT
|
|
|
|
|
|
|
|
|
|
|
|
def hash_func(name):
|
|
|
|
|
|
s = 0
|
|
|
|
|
|
for ch in name:
|
|
|
|
|
|
s += ord(ch)
|
|
|
|
|
|
return s % BUCKET_COUNT
|
|
|
|
|
|
|
|
|
|
|
|
def ht_insert(name, phone):
|
|
|
|
|
|
global buckets
|
|
|
|
|
|
idx = hash_func(name)
|
|
|
|
|
|
cur = buckets[idx]
|
|
|
|
|
|
while cur is not None:
|
|
|
|
|
|
if cur['name'] == name:
|
|
|
|
|
|
cur['phone'] = phone
|
|
|
|
|
|
return
|
|
|
|
|
|
cur = cur['next']
|
|
|
|
|
|
new_node = {'name': name, 'phone': phone, 'next': buckets[idx]}
|
|
|
|
|
|
buckets[idx] = new_node
|
|
|
|
|
|
|
|
|
|
|
|
def ht_find(name):
|
|
|
|
|
|
idx = hash_func(name)
|
|
|
|
|
|
cur = buckets[idx]
|
|
|
|
|
|
while cur is not None:
|
|
|
|
|
|
if cur['name'] == name:
|
|
|
|
|
|
return cur['phone']
|
|
|
|
|
|
cur = cur['next']
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def ht_delete(name):
|
|
|
|
|
|
global buckets
|
|
|
|
|
|
idx = hash_func(name)
|
|
|
|
|
|
head = buckets[idx]
|
|
|
|
|
|
if head is None:
|
|
|
|
|
|
return
|
|
|
|
|
|
if head['name'] == name:
|
|
|
|
|
|
buckets[idx] = head['next']
|
|
|
|
|
|
return
|
|
|
|
|
|
prev = head
|
|
|
|
|
|
cur = head['next']
|
|
|
|
|
|
while cur is not None:
|
|
|
|
|
|
if cur['name'] == name:
|
|
|
|
|
|
prev['next'] = cur['next']
|
|
|
|
|
|
return
|
|
|
|
|
|
prev = cur
|
|
|
|
|
|
cur = cur['next']
|
|
|
|
|
|
|
|
|
|
|
|
def ht_list_all():
|
|
|
|
|
|
all_records = []
|
|
|
|
|
|
for head in buckets:
|
|
|
|
|
|
cur = head
|
|
|
|
|
|
while cur is not None:
|
|
|
|
|
|
all_records.append((cur['name'], cur['phone']))
|
|
|
|
|
|
cur = cur['next']
|
|
|
|
|
|
all_records.sort(key=lambda x: x[0])
|
|
|
|
|
|
return all_records
|
|
|
|
|
|
|
2026-09-02 22:49:04 +00:00
|
|
|
|
bst_root = None
|
|
|
|
|
|
|
|
|
|
|
|
def bst_create_node(name, phone):
|
|
|
|
|
|
return {'name': name, 'phone': phone, 'left': None, 'right': None}
|
|
|
|
|
|
|
|
|
|
|
|
def bst_insert(name, phone):
|
|
|
|
|
|
global bst_root
|
|
|
|
|
|
if bst_root is None:
|
|
|
|
|
|
bst_root = bst_create_node(name, phone)
|
|
|
|
|
|
return
|
2026-09-02 23:03:21 +00:00
|
|
|
|
current = bst_root
|
|
|
|
|
|
while True:
|
|
|
|
|
|
if name == current['name']:
|
|
|
|
|
|
current['phone'] = phone
|
|
|
|
|
|
return
|
|
|
|
|
|
elif name < current['name']:
|
|
|
|
|
|
if current['left'] is None:
|
|
|
|
|
|
current['left'] = bst_create_node(name, phone)
|
|
|
|
|
|
return
|
|
|
|
|
|
current = current['left']
|
2026-09-02 22:49:04 +00:00
|
|
|
|
else:
|
2026-09-02 23:03:21 +00:00
|
|
|
|
if current['right'] is None:
|
|
|
|
|
|
current['right'] = bst_create_node(name, phone)
|
|
|
|
|
|
return
|
|
|
|
|
|
current = current['right']
|
2026-09-02 22:49:04 +00:00
|
|
|
|
|
|
|
|
|
|
def bst_find(name):
|
2026-09-02 23:03:21 +00:00
|
|
|
|
current = bst_root
|
|
|
|
|
|
while current is not None:
|
|
|
|
|
|
if name == current['name']:
|
|
|
|
|
|
return current['phone']
|
|
|
|
|
|
elif name < current['name']:
|
|
|
|
|
|
current = current['left']
|
2026-09-02 22:49:04 +00:00
|
|
|
|
else:
|
2026-09-02 23:03:21 +00:00
|
|
|
|
current = current['right']
|
|
|
|
|
|
return None
|
2026-09-02 22:49:04 +00:00
|
|
|
|
|
|
|
|
|
|
def find_min(node):
|
|
|
|
|
|
while node['left'] is not None:
|
|
|
|
|
|
node = node['left']
|
|
|
|
|
|
return node
|
|
|
|
|
|
|
|
|
|
|
|
def bst_delete(name):
|
|
|
|
|
|
global bst_root
|
|
|
|
|
|
def delete_rec(node):
|
|
|
|
|
|
if node is None:
|
|
|
|
|
|
return None
|
|
|
|
|
|
if name < node['name']:
|
|
|
|
|
|
node['left'] = delete_rec(node['left'])
|
|
|
|
|
|
elif name > node['name']:
|
|
|
|
|
|
node['right'] = delete_rec(node['right'])
|
|
|
|
|
|
else:
|
|
|
|
|
|
if node['left'] is None:
|
|
|
|
|
|
return node['right']
|
|
|
|
|
|
if node['right'] is None:
|
|
|
|
|
|
return node['left']
|
|
|
|
|
|
min_node = find_min(node['right'])
|
|
|
|
|
|
node['name'] = min_node['name']
|
|
|
|
|
|
node['phone'] = min_node['phone']
|
|
|
|
|
|
node['right'] = delete_rec(node['right'])
|
|
|
|
|
|
return node
|
|
|
|
|
|
bst_root = delete_rec(bst_root)
|
|
|
|
|
|
|
|
|
|
|
|
def bst_list_all():
|
|
|
|
|
|
result = []
|
|
|
|
|
|
def inorder(node):
|
|
|
|
|
|
if node is None:
|
|
|
|
|
|
return
|
|
|
|
|
|
inorder(node['left'])
|
|
|
|
|
|
result.append((node['name'], node['phone']))
|
|
|
|
|
|
inorder(node['right'])
|
|
|
|
|
|
inorder(bst_root)
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
2026-09-02 23:03:21 +00:00
|
|
|
|
def generate_records(n):
|
|
|
|
|
|
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 run_experiment():
|
|
|
|
|
|
N = 1000
|
|
|
|
|
|
base = generate_records(N)
|
|
|
|
|
|
shuffled = base.copy()
|
|
|
|
|
|
random.shuffle(shuffled)
|
|
|
|
|
|
sorted_records = sorted(base, key=lambda x: x[0])
|
|
|
|
|
|
|
|
|
|
|
|
structures = [
|
|
|
|
|
|
('LinkedList', ll_insert, ll_find, ll_delete, ll_list_all),
|
|
|
|
|
|
('HashTable', ht_insert, ht_find, ht_delete, ht_list_all),
|
|
|
|
|
|
('BST', bst_insert, bst_find, bst_delete, bst_list_all)
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
all_results = [] # для CSV: список словарей
|
|
|
|
|
|
repeats = 5
|
|
|
|
|
|
|
|
|
|
|
|
for mode_name, data in [('random', shuffled), ('sorted', sorted_records)]:
|
|
|
|
|
|
for struct_name, ins, fnd, dele, lst in structures:
|
|
|
|
|
|
print(f"Testing {struct_name} on {mode_name}...")
|
|
|
|
|
|
for rep in range(repeats):
|
|
|
|
|
|
# сброс структур
|
|
|
|
|
|
global ll_head, buckets, bst_root
|
|
|
|
|
|
ll_head = None
|
|
|
|
|
|
buckets = [None] * BUCKET_COUNT
|
|
|
|
|
|
bst_root = None
|
|
|
|
|
|
|
|
|
|
|
|
# вставка
|
|
|
|
|
|
t0 = time.perf_counter()
|
|
|
|
|
|
for name, phone in data:
|
|
|
|
|
|
ins(name, phone)
|
|
|
|
|
|
t1 = time.perf_counter()
|
|
|
|
|
|
insert_time = t1 - t0
|
|
|
|
|
|
|
|
|
|
|
|
# поиск 110 записей (100 существующих + 10 несуществующих)
|
|
|
|
|
|
existing = [name for name, _ in data]
|
|
|
|
|
|
sample = random.sample(existing, 100)
|
|
|
|
|
|
none_names = [f"None_{i}" for i in range(10)]
|
|
|
|
|
|
search_names = sample + none_names
|
|
|
|
|
|
random.shuffle(search_names)
|
|
|
|
|
|
t0 = time.perf_counter()
|
|
|
|
|
|
for name in search_names:
|
|
|
|
|
|
fnd(name)
|
|
|
|
|
|
t1 = time.perf_counter()
|
|
|
|
|
|
find_time = t1 - t0
|
|
|
|
|
|
|
|
|
|
|
|
# удаление 10 записей
|
|
|
|
|
|
to_delete = random.sample(existing, 10)
|
|
|
|
|
|
t0 = time.perf_counter()
|
|
|
|
|
|
for name in to_delete:
|
|
|
|
|
|
dele(name)
|
|
|
|
|
|
t1 = time.perf_counter()
|
|
|
|
|
|
delete_time = t1 - t0
|
|
|
|
|
|
|
|
|
|
|
|
all_results.append({
|
|
|
|
|
|
'Structure': struct_name,
|
|
|
|
|
|
'Mode': mode_name,
|
|
|
|
|
|
'Repetition': rep+1,
|
|
|
|
|
|
'Insert (sec)': insert_time,
|
|
|
|
|
|
'Find (sec)': find_time,
|
|
|
|
|
|
'Delete (sec)': delete_time
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
# Сохранение CSV
|
|
|
|
|
|
output_dir = "docs/data/1-st"
|
|
|
|
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
|
|
csv_path = os.path.join(output_dir, "experiment_results.csv")
|
|
|
|
|
|
with open(csv_path, 'w', newline='', encoding='utf-8') as f:
|
|
|
|
|
|
fieldnames = ['Structure', 'Mode', 'Repetition', 'Insert (sec)', 'Find (sec)', 'Delete (sec)']
|
|
|
|
|
|
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
|
|
|
|
|
writer.writeheader()
|
|
|
|
|
|
writer.writerows(all_results)
|
|
|
|
|
|
print(f"\nРезультаты сохранены в {csv_path}")
|
|
|
|
|
|
|
|
|
|
|
|
avg_data = {}
|
|
|
|
|
|
for r in all_results:
|
|
|
|
|
|
key = (r['Structure'], r['Mode'])
|
|
|
|
|
|
if key not in avg_data:
|
|
|
|
|
|
avg_data[key] = {'Insert': [], 'Find': [], 'Delete': []}
|
|
|
|
|
|
avg_data[key]['Insert'].append(r['Insert (sec)'])
|
|
|
|
|
|
avg_data[key]['Find'].append(r['Find (sec)'])
|
|
|
|
|
|
avg_data[key]['Delete'].append(r['Delete (sec)'])
|
|
|
|
|
|
|
|
|
|
|
|
structures_list = ['LinkedList', 'HashTable', 'BST']
|
|
|
|
|
|
modes_list = ['random', 'sorted']
|
|
|
|
|
|
insert_vals = {mode: [] for mode in modes_list}
|
|
|
|
|
|
find_vals = {mode: [] for mode in modes_list}
|
|
|
|
|
|
delete_vals = {mode: [] for mode in modes_list}
|
|
|
|
|
|
|
|
|
|
|
|
for mode in modes_list:
|
|
|
|
|
|
for struct in structures_list:
|
|
|
|
|
|
key = (struct, mode)
|
|
|
|
|
|
if key in avg_data:
|
|
|
|
|
|
insert_avg = sum(avg_data[key]['Insert']) / len(avg_data[key]['Insert'])
|
|
|
|
|
|
find_avg = sum(avg_data[key]['Find']) / len(avg_data[key]['Find'])
|
|
|
|
|
|
delete_avg = sum(avg_data[key]['Delete']) / len(avg_data[key]['Delete'])
|
|
|
|
|
|
else:
|
|
|
|
|
|
insert_avg = find_avg = delete_avg = 0
|
|
|
|
|
|
insert_vals[mode].append(insert_avg)
|
|
|
|
|
|
find_vals[mode].append(find_avg)
|
|
|
|
|
|
delete_vals[mode].append(delete_avg)
|
|
|
|
|
|
|
|
|
|
|
|
# Рисуем три столбчатые диаграммы
|
|
|
|
|
|
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
|
|
|
|
|
x = np.arange(len(structures_list))
|
|
|
|
|
|
width = 0.35
|
|
|
|
|
|
|
|
|
|
|
|
for ax, op_data, op_label, ylabel in zip(
|
|
|
|
|
|
axes,
|
|
|
|
|
|
[insert_vals, find_vals, delete_vals],
|
|
|
|
|
|
['Insert', 'Find', 'Delete'],
|
|
|
|
|
|
['Время вставки (с)', 'Время поиска (с)', 'Время удаления (с)']
|
|
|
|
|
|
):
|
|
|
|
|
|
random_vals = op_data['random']
|
|
|
|
|
|
sorted_vals = op_data['sorted']
|
|
|
|
|
|
ax.bar(x - width/2, random_vals, width, label='Случайный порядок', color='skyblue')
|
|
|
|
|
|
ax.bar(x + width/2, sorted_vals, width, label='Отсортированный порядок', color='salmon')
|
|
|
|
|
|
ax.set_xticks(x)
|
|
|
|
|
|
ax.set_xticklabels(structures_list)
|
|
|
|
|
|
ax.set_ylabel(ylabel)
|
|
|
|
|
|
ax.set_title(op_label)
|
|
|
|
|
|
ax.legend()
|
|
|
|
|
|
|
|
|
|
|
|
plt.tight_layout()
|
|
|
|
|
|
png_path = os.path.join(output_dir, "performance_comparison.png")
|
|
|
|
|
|
plt.savefig(png_path, dpi=150)
|
|
|
|
|
|
print(f"График сохранён в {png_path}")
|
|
|
|
|
|
plt.show()
|
|
|
|
|
|
|
|
|
|
|
|
# Вывод средних значений в консоль (для отчёта)
|
|
|
|
|
|
print("\nСредние значения (сек):")
|
|
|
|
|
|
print("Структура\tРежим\tВставка\tПоиск\tУдаление")
|
|
|
|
|
|
for (struct, mode), vals in avg_data.items():
|
|
|
|
|
|
ins_avg = sum(vals['Insert'])/len(vals['Insert'])
|
|
|
|
|
|
find_avg = sum(vals['Find'])/len(vals['Find'])
|
|
|
|
|
|
del_avg = sum(vals['Delete'])/len(vals['Delete'])
|
|
|
|
|
|
print(f"{struct}\t{mode}\t{ins_avg:.6f}\t{find_avg:.6f}\t{del_avg:.6f}")
|
|
|
|
|
|
|
2026-09-02 22:45:10 +00:00
|
|
|
|
if __name__ == '__main__':
|
2026-09-02 23:03:21 +00:00
|
|
|
|
run_experiment()
|