forked from UNN/2026-rff_mp
312 lines
12 KiB
Python
312 lines
12 KiB
Python
|
|
import csv
|
|||
|
|
import random
|
|||
|
|
import sys
|
|||
|
|
import time
|
|||
|
|
import threading
|
|||
|
|
import matplotlib.pyplot as plt
|
|||
|
|
|
|||
|
|
|
|||
|
|
sys.setrecursionlimit(30000)
|
|||
|
|
threading.stack_size(64*1024*1024)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def ll_insert(head, name, phone):
|
|||
|
|
"""Добавляет запись или обновляет телефон, если имя уже существует. Возвращает новую голову списка."""
|
|||
|
|
current = head
|
|||
|
|
while current is not None:
|
|||
|
|
if current["name"] == name:
|
|||
|
|
current["phone"] = phone
|
|||
|
|
return head
|
|||
|
|
current = current["next"]
|
|||
|
|
|
|||
|
|
fresh_node = {"name": name, "phone": phone, "next": head}
|
|||
|
|
return fresh_node
|
|||
|
|
|
|||
|
|
def ll_find(head, name):
|
|||
|
|
"""Ищет узел по имени. Возвращает телефон или None."""
|
|||
|
|
current = head
|
|||
|
|
while current is not None:
|
|||
|
|
if current["name"] == name:
|
|||
|
|
return current["phone"]
|
|||
|
|
current = current["next"]
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
def ll_delete(head, name):
|
|||
|
|
"""Удаляет узел по имени. Возвращает новую голову списка."""
|
|||
|
|
current = head
|
|||
|
|
previous = None
|
|||
|
|
|
|||
|
|
while current is not None:
|
|||
|
|
if current["name"] == name:
|
|||
|
|
if previous is None:
|
|||
|
|
return current["next"]
|
|||
|
|
else:
|
|||
|
|
previous["next"] = current["next"]
|
|||
|
|
return head
|
|||
|
|
previous = current
|
|||
|
|
current = current["next"]
|
|||
|
|
|
|||
|
|
return head
|
|||
|
|
|
|||
|
|
def ll_list_all(head):
|
|||
|
|
"""Собирает все записи в список и сортирует их по имени."""
|
|||
|
|
entries = []
|
|||
|
|
current = head
|
|||
|
|
while current is not None:
|
|||
|
|
entries.append((current["name"], current["phone"]))
|
|||
|
|
current = current["next"]
|
|||
|
|
entries.sort(key=lambda item: item[0])
|
|||
|
|
return entries
|
|||
|
|
|
|||
|
|
|
|||
|
|
def ht_create(size=1000):
|
|||
|
|
"""Создает пустую хеш-таблицу заданного размера."""
|
|||
|
|
return [None] * size
|
|||
|
|
|
|||
|
|
def ht_insert(buckets, name, phone):
|
|||
|
|
"""Вычисляет индекс бакета и вызывает ll_insert."""
|
|||
|
|
bucket_idx = abs(hash(name)) % len(buckets)
|
|||
|
|
buckets[bucket_idx] = ll_insert(buckets[bucket_idx], name, phone)
|
|||
|
|
|
|||
|
|
def ht_find(buckets, name):
|
|||
|
|
"""Вычисляет индекс бакета и вызывает ll_find."""
|
|||
|
|
bucket_idx = abs(hash(name)) % len(buckets)
|
|||
|
|
return ll_find(buckets[bucket_idx], name)
|
|||
|
|
|
|||
|
|
def ht_delete(buckets, name):
|
|||
|
|
"""Вычисляет индекс бакета и вызывает ll_delete."""
|
|||
|
|
bucket_idx = abs(hash(name)) % len(buckets)
|
|||
|
|
buckets[bucket_idx] = ll_delete(buckets[bucket_idx], name)
|
|||
|
|
|
|||
|
|
def ht_list_all(buckets):
|
|||
|
|
"""Собирает записи из всех бакетов и сортирует их по имени."""
|
|||
|
|
entries = []
|
|||
|
|
for head_node in buckets:
|
|||
|
|
current = head_node
|
|||
|
|
while current is not None:
|
|||
|
|
entries.append((current["name"], current["phone"]))
|
|||
|
|
current = current["next"]
|
|||
|
|
entries.sort(key=lambda item: item[0])
|
|||
|
|
return entries
|
|||
|
|
|
|||
|
|
|
|||
|
|
def bst_insert(root, name, phone):
|
|||
|
|
"""Рекурсивно вставляет узел или обновляет телефон."""
|
|||
|
|
if root is None:
|
|||
|
|
return {"name": name, "phone": phone, "left": None, "right": None}
|
|||
|
|
|
|||
|
|
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):
|
|||
|
|
"""Рекурсивный поиск по дереву."""
|
|||
|
|
if root is None:
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
if name == root["name"]:
|
|||
|
|
return root["phone"]
|
|||
|
|
elif name < root["name"]:
|
|||
|
|
return bst_find(root["left"], name)
|
|||
|
|
else:
|
|||
|
|
return bst_find(root["right"], name)
|
|||
|
|
|
|||
|
|
def bst_delete(root, name):
|
|||
|
|
"""Рекурсивное удаление узла из BST."""
|
|||
|
|
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 = root["right"]
|
|||
|
|
while successor["left"] is not None:
|
|||
|
|
successor = successor["left"]
|
|||
|
|
|
|||
|
|
root["name"] = successor["name"]
|
|||
|
|
root["phone"] = successor["phone"]
|
|||
|
|
root["right"] = bst_delete(root["right"], successor["name"])
|
|||
|
|
|
|||
|
|
return root
|
|||
|
|
|
|||
|
|
def bst_list_all(root):
|
|||
|
|
"""Центрированный обход дерева для сбора записей."""
|
|||
|
|
entries = []
|
|||
|
|
def _inorder(node):
|
|||
|
|
if node is not None:
|
|||
|
|
_inorder(node["left"])
|
|||
|
|
entries.append((node["name"], node["phone"]))
|
|||
|
|
_inorder(node["right"])
|
|||
|
|
|
|||
|
|
_inorder(root)
|
|||
|
|
return entries
|
|||
|
|
|
|||
|
|
|
|||
|
|
def perform_benchmark():
|
|||
|
|
total_records = 10000
|
|||
|
|
random.seed(42)
|
|||
|
|
|
|||
|
|
ordered_records = [(f"User_{i:05d}", f"8-999-123-{i:04d}") for i in range(total_records)]
|
|||
|
|
shuffled_records = ordered_records.copy()
|
|||
|
|
random.shuffle(shuffled_records)
|
|||
|
|
|
|||
|
|
existing_searches = [random.choice(ordered_records)[0] for _ in range(100)]
|
|||
|
|
non_existing_searches = [f"None_{i}" for i in range(10)]
|
|||
|
|
search_queries = existing_searches + non_existing_searches
|
|||
|
|
deletion_targets = [random.choice(ordered_records)[0] for _ in range(50)]
|
|||
|
|
|
|||
|
|
output_rows = [["Structure", "Mode", "Operation", "Time (sec)"]]
|
|||
|
|
graph_entries = []
|
|||
|
|
|
|||
|
|
def execute_trial(structure_kind, data_mode, data_collection):
|
|||
|
|
print(f"Starting: {structure_kind} | Mode: {data_mode}...")
|
|||
|
|
insertion_measurements, search_measurements, deletion_measurements = [], [], []
|
|||
|
|
|
|||
|
|
for trial_num in range(1, 6):
|
|||
|
|
if structure_kind == "LinkedList": container = None
|
|||
|
|
elif structure_kind == "HashTable": container = ht_create(size=1000)
|
|||
|
|
elif structure_kind == "BST": container = None
|
|||
|
|
|
|||
|
|
# А. Вставка
|
|||
|
|
timer_start = time.perf_counter()
|
|||
|
|
if structure_kind == "LinkedList":
|
|||
|
|
for name, phone in data_collection: container = ll_insert(container, name, phone)
|
|||
|
|
elif structure_kind == "HashTable":
|
|||
|
|
for name, phone in data_collection: ht_insert(container, name, phone)
|
|||
|
|
elif structure_kind == "BST":
|
|||
|
|
for name, phone in data_collection: container = bst_insert(container, name, phone)
|
|||
|
|
insert_elapsed = time.perf_counter() - timer_start
|
|||
|
|
insertion_measurements.append(insert_elapsed)
|
|||
|
|
output_rows.append([structure_kind, data_mode, f"insert (trial {trial_num})", f"{insert_elapsed:.6f}"])
|
|||
|
|
|
|||
|
|
# Б. Поиск
|
|||
|
|
timer_start = time.perf_counter()
|
|||
|
|
if structure_kind == "LinkedList":
|
|||
|
|
for name in search_queries: ll_find(container, name)
|
|||
|
|
elif structure_kind == "HashTable":
|
|||
|
|
for name in search_queries: ht_find(container, name)
|
|||
|
|
elif structure_kind == "BST":
|
|||
|
|
for name in search_queries: bst_find(container, name)
|
|||
|
|
search_elapsed = time.perf_counter() - timer_start
|
|||
|
|
search_measurements.append(search_elapsed)
|
|||
|
|
output_rows.append([structure_kind, data_mode, f"find (trial {trial_num})", f"{search_elapsed:.6f}"])
|
|||
|
|
|
|||
|
|
# В. Удаление
|
|||
|
|
timer_start = time.perf_counter()
|
|||
|
|
if structure_kind == "LinkedList":
|
|||
|
|
for name in deletion_targets: container = ll_delete(container, name)
|
|||
|
|
elif structure_kind == "HashTable":
|
|||
|
|
for name in deletion_targets: ht_delete(container, name)
|
|||
|
|
elif structure_kind == "BST":
|
|||
|
|
for name in deletion_targets: container = bst_delete(container, name)
|
|||
|
|
delete_elapsed = time.perf_counter() - timer_start
|
|||
|
|
deletion_measurements.append(delete_elapsed)
|
|||
|
|
output_rows.append([structure_kind, data_mode, f"delete (trial {trial_num})", f"{delete_elapsed:.6f}"])
|
|||
|
|
|
|||
|
|
# Запись средних значений
|
|||
|
|
output_rows.append([structure_kind, data_mode, "Insert (avg)", f"{sum(insertion_measurements)/5:.6f}"])
|
|||
|
|
output_rows.append([structure_kind, data_mode, "Find (avg)", f"{sum(search_measurements)/5:.6f}"])
|
|||
|
|
output_rows.append([structure_kind, data_mode, "Delete (avg)", f"{sum(deletion_measurements)/5:.6f}"])
|
|||
|
|
|
|||
|
|
avg_insertion = sum(insertion_measurements) / 5
|
|||
|
|
avg_search = sum(search_measurements) / 5
|
|||
|
|
avg_deletion = sum(deletion_measurements) / 5
|
|||
|
|
|
|||
|
|
graph_entries.append((structure_kind, data_mode, avg_insertion, avg_search, avg_deletion))
|
|||
|
|
|
|||
|
|
# Запуск всех тестов
|
|||
|
|
execute_trial("LinkedList", "random", shuffled_records)
|
|||
|
|
execute_trial("LinkedList", "sorted", ordered_records)
|
|||
|
|
execute_trial("HashTable", "random", shuffled_records)
|
|||
|
|
execute_trial("HashTable", "sorted", ordered_records)
|
|||
|
|
execute_trial("BST", "random", shuffled_records)
|
|||
|
|
execute_trial("BST", "sorted", ordered_records)
|
|||
|
|
|
|||
|
|
# Сохранение в CSV
|
|||
|
|
with open("benchmark_output.csv", "w", newline="", encoding="utf-8") as csv_file:
|
|||
|
|
csv_writer = csv.writer(csv_file)
|
|||
|
|
csv_writer.writerows(output_rows)
|
|||
|
|
print("\n[Success] All benchmarks completed! Results saved to 'benchmark_output.csv'.")
|
|||
|
|
|
|||
|
|
# ВЫЗЫВАЕМ ФУНКЦИЮ ДЛЯ СОЗДАНИЯ ГРАФИКОВ
|
|||
|
|
generate_performance_charts(graph_entries)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def generate_performance_charts(plot_data):
|
|||
|
|
"""
|
|||
|
|
Создает графики производительности структур данных.
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
plot_data: список кортежей (structure, mode, avg_insert, avg_find, avg_delete)
|
|||
|
|
"""
|
|||
|
|
if not plot_data:
|
|||
|
|
print("Нет данных для построения графиков")
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
# Подготовка данных
|
|||
|
|
structures = ['LinkedList', 'HashTable', 'BST']
|
|||
|
|
modes = ['random', 'sorted']
|
|||
|
|
operations = ['Insert', 'Find', 'Delete']
|
|||
|
|
|
|||
|
|
# Создаем фигуру с тремя подграфиками
|
|||
|
|
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
|||
|
|
|
|||
|
|
for idx, operation in enumerate(operations):
|
|||
|
|
ax = axes[idx]
|
|||
|
|
|
|||
|
|
# Данные для текущей операции
|
|||
|
|
x_positions = []
|
|||
|
|
y_values = []
|
|||
|
|
labels = []
|
|||
|
|
|
|||
|
|
for structure in structures:
|
|||
|
|
for mode in modes:
|
|||
|
|
# Находим данные для этой комбинации
|
|||
|
|
for data in plot_data:
|
|||
|
|
if data[0] == structure and data[1] == mode:
|
|||
|
|
value = data[2 + idx] # 2=insert, 3=find, 4=delete
|
|||
|
|
x_positions.append(len(x_positions))
|
|||
|
|
y_values.append(value)
|
|||
|
|
labels.append(f"{structure}\n{mode}")
|
|||
|
|
|
|||
|
|
# Создаем столбчатую диаграмму
|
|||
|
|
bars = ax.bar(x_positions, y_values, color=['skyblue', 'lightcoral']*3)
|
|||
|
|
|
|||
|
|
# Настройка графика
|
|||
|
|
ax.set_title(f'Операция: {operation}', fontsize=14, fontweight='bold')
|
|||
|
|
ax.set_ylabel('Время (сек)', fontsize=12)
|
|||
|
|
ax.set_xticks(x_positions)
|
|||
|
|
ax.set_xticklabels(labels, rotation=45, ha='right', fontsize=10)
|
|||
|
|
ax.grid(axis='y', alpha=0.3)
|
|||
|
|
|
|||
|
|
# Добавляем значения над столбцами
|
|||
|
|
for bar, value in zip(bars, y_values):
|
|||
|
|
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height(),
|
|||
|
|
f'{value:.4f}', ha='center', va='bottom', fontsize=8)
|
|||
|
|
|
|||
|
|
plt.tight_layout()
|
|||
|
|
plt.savefig('performance_charts.png', dpi=300, bbox_inches='tight')
|
|||
|
|
plt.show()
|
|||
|
|
print("Графики сохранены в 'performance_charts.png'")
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == '__main__':
|
|||
|
|
benchmark_thread = threading.Thread(target=perform_benchmark)
|
|||
|
|
benchmark_thread.start()
|
|||
|
|
benchmark_thread.join()
|
|||
|
|
|
|||
|
|
# Дополнительно: если графики не показались автоматически
|
|||
|
|
# Можно вызвать функцию напрямую после завершения потока
|
|||
|
|
print("\nПрограмма завершена. Проверьте файл 'performance_charts.png'")
|