2026-rff_mp/smirnovad/lab1/docs/data/lab1.py
2026-05-17 15:36:14 +03:00

221 lines
9.3 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 time
import random
import csv
from pathlib import Path
import matplotlib.pyplot as plt
import sys
# Увеличиваем лимит рекурсии для работы с глубокими деревьями (особенно на сортированных данных)
sys.setrecursionlimit(15000)
# Настройка путей (используем pathlib для гибкости)
ROOT_DIR = Path(r"C:\Users\andre\2026-rff_mp\smirnovad\lab1")
DOCS_DIR = ROOT_DIR / "docs"
DATA_DIR = DOCS_DIR / "data"
# Создание необходимых директорий
DATA_DIR.mkdir(parents=True, exist_ok=True)
# --- 1. СВЯЗНЫЙ СПИСОК (LinkedList) ---
def ll_insert(first_node, name, phone):
"""Добавление в начало списка (O(1))"""
return {'name': name, 'phone': phone, 'next': first_node}
def ll_find(first_node, name):
"""Линейный поиск по имени"""
item = first_node
while item:
if item['name'] == name:
return item['phone']
item = item['next']
return None
def ll_delete(first_node, name):
"""Удаление узла по имени"""
if not first_node:
return None
if first_node['name'] == name:
return first_node['next']
prev = first_node
while prev['next']:
if prev['next']['name'] == name:
prev['next'] = prev['next']['next']
return first_node
prev = prev['next']
return first_node
def ll_list_all(first_node):
"""Вывод всех записей в алфавитном порядке"""
result_list = []
item = first_node
while item:
result_list.append((item['name'], item['phone']))
item = item['next']
return sorted(result_list)
# --- 2. ХЕШ-ТАБЛИЦА (Hash Table) ---
def ht_insert(hash_table, name, phone):
slot = hash(name) % len(hash_table)
hash_table[slot] = ll_insert(hash_table[slot], name, phone)
def ht_find(hash_table, name):
slot = hash(name) % len(hash_table)
return ll_find(hash_table[slot], name)
def ht_delete(hash_table, name):
slot = hash(name) % len(hash_table)
hash_table[slot] = ll_delete(hash_table[slot], name)
def ht_list_all(hash_table):
"""Сбор данных из всех бакетов"""
total_data = []
for bucket in hash_table:
node = bucket
while node:
total_data.append((node['name'], node['phone']))
node = node['next']
return sorted(total_data)
# --- 3. ДВОИЧНОЕ ДЕРЕВО ПОИСКА (BST) ---
def bst_insert(root, name, phone):
if not root:
return {'name': name, 'phone': phone, 'left': None, 'right': None}
if name < root['name']:
root['left'] = bst_insert(root['left'], name, phone)
elif name > root['name']:
root['right'] = bst_insert(root['right'], name, phone)
else:
root['phone'] = phone
return root
def bst_find(root, name):
if not root:
return None
if root['name'] == name:
return root['phone']
if name < root['name']:
return bst_find(root['left'], name)
return bst_find(root['right'], name)
def bst_delete(root, name):
"""Удаление узла в BST"""
if not root:
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 not root['left']: return root['right']
if not root['right']: return root['left']
# Поиск минимального в правом поддереве
min_node = root['right']
while min_node['left']:
min_node = min_node['left']
root['name'], root['phone'] = min_node['name'], min_node['phone']
root['right'] = bst_delete(root['right'], min_node['name'])
return root
# --- ЭКСПЕРИМЕНТАЛЬНАЯ ЧАСТЬ ---
log_entries = []
stats_summary = []
def run_test(structure_name, data_mode, dataset):
print(f"Тестирование: {structure_name} | Режим: {data_mode}")
t_ins, t_find, t_del = [], [], []
for run_idx in range(5): # 5 итераций
# Инициализация хранилища
storage = [None] * 1024 if structure_name == "HashTable" else None
# 1. Замер вставки
start = time.perf_counter()
for n, p in dataset:
if structure_name == "LinkedList": storage = ll_insert(storage, n, p)
elif structure_name == "HashTable": ht_insert(storage, n, p)
elif structure_name == "BST": storage = bst_insert(storage, n, p)
t_ins.append(time.perf_counter() - start)
# 2. Замер поиска (100 существующих + 10 отсутствующих)
test_names = [x[0] for x in random.sample(dataset, 100)] + [f"Missing_{j}" for j in range(10)]
start = time.perf_counter()
for name_to_find in test_names:
if structure_name == "LinkedList": ll_find(storage, name_to_find)
elif structure_name == "HashTable": ht_find(storage, name_to_find)
elif structure_name == "BST": bst_find(storage, name_to_find)
t_find.append(time.perf_counter() - start)
# 3. Замер удаления (50 записей)
test_dels = [x[0] for x in random.sample(dataset, 50)]
start = time.perf_counter()
for name_to_del in test_dels:
if structure_name == "LinkedList": storage = ll_delete(storage, name_to_del)
elif structure_name == "HashTable": ht_delete(storage, name_to_del)
elif structure_name == "BST": bst_delete(storage, name_to_del)
t_del.append(time.perf_counter() - start)
log_entries.append([structure_name, data_mode, f"Run_{run_idx+1}", t_ins[-1], t_find[-1], t_del[-1]])
# Считаем среднее
avg_i, avg_f, avg_d = sum(t_ins)/5, sum(t_find)/5, sum(t_del)/5
stats_summary.append({"type": structure_name, "mode": data_mode, "ins": avg_i, "find": avg_f, "del": avg_d})
# Генерация данных
N_COUNT = 10000
raw_data = [(f"User_{i:05d}", f"{random.randint(100, 999)}-{random.randint(10, 99)}") for i in range(N_COUNT)]
data_shuffled = random.sample(raw_data, len(raw_data))
data_sorted = sorted(raw_data)
# Запуск тестов
for mode_label, data_src in [("random", data_shuffled), ("sorted", data_sorted)]:
for s_kind in ["LinkedList", "HashTable", "BST"]:
run_test(s_kind, mode_label, data_src)
# Сохранение CSV
with open(DATA_DIR / "performance_stats.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["Structure", "Input_Mode", "Iteration", "Insert_Sec", "Find_Sec", "Delete_Sec"])
writer.writerows(log_entries)
# Построение графиков
def generate_visuals():
ops = ["Вставка", "Поиск", "Удаление"]
structs = ["LinkedList", "HashTable", "BST"]
palette = ["#3498db", "#9b59b6", "#2ecc71"] # Другие цвета
# График влияния порядка
fig, axes = plt.subplots(1, 3, figsize=(16, 5))
fig.suptitle("Анализ влияния упорядоченности данных", fontsize=14)
for idx, s_name in enumerate(structs):
r_vals = next(s for s in stats_summary if s['type'] == s_name and s['mode'] == "random")
s_vals = next(s for s in stats_summary if s['type'] == s_name and s['mode'] == "sorted")
pos = [0, 1, 2]
axes[idx].bar([p - 0.2 for p in pos], [r_vals['ins'], r_vals['find'], r_vals['del']], 0.4, label='Random', color=palette[0])
axes[idx].bar([p + 0.2 for p in pos], [s_vals['ins'], s_vals['find'], s_vals['del']], 0.4, label='Sorted', color="#e74c3c")
axes[idx].set_title(s_name)
axes[idx].set_xticks(pos)
axes[idx].set_xticklabels(ops)
axes[idx].legend()
plt.tight_layout()
plt.savefig(DATA_DIR / "impact_analysis.png")
generate_visuals()
# Генерация отчета
with open(DOCS_DIR / "LAB_REPORT.md", "w", encoding="utf-8") as f:
f.write("# Лабораторная работа №1: Структуры данных\n\n")
f.write(f"Выполнен замер производительности на выборке N={N_COUNT}.\n")
f.write("## Сводная таблица (Средние значения)\n")
f.write("| Тип | Режим | Вставка | Поиск | Удаление |\n| :--- | :--- | :--- | :--- | :--- |\n")
for s in stats_summary:
f.write(f"| {s['type']} | {s['mode']} | {s['ins']:.5f} | {s['find']:.5f} | {s['del']:.5f} |\n")
f.write("\n## Основные выводы\n")
f.write("1. **BST** крайне чувствителен к порядку: на отсортированных данных скорость падает из-за превращения дерева в список.\n")
f.write("2. **HashTable** — самая стабильная структура, время операций почти не зависит от входной последовательности.\n")
f.write("3. **LinkedList** показывает худшее время на операциях поиска из-за необходимости полного перебора.")
print(f"Все файлы успешно сохранены в {DOCS_DIR}")