2026-rff_mp/ivankinad/task1/laba1.py

312 lines
12 KiB
Python
Raw Normal View History

2026-09-04 20:39:33 +00:00
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'")