forked from UNN/2026-rff_mp
387 lines
15 KiB
Python
387 lines
15 KiB
Python
|
|
experiment_code = ''
|
|
|
|
import time
|
|
import csv
|
|
import random
|
|
import sys
|
|
import matplotlib.pyplot as plt
|
|
import numpy as np
|
|
|
|
from linked_list import ll_insert, ll_find, ll_delete, ll_list_all
|
|
from hash_table import ht_insert, ht_find, ht_delete, ht_list_all
|
|
from bst import bst_insert, bst_find, bst_delete, bst_list_all
|
|
|
|
|
|
sys.setrecursionlimit(20000)
|
|
|
|
# параметры
|
|
N = 5000 # Количество записей
|
|
NUM_RUNS = 5 # Количество прогонов для усреднения
|
|
BUCKET_SIZE = N // 2 # Размер хеш-таблицы (load factor ~2)
|
|
|
|
|
|
def generate_data(n):
|
|
records = []
|
|
for i in range(n):
|
|
name = f"User_{i:05d}"
|
|
phone = f"+7{random.randint(9000000000, 9999999999)}"
|
|
records.append((name, phone))
|
|
|
|
records_shuffled = records.copy()
|
|
random.shuffle(records_shuffled)
|
|
records_sorted = sorted(records, key=lambda x: x[0])
|
|
|
|
return records, records_shuffled, records_sorted
|
|
|
|
|
|
def run_linked_list_experiment(records, search_existing, search_nonexistent, names_to_delete):
|
|
|
|
head = None
|
|
|
|
# Вставка
|
|
start = time.perf_counter()
|
|
for name, phone in records:
|
|
head = ll_insert(head, name, phone)
|
|
end = time.perf_counter()
|
|
insert_time = end - start
|
|
|
|
# Поиск
|
|
start = time.perf_counter()
|
|
for name in search_existing:
|
|
ll_find(head, name)
|
|
for name in search_nonexistent:
|
|
ll_find(head, name)
|
|
end = time.perf_counter()
|
|
find_time = end - start
|
|
|
|
# Удаление
|
|
start = time.perf_counter()
|
|
for name in names_to_delete:
|
|
head = ll_delete(head, name)
|
|
end = time.perf_counter()
|
|
delete_time = end - start
|
|
|
|
return insert_time, find_time, delete_time
|
|
|
|
|
|
def run_hash_table_experiment(records, search_existing, search_nonexistent, names_to_delete):
|
|
|
|
buckets = [None] * BUCKET_SIZE
|
|
|
|
# Вставка
|
|
start = time.perf_counter()
|
|
for name, phone in records:
|
|
ht_insert(buckets, name, phone)
|
|
end = time.perf_counter()
|
|
insert_time = end - start
|
|
|
|
# Поиск
|
|
start = time.perf_counter()
|
|
for name in search_existing:
|
|
ht_find(buckets, name)
|
|
for name in search_nonexistent:
|
|
ht_find(buckets, name)
|
|
end = time.perf_counter()
|
|
find_time = end - start
|
|
|
|
# Удаление
|
|
start = time.perf_counter()
|
|
for name in names_to_delete:
|
|
ht_delete(buckets, name)
|
|
end = time.perf_counter()
|
|
delete_time = end - start
|
|
|
|
return insert_time, find_time, delete_time
|
|
|
|
|
|
def run_bst_experiment(records, search_existing, search_nonexistent, names_to_delete):
|
|
|
|
root = None
|
|
|
|
# Вставка
|
|
start = time.perf_counter()
|
|
for name, phone in records:
|
|
root = bst_insert(root, name, phone)
|
|
end = time.perf_counter()
|
|
insert_time = end - start
|
|
|
|
# Поиск
|
|
start = time.perf_counter()
|
|
for name in search_existing:
|
|
bst_find(root, name)
|
|
for name in search_nonexistent:
|
|
bst_find(root, name)
|
|
end = time.perf_counter()
|
|
find_time = end - start
|
|
|
|
# Удаление
|
|
start = time.perf_counter()
|
|
for name in names_to_delete:
|
|
root = bst_delete(root, name)
|
|
end = time.perf_counter()
|
|
delete_time = end - start
|
|
|
|
return insert_time, find_time, delete_time
|
|
|
|
|
|
def run_all_experiments():
|
|
|
|
print("=" * 60)
|
|
print("ЭКСПЕРИМЕНТ: Сравнение структур данных")
|
|
print(f"N = {N}, прогонов = {NUM_RUNS}")
|
|
print("=" * 60)
|
|
|
|
# Генерация данных
|
|
print("\\n[1/5] Генерация тестовых данных...")
|
|
records, records_shuffled, records_sorted = generate_data(N)
|
|
|
|
# Подготовка данных для поиска и удаления (фиксируем seed для воспроизводимости)
|
|
random.seed(42)
|
|
existing_names = [r[0] for r in records]
|
|
search_existing = random.sample(existing_names, 100)
|
|
search_nonexistent = [f"None_{i:05d}" for i in range(10)]
|
|
names_to_delete = random.sample(existing_names, 50)
|
|
print(f" Записей: {len(records)}")
|
|
print(f" Поиск: {len(search_existing)} существующих + {len(search_nonexistent)} несуществующих")
|
|
print(f" Удаление: {len(names_to_delete)} записей")
|
|
|
|
# Хранение результатов
|
|
all_results = []
|
|
|
|
|
|
print("\\n[2/5] Linked List...")
|
|
for run in range(NUM_RUNS):
|
|
t_insert, t_find, t_delete = run_linked_list_experiment(
|
|
records_shuffled, search_existing, search_nonexistent, names_to_delete
|
|
)
|
|
all_results.append({
|
|
'Структура': 'LinkedList', 'Режим': 'случайный', 'Прогон': run + 1,
|
|
'Вставка': t_insert, 'Поиск': t_find, 'Удаление': t_delete
|
|
})
|
|
print(f" Случайный прогон {run + 1}: insert={t_insert:.4f}s, find={t_find:.4f}s, delete={t_delete:.4f}s")
|
|
|
|
for run in range(NUM_RUNS):
|
|
t_insert, t_find, t_delete = run_linked_list_experiment(
|
|
records_sorted, search_existing, search_nonexistent, names_to_delete
|
|
)
|
|
all_results.append({
|
|
'Структура': 'LinkedList', 'Режим': 'отсортированный', 'Прогон': run + 1,
|
|
'Вставка': t_insert, 'Поиск': t_find, 'Удаление': t_delete
|
|
})
|
|
print(f" Отсортированный прогон {run + 1}: insert={t_insert:.4f}s, find={t_find:.4f}s, delete={t_delete:.4f}s")
|
|
|
|
|
|
print("\\n[3/5] Hash Table...")
|
|
for run in range(NUM_RUNS):
|
|
t_insert, t_find, t_delete = run_hash_table_experiment(
|
|
records_shuffled, search_existing, search_nonexistent, names_to_delete
|
|
)
|
|
all_results.append({
|
|
'Структура': 'HashTable', 'Режим': 'случайный', 'Прогон': run + 1,
|
|
'Вставка': t_insert, 'Поиск': t_find, 'Удаление': t_delete
|
|
})
|
|
print(f" Случайный прогон {run + 1}: insert={t_insert:.4f}s, find={t_find:.4f}s, delete={t_delete:.4f}s")
|
|
|
|
for run in range(NUM_RUNS):
|
|
t_insert, t_find, t_delete = run_hash_table_experiment(
|
|
records_sorted, search_existing, search_nonexistent, names_to_delete
|
|
)
|
|
all_results.append({
|
|
'Структура': 'HashTable', 'Режим': 'отсортированный', 'Прогон': run + 1,
|
|
'Вставка': t_insert, 'Поиск': t_find, 'Удаление': t_delete
|
|
})
|
|
print(f" Отсортированный прогон {run + 1}: insert={t_insert:.4f}s, find={t_find:.4f}s, delete={t_delete:.4f}s")
|
|
|
|
|
|
print("\\n[4/5] BST...")
|
|
for run in range(NUM_RUNS):
|
|
t_insert, t_find, t_delete = run_bst_experiment(
|
|
records_shuffled, search_existing, search_nonexistent, names_to_delete
|
|
)
|
|
all_results.append({
|
|
'Структура': 'BST', 'Режим': 'случайный', 'Прогон': run + 1,
|
|
'Вставка': t_insert, 'Поиск': t_find, 'Удаление': t_delete
|
|
})
|
|
print(f" Случайный прогон {run + 1}: insert={t_insert:.4f}s, find={t_find:.4f}s, delete={t_delete:.4f}s")
|
|
|
|
for run in range(NUM_RUNS):
|
|
t_insert, t_find, t_delete = run_bst_experiment(
|
|
records_sorted, search_existing, search_nonexistent, names_to_delete
|
|
)
|
|
all_results.append({
|
|
'Структура': 'BST', 'Режим': 'отсортированный', 'Прогон': run + 1,
|
|
'Вставка': t_insert, 'Поиск': t_find, 'Удаление': t_delete
|
|
})
|
|
print(f" Отсортированный прогон {run + 1}: insert={t_insert:.4f}s, find={t_find:.4f}s, delete={t_delete:.4f}s")
|
|
|
|
|
|
print("\\n[5/5] Сохранение результатов...")
|
|
|
|
# Сырые данные
|
|
with open('../docs/data/results_raw.csv', 'w', newline='', encoding='utf-8') as f:
|
|
writer = csv.writer(f)
|
|
writer.writerow(['Структура', 'Режим', 'Прогон', 'Вставка (сек)', 'Поиск (сек)', 'Удаление (сек)'])
|
|
for r in all_results:
|
|
writer.writerow([r['Структура'], r['Режим'], r['Прогон'],
|
|
r['Вставка'], r['Поиск'], r['Удаление']])
|
|
print(" Сохранено: ../docs/data/results_raw.csv")
|
|
|
|
# Сводная таблица
|
|
from collections import defaultdict
|
|
avg_results = defaultdict(lambda: {'insert': [], 'find': [], 'delete': []})
|
|
for r in all_results:
|
|
key = (r['Структура'], r['Режим'])
|
|
avg_results[key]['insert'].append(r['Вставка'])
|
|
avg_results[key]['find'].append(r['Поиск'])
|
|
avg_results[key]['delete'].append(r['Удаление'])
|
|
|
|
with open('../docs/data/results_summary.csv', 'w', newline='', encoding='utf-8') as f:
|
|
writer = csv.writer(f)
|
|
writer.writerow(['Структура', 'Режим', 'Операция', 'Среднее (сек)', 'Мин (сек)', 'Макс (сек)'])
|
|
for (struct, mode), times in avg_results.items():
|
|
writer.writerow([struct, mode, 'Вставка',
|
|
f"{sum(times['insert']) / len(times['insert']):.6f}",
|
|
f"{min(times['insert']):.6f}",
|
|
f"{max(times['insert']):.6f}"])
|
|
writer.writerow([struct, mode, 'Поиск',
|
|
f"{sum(times['find']) / len(times['find']):.6f}",
|
|
f"{min(times['find']):.6f}",
|
|
f"{max(times['find']):.6f}"])
|
|
writer.writerow([struct, mode, 'Удаление',
|
|
f"{sum(times['delete']) / len(times['delete']):.6f}",
|
|
f"{min(times['delete']):.6f}",
|
|
f"{max(times['delete']):.6f}"])
|
|
print(" Сохранено: ../docs/data/results_summary.csv")
|
|
|
|
print(" Построение графиков...")
|
|
build_charts(avg_results)
|
|
print(" Сохранено: ../docs/data/charts.png")
|
|
|
|
print("\\n" + "=" * 60)
|
|
print("ЭКСПЕРИМЕНТ ЗАВЕРШЁН!")
|
|
print("=" * 60)
|
|
|
|
return all_results, avg_results
|
|
|
|
|
|
def build_charts(avg_results):
|
|
"""Строит графики сравнения производительности."""
|
|
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
|
|
fig.suptitle(f'Сравнение производительности структур данных (N={N})', fontsize=16, fontweight='bold')
|
|
|
|
structures = ['LinkedList', 'HashTable', 'BST']
|
|
modes = ['случайный', 'отсортированный']
|
|
struct_colors = {'LinkedList': '#FF6B6B', 'HashTable': '#4ECDC4', 'BST': '#45B7D1'}
|
|
|
|
# Подготовка данных для графиков
|
|
def get_value(struct, mode, op):
|
|
key = (struct, mode)
|
|
if key in avg_results:
|
|
return sum(avg_results[key][op]) / len(avg_results[key][op])
|
|
return 0
|
|
|
|
# График 1: Вставка
|
|
ax = axes[0, 0]
|
|
x = np.arange(len(modes))
|
|
width = 0.25
|
|
for i, struct in enumerate(structures):
|
|
vals = [get_value(struct, mode, 'insert') for mode in modes]
|
|
ax.bar(x + i * width, vals, width, label=struct, color=struct_colors[struct])
|
|
ax.set_xlabel('Режим данных')
|
|
ax.set_ylabel('Время (сек)')
|
|
ax.set_title('Вставка')
|
|
ax.set_xticks(x + width)
|
|
ax.set_xticklabels(modes)
|
|
ax.legend()
|
|
ax.set_yscale('log')
|
|
ax.grid(True, alpha=0.3)
|
|
|
|
# График 2: Поиск
|
|
ax = axes[0, 1]
|
|
for i, struct in enumerate(structures):
|
|
vals = [get_value(struct, mode, 'find') for mode in modes]
|
|
ax.bar(x + i * width, vals, width, label=struct, color=struct_colors[struct])
|
|
ax.set_xlabel('Режим данных')
|
|
ax.set_ylabel('Время (сек)')
|
|
ax.set_title('Поиск (110 операций)')
|
|
ax.set_xticks(x + width)
|
|
ax.set_xticklabels(modes)
|
|
ax.legend()
|
|
ax.set_yscale('log')
|
|
ax.grid(True, alpha=0.3)
|
|
|
|
# График 3: Удаление
|
|
ax = axes[0, 2]
|
|
for i, struct in enumerate(structures):
|
|
vals = [get_value(struct, mode, 'delete') for mode in modes]
|
|
ax.bar(x + i * width, vals, width, label=struct, color=struct_colors[struct])
|
|
ax.set_xlabel('Режим данных')
|
|
ax.set_ylabel('Время (сек)')
|
|
ax.set_title('Удаление (50 операций)')
|
|
ax.set_xticks(x + width)
|
|
ax.set_xticklabels(modes)
|
|
ax.legend()
|
|
ax.set_yscale('log')
|
|
ax.grid(True, alpha=0.3)
|
|
|
|
# График 4: BST деградация
|
|
ax = axes[1, 0]
|
|
bst_random = get_value('BST', 'случайный', 'insert')
|
|
bst_sorted = get_value('BST', 'отсортированный', 'insert')
|
|
ax.bar(['Случайный', 'Отсортированный'], [bst_random, bst_sorted],
|
|
color=['#45B7D1', '#E74C3C'])
|
|
ax.set_ylabel('Время (сек)')
|
|
ax.set_title('BST: влияние порядка данных на вставку')
|
|
for i, v in enumerate([bst_random, bst_sorted]):
|
|
ax.text(i, v + max(v * 0.05, 0.01), f'{v:.3f}s', ha='center', fontweight='bold')
|
|
ax.grid(True, alpha=0.3)
|
|
|
|
# График 5: Случайный режим — все операции
|
|
ax = axes[1, 1]
|
|
x = np.arange(len(structures))
|
|
width = 0.25
|
|
insert_vals = [get_value(s, 'случайный', 'insert') for s in structures]
|
|
find_vals = [get_value(s, 'случайный', 'find') for s in structures]
|
|
delete_vals = [get_value(s, 'случайный', 'delete') for s in structures]
|
|
ax.bar(x - width, insert_vals, width, label='Вставка', color='#FF6B6B')
|
|
ax.bar(x, find_vals, width, label='Поиск', color='#4ECDC4')
|
|
ax.bar(x + width, delete_vals, width, label='Удаление', color='#45B7D1')
|
|
ax.set_xlabel('Структура данных')
|
|
ax.set_ylabel('Время (сек)')
|
|
ax.set_title('Случайный режим: все операции')
|
|
ax.set_xticks(x)
|
|
ax.set_xticklabels(structures)
|
|
ax.legend()
|
|
ax.set_yscale('log')
|
|
ax.grid(True, alpha=0.3)
|
|
|
|
# График 6: Отсортированный режим — поиск и удаление
|
|
ax = axes[1, 2]
|
|
find_vals = [get_value(s, 'отсортированный', 'find') for s in structures]
|
|
delete_vals = [get_value(s, 'отсортированный', 'delete') for s in structures]
|
|
ax.bar(x - width / 2, find_vals, width, label='Поиск', color='#4ECDC4')
|
|
ax.bar(x + width / 2, delete_vals, width, label='Удаление', color='#45B7D1')
|
|
ax.set_xlabel('Структура данных')
|
|
ax.set_ylabel('Время (сек)')
|
|
ax.set_title('Отсортированный режим: поиск и удаление')
|
|
ax.set_xticks(x)
|
|
ax.set_xticklabels(structures)
|
|
ax.legend()
|
|
ax.set_yscale('log')
|
|
ax.grid(True, alpha=0.3)
|
|
|
|
plt.tight_layout()
|
|
plt.savefig('../docs/data/charts.png', dpi=150, bbox_inches='tight')
|
|
plt.close()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
run_all_experiments()
|
|
|
|
|
|
with open('/mnt/agents/output/lab1/src/experiment.py', 'w', encoding='utf-8') as f:
|
|
f.write(experiment_code)
|
|
|
|
print("✅ experiment.py создан")
|