import time import random import csv import matplotlib.pyplot as plt import numpy as np import sys from collections import defaultdict # Увеличиваем лимит рекурсии для BST sys.setrecursionlimit(10000) def ll_insert(head, name, phone): current = head while current: if current['name'] == name: current['phone'] = phone return head current = current['next'] new_node = {'name': name, 'phone': phone, 'next': head} return new_node def ll_find(head, name): current = head while current: if current['name'] == name: return current['phone'] current = current['next'] return None def ll_delete(head, name): if not head: return None if head['name'] == name: return head['next'] current = head while current['next']: if current['next']['name'] == name: current['next'] = current['next']['next'] return head current = current['next'] return head def ll_list_all(head): records = [] current = head while current: records.append((current['name'], current['phone'])) current = current['next'] records.sort(key=lambda x: x[0]) return records def hash_function(name, size): return sum(ord(c) for c in name) % size def ht_create(size=1000): return [None] * size def ht_insert(buckets, name, phone): index = hash_function(name, len(buckets)) buckets[index] = ll_insert(buckets[index], name, phone) def ht_find(buckets, name): index = hash_function(name, len(buckets)) return ll_find(buckets[index], name) def ht_delete(buckets, name): index = hash_function(name, len(buckets)) buckets[index] = ll_delete(buckets[index], name) def ht_list_all(buckets): records = [] for bucket in buckets: current = bucket while current: records.append((current['name'], current['phone'])) current = current['next'] records.sort(key=lambda x: x[0]) return records def bst_insert(root, name, phone): """Итеративная вставка для избежания RecursionError""" new_node = {'name': name, 'phone': phone, 'left': None, 'right': None} if root is None: return new_node current = root while True: if name < current['name']: if current['left'] is None: current['left'] = new_node break current = current['left'] elif name > current['name']: if current['right'] is None: current['right'] = new_node break current = current['right'] else: current['phone'] = phone break return root def bst_find(root, name): current = root while current: if name == current['name']: return current['phone'] elif name < current['name']: current = current['left'] else: current = current['right'] return None def bst_find_min(node): current = node while current and current['left']: current = current['left'] return current def bst_delete(root, name): 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'] elif root['right'] is None: return root['left'] min_node = bst_find_min(root['right']) root['name'] = min_node['name'] root['phone'] = min_node['phone'] root['right'] = bst_delete(root['right'], min_node['name']) return root def bst_list_all(root): records = [] stack = [] current = root while stack or current: while current: stack.append(current) current = current['left'] current = stack.pop() records.append((current['name'], current['phone'])) current = current['right'] return records def copy_linked_list(head): if not head: return None new_head = {'name': head['name'], 'phone': head['phone'], 'next': None} current_new = new_head current_old = head['next'] while current_old: current_new['next'] = {'name': current_old['name'], 'phone': current_old['phone'], 'next': None} current_new = current_new['next'] current_old = current_old['next'] return new_head def copy_bst(node): if not node: return None return { 'name': node['name'], 'phone': node['phone'], 'left': copy_bst(node['left']), 'right': copy_bst(node['right']) } def generate_test_data(N=10000): names = [f"User_{i:05d}" for i in range(N)] records = [(name, f"+7-999-{random.randint(1000000, 9999999)}") for name in names] records_shuffled = records.copy() random.shuffle(records_shuffled) records_sorted = sorted(records, key=lambda x: x[0]) return records_shuffled, records_sorted def get_test_queries(records, num_existing=100, num_nonexisting=10): existing_names = [name for name, _ in random.sample(records, min(num_existing, len(records)))] nonexisting_names = [f"None_{i:05d}" for i in range(num_nonexisting)] queries = existing_names + nonexisting_names random.shuffle(queries) return queries def get_delete_names(records, num_to_delete=50): return [name for name, _ in random.sample(records, min(num_to_delete, len(records)))] def measure_insertion(structure_type, records, repeats=3): times = [] for _ in range(repeats): if structure_type == "LinkedList": structure = None insert_func = ll_insert elif structure_type == "HashTable": structure = ht_create(2000) insert_func = ht_insert elif structure_type == "BST": structure = None insert_func = bst_insert else: raise ValueError(f"Unknown structure: {structure_type}") start = time.perf_counter() for name, phone in records: if structure_type == "HashTable": insert_func(structure, name, phone) else: structure = insert_func(structure, name, phone) end = time.perf_counter() times.append(end - start) return times def measure_search(structure_type, structure, queries, repeats=3): times = [] for _ in range(repeats): start = time.perf_counter() for name in queries: if structure_type == "LinkedList": ll_find(structure, name) elif structure_type == "HashTable": ht_find(structure, name) elif structure_type == "BST": bst_find(structure, name) end = time.perf_counter() times.append(end - start) return times def measure_deletion(structure_type, structure, names_to_delete, repeats=3): times = [] for _ in range(repeats): if structure_type == "LinkedList": temp_structure = copy_linked_list(structure) delete_func = ll_delete elif structure_type == "HashTable": temp_structure = structure.copy() for i in range(len(temp_structure)): if temp_structure[i]: temp_structure[i] = copy_linked_list(temp_structure[i]) delete_func = ht_delete elif structure_type == "BST": temp_structure = copy_bst(structure) delete_func = bst_delete start = time.perf_counter() for name in names_to_delete: if structure_type == "HashTable": delete_func(temp_structure, name) else: temp_structure = delete_func(temp_structure, name) end = time.perf_counter() times.append(end - start) return times def run_experiment(N=2000): """Запускает все эксперименты и возвращает результаты""" print(f"Генерация тестовых данных (N={N})...") records_shuffled, records_sorted = generate_test_data(N) queries = get_test_queries(records_shuffled, num_existing=100, num_nonexisting=10) delete_names = get_delete_names(records_shuffled, num_to_delete=50) structures = ["LinkedList", "HashTable", "BST"] modes = ["случайный", "отсортированный"] results = [] print("\nНачало экспериментов:") print("=" * 60) for structure in structures: print(f"\nТестирование {structure}...") for mode in modes: print(f" Режим: {mode}") records = records_shuffled if mode == "случайный" else records_sorted # Вставка print(f" Измерение вставки...") try: insert_times = measure_insertion(structure, records, repeats=3) avg_insert = sum(insert_times) / len(insert_times) except RecursionError: print(f" ОШИБКА: Превышена глубина рекурсии при вставке в {structure} для {mode} режима") continue print(f" Создание финальной структуры...") if structure == "LinkedList": final_structure = None for name, phone in records: final_structure = ll_insert(final_structure, name, phone) elif structure == "HashTable": final_structure = ht_create(2000) for name, phone in records: ht_insert(final_structure, name, phone) elif structure == "BST": final_structure = None for name, phone in records: final_structure = bst_insert(final_structure, name, phone) print(f" Измерение поиска...") search_times = measure_search(structure, final_structure, queries, repeats=3) avg_search = sum(search_times) / len(search_times) print(f" Измерение удаления...") deletion_times = measure_deletion(structure, final_structure, delete_names, repeats=3) avg_deletion = sum(deletion_times) / len(deletion_times) results.append({ "Структура": structure, "Режим": mode, "Операция": "вставка", "Замеры": insert_times, "Среднее": avg_insert }) results.append({ "Структура": structure, "Режим": mode, "Операция": "поиск", "Замеры": search_times, "Среднее": avg_search }) results.append({ "Структура": structure, "Режим": mode, "Операция": "удаление", "Замеры": deletion_times, "Среднее": avg_deletion }) print(f" Вставка: {avg_insert:.6f} сек") print(f" Поиск: {avg_search:.6f} сек") print(f" Удаление: {avg_deletion:.6f} сек") return results # ============================================================ # СОХРАНЕНИЕ РЕЗУЛЬТАТОВ В CSV # ============================================================ import os import csv from datetime import datetime def save_to_csv(results, filename="results.csv"): save_dir = "/Users/mariiaos/2026-rff_mp/osipovamd/docs" filepath = os.path.join(save_dir, filename) with open(filepath, "w", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow(["Структура", "Режим", "Операция", "Замер1", "Замер2", "Замер3", "Среднее"]) for res in results: row = [ res["Структура"], res["Режим"], res["Операция"], *[f"{t:.6f}" for t in res["Замеры"]], f"{res['Среднее']:.6f}" ] writer.writerow(row) print(f"\nРезультаты сохранены в: {filepath}") return filepath def plot_results(results): if not results: print("Нет данных для построения графиков!") return plt.style.use('seaborn-v0_8-darkgrid') fig, axes = plt.subplots(1, 3, figsize=(15, 5)) operations = ["вставка", "поиск", "удаление"] structures = ["LinkedList", "HashTable", "BST"] modes = ["случайный", "отсортированный"] colors = {'LinkedList': '#FF6B6B', 'HashTable': '#4ECDC4', 'BST': '#45B7D1'} for idx, operation in enumerate(operations): ax = axes[idx] x = np.arange(len(modes)) width = 0.25 multiplier = 0 for structure in structures: values = [] for mode in modes: found = False for res in results: if (res["Структура"] == structure and res["Режим"] == mode and res["Операция"] == operation): values.append(res["Среднее"]) found = True break if not found: values.append(0) if max(values) > 0: offset = width * multiplier bars = ax.bar(x + offset, values, width, label=structure, color=colors[structure]) multiplier += 1 ax.set_xlabel('Режим данных', fontsize=12) ax.set_ylabel('Время (секунды)', fontsize=12) ax.set_title(f'{operation.capitalize()}', fontsize=14, fontweight='bold') ax.set_xticks(x + width) ax.set_xticklabels(modes) ax.legend(loc='upper left') ax.grid(True, alpha=0.3) plt.suptitle('Сравнение производительности структур данных', fontsize=16, fontweight='bold') plt.tight_layout() plt.savefig('performance_comparison.png', dpi=300, bbox_inches='tight') plt.show() print("\nГрафик сохранён как 'performance_comparison.png'") if __name__ == "__main__": print("=" * 60) print("ТЕСТИРОВАНИЕ ПРОИЗВОДИТЕЛЬНОСТИ СТРУКТУР ДАННЫХ") print("=" * 60) results = run_experiment(N=1000) save_to_csv(results) if results: print("\nПостроение графиков...") plot_results(results) print("\n" + "=" * 60) print("СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ (среднее время в секундах)") print("=" * 60) print(f"{'Структура':<12} {'Режим':<12} {'Вставка':<10} {'Поиск':<10} {'Удаление':<10}") print("-" * 60) for structure in ["LinkedList", "HashTable", "BST"]: for mode in ["случайный", "отсортированный"]: insert_time = search_time = delete_time = 0 for res in results: if res["Структура"] == structure and res["Режим"] == mode: if res["Операция"] == "вставка": insert_time = res["Среднее"] elif res["Операция"] == "поиск": search_time = res["Среднее"] elif res["Операция"] == "удаление": delete_time = res["Среднее"] if insert_time > 0 or search_time > 0 or delete_time > 0: print(f"{structure:<12} {mode:<12} {insert_time:<10.6f} {search_time:<10.6f} {delete_time:<10.6f}") else: print("\nЭксперимент не дал результатов из-за ошибок.") print("\nЭксперимент завершён!")