import sys sys.setrecursionlimit(30000) # Увеличиваю лимит рекурсии для BST # Связный список def ll_insert(head, name, phone): new_node = {'name': name, 'phone': phone, 'next': None} # Создаю новый узел if head is None: # Если список пуст return new_node # Возвращаю узел как голову curr = head # Указатель для обхода prev = None # Храню предыдущий узел while curr is not None: # Иду по списку if curr['name'] == name: # Если нашел такое же имя curr['phone'] = phone # Обновляю телефон return head prev = curr curr = curr['next'] prev['next'] = new_node # Добавляю в конец 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'] # Перепрыгиваю через него return head curr = curr['next'] return head def ll_list_all(head): result = [] curr = head while curr: # Собираю все элементы result.append((curr['name'], curr['phone'])) curr = curr['next'] result.sort(key=lambda x: x[0]) # Сортирую по имени return result # Хэш-таблица HASH_SIZE = 1009 # Размер таблицы - простое число def _hash_name(name): return hash(name) % HASH_SIZE # Беру остаток от деления - это индекс корзины def ht_insert(buckets, name, phone): idx = _hash_name(name) # Вычисляю индекс корзины buckets[idx] = ll_insert(buckets[idx], name, phone) # Метод цепочек - вставляю в список def ht_find(buckets, name): idx = _hash_name(name) # Нахожу корзину return ll_find(buckets[idx], name) # Ищу в цепочке def ht_delete(buckets, name): idx = _hash_name(name) # Нахожу корзину buckets[idx] = ll_delete(buckets[idx], name) # Удаляю из цепочки def ht_list_all(buckets): all_entries = [] for bucket in buckets: # Прохожу по всем корзинам if bucket is not None: curr = bucket while curr: # Собираю всю цепочку all_entries.append((curr['name'], curr['phone'])) curr = curr['next'] all_entries.sort(key=lambda x: x[0]) return all_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['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']: # Иду до самого левого 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): result = [] def inorder(node): # Симметричный обход if node: inorder(node['left']) # Сначала левое result.append((node['name'], node['phone'])) # Потом корень inorder(node['right']) # Потом правое inorder(root) return result # ============================================================ # TECT # ============================================================ import os import random import time import csv import pandas as pd import matplotlib.pyplot as plt # ============================================================ # ПОДГОТОВКА ПАПОК # ============================================================ DATA_DIR = os.path.join("docs", "data") os.makedirs(DATA_DIR, exist_ok=True) csv_path = os.path.join(DATA_DIR, "lab1_results.csv") graph_path = os.path.join(DATA_DIR, "lab1_graph.png") # ============================================================ # ТЕСТОВЫЕ ДАННЫЕ # ============================================================ random.seed(42) # Фиксирую seed для повторяемости N = 3000 # 3000 записей base_records = [ (f"User_{i:05d}", f"123-{i:05d}") for i in range(N) ] records_shuffled = base_records.copy() random.shuffle(records_shuffled) # Перемешанный порядок records_sorted = sorted(base_records, key=lambda x: x[0]) # Отсортированный порядок # Данные для поиска search_existing = [ name for name, _ in random.sample(base_records, 100) # 100 существующих имен ] search_nonexist = [ f"None_{i}" for i in range(10) # 10 несуществующих имен ] # Данные для удаления delete_names = [ name for name, _ in random.sample(base_records, 50) # 50 имен для удаления ] # ============================================================ # СОЗДАНИЕ СТРУКТУР # ============================================================ def build_structure(records, struct_type): if struct_type == "ll": structure = None for name, phone in records: structure = ll_insert(structure, name, phone) # Последовательная вставка return structure elif struct_type == "ht": structure = [None] * HASH_SIZE for name, phone in records: ht_insert(structure, name, phone) # Вставка с хэшированием return structure elif struct_type == "bst": structure = None for name, phone in records: structure = bst_insert(structure, name, phone) # Вставка с ветвлением return structure # ============================================================ # INSERT # ============================================================ def measure_insert(records, struct_type): start = time.perf_counter() build_structure(records, struct_type) # Замеряю время построения структуры end = time.perf_counter() return end - start # ============================================================ # SEARCH # ============================================================ def measure_search(records, struct_type): structure = build_structure(records, struct_type) # Строю структуру start = time.perf_counter() if struct_type == "ll": for name in search_existing + search_nonexist: ll_find(structure, name) # Поиск перебором elif struct_type == "ht": for name in search_existing + search_nonexist: ht_find(structure, name) # Поиск через хэш elif struct_type == "bst": for name in search_existing + search_nonexist: bst_find(structure, name) # Поиск спуском по дереву end = time.perf_counter() return end - start # ============================================================ # DELETE # ============================================================ def measure_delete(records, struct_type): structure = build_structure(records, struct_type) # Строю структуру start = time.perf_counter() if struct_type == "ll": for name in delete_names: structure = ll_delete(structure, name) # Удаление со сдвигом elif struct_type == "ht": for name in delete_names: ht_delete(structure, name) # Удаление из цепочки elif struct_type == "bst": for name in delete_names: structure = bst_delete(structure, name) # Удаление с ребалансировкой end = time.perf_counter() return end - start # ============================================================ # ЗАМЕРЫ # ============================================================ all_data = [] experiments = [ ("LinkedList", "ll"), ("HashTable", "ht"), ("BST", "bst") ] modes = [ ("shuffled", records_shuffled), # Тест на случайных данных ("sorted", records_sorted) # Тест на отсортированных данных ] for struct_name, struct_type in experiments: for mode_name, records in modes: for rep in range(1, 4): # 3 повтора для усреднения insert_time = measure_insert(records, struct_type) search_time = measure_search(records, struct_type) delete_time = measure_delete(records, struct_type) all_data.append([struct_name, mode_name, rep, "insert", insert_time]) all_data.append([struct_name, mode_name, rep, "search", search_time]) all_data.append([struct_name, mode_name, rep, "delete", delete_time]) # ============================================================ # CSV # ============================================================ with open(csv_path, "w", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow(["Структура", "Режим", "Повтор", "Операция", "Время (сек)"]) writer.writerows(all_data) print(f"CSV сохранён: {csv_path}") # ============================================================ # ГРАФИК # ============================================================ df = pd.read_csv(csv_path) df_avg = ( df.groupby(["Структура", "Режим", "Операция"])["Время (сек)"] .mean() .reset_index() ) fig, ax = plt.subplots(figsize=(12, 6)) ops = ["insert", "search", "delete"] x = range(len(ops)) width = 0.12 configs = [ ("LinkedList", "shuffled"), ("LinkedList", "sorted"), ("HashTable", "shuffled"), ("HashTable", "sorted"), ("BST", "shuffled"), ("BST", "sorted") ] for i, (struct, mode) in enumerate(configs): subset = df_avg[ (df_avg["Структура"] == struct) & (df_avg["Режим"] == mode) ] times = [ subset[subset["Операция"] == op]["Время (сек)"].values[0] for op in ops ] ax.bar( [p + i * width for p in x], times, width, label=f"{struct} ({mode})" ) ax.set_xticks([p + 2.5 * width for p in x]) ax.set_xticklabels(ops) ax.set_ylabel("Среднее время (сек)") ax.set_title("Сравнение структур данных") ax.legend(bbox_to_anchor=(1.05, 1), loc="upper left") plt.tight_layout() plt.savefig(graph_path) print(f"График сохранён: {graph_path}") plt.show()