336 lines
10 KiB
Python
336 lines
10 KiB
Python
import random
|
||
import time
|
||
import sys
|
||
import csv
|
||
import os
|
||
import matplotlib.pyplot as plt
|
||
import numpy as np
|
||
|
||
sys.setrecursionlimit(10000)
|
||
|
||
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
|
||
|
||
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
|
||
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']
|
||
else:
|
||
if current['right'] is None:
|
||
current['right'] = bst_create_node(name, phone)
|
||
return
|
||
current = current['right']
|
||
|
||
def bst_find(name):
|
||
current = bst_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 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
|
||
|
||
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}")
|
||
|
||
if __name__ == '__main__':
|
||
run_experiment()
|