import random import pandas as pd import time import sys import os import matplotlib.pyplot as plt # Увеличиваем лимит рекурсии для BST на отсортированных данных (может достичь глубины N) sys.setrecursionlimit(20000) # ========================================================= # 1. СВЯЗНЫЙ СПИСОК (LinkedListPhoneBook) # ========================================================= def ll_insert(head, name, phone): if head is None: return {'name': name, 'phone': phone, 'next': None} curr = head while True: if curr['name'] == name: curr['phone'] = phone # Обновление существующей записи break if curr['next'] is None: curr['next'] = {'name': name, 'phone': phone, 'next': None} break curr = curr['next'] return head def ll_find(head, name): curr = head while curr: if curr['name'] == name: return curr['phone'] curr = curr['next'] return None def ll_delete(head, name): if head is None: return None if head['name'] == name: return head['next'] curr = head while curr['next']: if curr['next']['name'] == name: curr['next'] = curr['next']['next'] break curr = curr['next'] return head def ll_list_all(head): res = [] curr = head while curr: res.append((curr['name'], curr['phone'])) curr = curr['next'] res.sort(key=lambda x: x[0]) return res # ========================================================= # 2. ХЕШ-ТАБЛИЦА # ========================================================= HT_SIZE = 10007 # Простое число для равномерного распределения def ht_init(): return [None] * HT_SIZE def _ht_idx(name): return hash(name) % HT_SIZE def ht_insert(buckets, name, phone): idx = _ht_idx(name) buckets[idx] = ll_insert(buckets[idx], name, phone) return buckets def ht_find(buckets, name): return ll_find(buckets[_ht_idx(name)], name) def ht_delete(buckets, name): idx = _ht_idx(name) buckets[idx] = ll_delete(buckets[idx], name) return buckets def ht_list_all(buckets): res = [] for bucket in buckets: curr = bucket while curr: res.append((curr['name'], curr['phone'])) curr = curr['next'] res.sort(key=lambda x: x[0]) return res # ========================================================= # 3. ДВОИЧНОЕ ДЕРЕВО ПОИСКА (BST) # ========================================================= def bst_insert(root, name, phone): if root is None: 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): curr = root while curr: if name == curr['name']: return curr['phone'] elif name < curr['name']: curr = curr['left'] else: curr = curr['right'] return None 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'] if root['right'] is None: return root['left'] # Два потомка: находим минимальный в правом поддереве min_node = root['right'] while min_node['left'] is not None: min_node = min_node['left'] 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): if root is None: return [] return bst_list_all(root['left']) + [(root['name'], root['phone'])] + bst_list_all(root['right']) # ========================================================= # ЭКСПЕРИМЕНТАЛЬНАЯ ЧАСТЬ # ========================================================= def run_experiments(): N = 10000 RECORDS = [(f"User_{i:05d}", f"+7900{i:04d}{i%100:02d}") for i in range(N)] records_shuffled = RECORDS[:] random.shuffle(records_shuffled) records_sorted = sorted(RECORDS, key=lambda x: x[0]) # Наборы для поиска и удаления existing_names = [r[0] for r in random.sample(RECORDS, 100)] non_existing_names = [f"None_{i}" for i in range(10)] find_names = existing_names + non_existing_names delete_names = [r[0] for r in random.sample(RECORDS, 50)] structures = { "LinkedList": (lambda: None, ll_insert, ll_find, ll_delete), "HashTable": (ht_init, ht_insert, ht_find, ht_delete), "BST": (lambda: None, bst_insert, bst_find, bst_delete) } modes = {"случайный": records_shuffled, "отсортированный": records_sorted} results = [] print("Запуск экспериментов...") trials = 5 for struct_name, (init_f, ins_f, find_f, del_f) in structures.items(): for mode_name, data in modes.items(): print(f" {struct_name} | {mode_name}") for t in range(1, trials + 1): # Инициализация ds = init_f() # A. Вставка t0 = time.perf_counter() for name, phone in data: ds = ins_f(ds, name, phone) t_ins = time.perf_counter() - t0 # B. Поиск t0 = time.perf_counter() for name in find_names: find_f(ds, name) t_find = time.perf_counter() - t0 # C. Удаление t0 = time.perf_counter() for name in delete_names: ds = del_f(ds, name) t_del = time.perf_counter() - t0 results.append([struct_name, mode_name, "вставка", t, t_ins]) results.append([struct_name, mode_name, "поиск", t, t_find]) results.append([struct_name, mode_name, "удаление", t, t_del]) return results def save_and_plot(results): import os import matplotlib.pyplot as plt import pandas as pd os.makedirs("docs/data", exist_ok=True) # 1. Сохранение CSV (как было) df = pd.DataFrame(results, columns=["Структура", "Режим", "Операция", "Повторение", "Время (сек)"]) avg = df.groupby(["Структура", "Режим", "Операция"])["Время (сек)"].mean().reset_index() avg["Повторение"] = "СРЕДНЕЕ" df_full = pd.concat([df, avg], ignore_index=True) df_full.to_csv("docs/data/results.csv", index=False, encoding="utf-8-sig") # 2. Улучшенный график: 3 отдельных подграфика + логарифмическая шкала fig, axes = plt.subplots(1, 3, figsize=(18, 6)) operations = ["вставка", "поиск", "удаление"] structures_order = ["HashTable", "BST", "LinkedList"] # Фиксируем порядок для удобства чтения colors = {"случайный": "#6C157F", "отсортированный": "#1E299F"} for ax, op in zip(axes, operations): op_data = avg[avg["Операция"] == op] pivot = op_data.pivot(index="Структура", columns="Режим", values="Время (сек)") pivot = pivot.reindex(structures_order) # Ставим структуры в удобном порядке pivot.plot(kind="bar", ax=ax, color=[colors["случайный"], colors["отсортированный"]], width=0.75) ax.set_title(f"Операция: {op.capitalize()}") ax.set_ylabel("Время (сек)") ax.set_xticklabels(ax.get_xticklabels(), rotation=0) ax.grid(axis="y", alpha=0.3, linestyle="--") # ЛОГАРИФМИЧЕСКАЯ ШКАЛА: обязательна при разбросе от 0.0001 до 30 сек ax.set_yscale("log") ax.legend(title="Режим", loc="upper right") fig.suptitle("Сравнение производительности структур данных", fontsize=16, y=1.05) plt.tight_layout() plt.savefig("docs/data/plot.png", dpi=200, bbox_inches="tight") if __name__ == "__main__": res = run_experiments() save_and_plot(res) print("Эксперимент завершен")