2026-rff_mp/ivankinad/task1/laba1.py
2026-09-04 23:39:33 +03:00

312 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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'")