import random import time import csv import os import traceback import sys sys.setrecursionlimit(30000) def ll_insert(head, name, phone): """ проходит до конца (или сразу добавляет в конец) возвращает новую голову (если вставка в начало) или изменяет список по ссылке. """ new_node = {'name': name, 'phone': phone, 'next': None} # в случае пустого списка, новый узел становится головой if head is None: return new_node # в противном случае проходим в конец списка current = head while current['next'] is not None: if current['name'] == name: current['phone'] = phone return head current = current['next'] # проверяем последний узел if current['name'] == name: current['phone'] = phone else: current['next'] = new_node return head 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): """ удаляет узел по имени. возвращает новую голову. """ # если список пуст if head is None: return None # если удаляем голову if head['name'] == name: new_head = head['next'] head['next'] = None return new_head # ищем узел для удаления current = head while current['next'] is not None: if current['next']['name'] == name: target = current['next'] current['next'] = target['next'] target['next'] = None # разрываем связь у удаляемого узла (иными словами, обнуление ссылки) return head current = current['next'] return head def ll_collect(head, result_list): """собирает все данные из связного списка в result_list""" current = head while current is not None: result_list.append((current['name'], current['phone'])) current = current['next'] def ll_list_all(head): """ собирает все записи в список и сортирует """ result = [] current = head while current is not None: result.append((current['name'], current['phone'])) current = current['next'] # ручная сортировка пузырьком n = len(result) for i in range(n): for j in range(0, n - i - 1): if result[j][0] > result[j + 1][0]: result[j], result[j + 1] = result[j + 1], result[j] return result def hash_table(size): """создание хеш-таблицы""" return [None] * size def hash_func(name, buckets_count): """ использует умножение на простое число для лучшего распределения """ h = 0 multiplier = 1 for char in name: h = (h + ord(char) * multiplier) % buckets_count multiplier = (multiplier * 31) % buckets_count return h def ht_insert(buckets, name, phone): """добавить или обновить запись""" if buckets is None: return index = hash_func(name, len(buckets)) buckets[index] = ll_insert(buckets[index], name, phone) def ht_find(buckets, name): """найти телефон по имени""" idx = hash_func(name, len(buckets)) return ll_find(buckets[idx], name) def ht_delete(buckets, name): """ удалить запись """ idx = hash_func(name, len(buckets)) buckets[idx] = ll_delete(buckets[idx], name) def bubble_sort(records): """пузырьковая сортировка""" n = len(records) for i in range(n - 1): swapped = False for j in range(n - 1 - i): if records[j][0] > records[j + 1][0]: records[j], records[j + 1] = records[j + 1], records[j] swapped = True if not swapped: break return records def ht_list_all(buckets): """ собрание всех записей и сортировка """ # Собираем все записи full_data = [] for head in buckets: ll_collect(head, full_data) # Сортируем пузырьком bubble_sort(full_data) return full_data #Hash_table1 = hash_table(3) #ht_insert(Hash_table1, 'Alena', '010') #ht_insert(Hash_table1, 'Helena', '111') #ht_insert(Hash_table1, 'Gena', '222') #print(ht_list_all(Hash_table1)) 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): """ рекурсивный поиск телефона по имени возвращает phone или None """ # не нашли if root is None: return None # нашли if root['name'] == name: return root['phone'] # ищем в левом поддереве elif name < root['name']: return bst_find(root['left'], name) # ищем в правом поддереве else: return bst_find(root['right'], name) def bst_find_min(node): """вспомогательная функция: поиск узла с минимальным ключом""" current = node while current['left'] is not None: 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 and root['right'] is None: return None elif root['left'] is None: return root['right'] elif root['right'] is None: return root['left'] # находим минимальный элемент в правом поддереве successor = bst_find_min(root['right']) # копируем данные из преемника в текущий узел root['name'] = successor['name'] root['phone'] = successor['phone'] # удаляем преемника root['right'] = bst_delete(root['right'], successor['name']) return root def bst_list_all(root, result=None): """ центрированный (in-order) обход дерева рекурсивно собирает записи в отсортированном порядке """ if result is None: result = [] if root is not None: # сначала обходим левое поддерево (все меньшие ключи) bst_list_all(root['left'], result) # затем текущий узел result.append((root['name'], root['phone'])) # затем правое поддерево (все большие ключи) bst_list_all(root['right'], result) return result def generate_records(count=10000): """ генерация тестовых данных 70% уникальных имён, 30% повторяющихся (для коллизий) """ records = [] base_names = ["Алексей", "Борис", "Владимир", "Дмитрий", "Елена", "Иван", "Мария", "Николай", "Ольга", "Павел"] for i in range(count): if random.random() < 0.7: name = f"User_{i:05d}" else: name = random.choice(base_names) + f"_{random.randint(1, 100)}" phone = f"+7-{random.randint(100,999)}-{random.randint(100,999)}-{random.randint(1000,9999)}" records.append((name, phone)) shuffled = records.copy() random.shuffle(shuffled) sorted_records = sorted(records, key=lambda x: x[0]) return shuffled, sorted_records def measure_insertion(structure_name, records): """ замер времени вставки возвращает список замеров и заполненную структуру """ times = [] filled_structure = None for run in range(5): if structure_name == "linked_list": structure = None elif structure_name == "hash_table": structure = hash_table(2003) elif structure_name == "bst": structure = None start = time.perf_counter() for name, phone in records: if structure_name == "linked_list": structure = ll_insert(structure, name, phone) elif structure_name == "hash_table": ht_insert(structure, name, phone) elif structure_name == "bst": structure = bst_insert(structure, name, phone) end = time.perf_counter() times.append(end - start) if run == 4: # Сохраняем после последнего замера filled_structure = structure return times, filled_structure def measure_search(structure_name, structure, search_names): """ замер времени поиска возвращает список замеров """ times = [] for run in range(5): start = time.perf_counter() for name in search_names: if structure_name == "linked_list": ll_find(structure, name) elif structure_name == "hash_table": ht_find(structure, name) elif structure_name == "bst": bst_find(structure, name) end = time.perf_counter() times.append(end - start) return times def measure_deletion(structure_name, original_structure, delete_names): """ замер времени удаления возвращает список замеров """ times = [] for run in range(5): # создаём копию структуры if structure_name == "linked_list": all_records = ll_list_all(original_structure) test_structure = None for name, phone in all_records: test_structure = ll_insert(test_structure, name, phone) elif structure_name == "hash_table": all_records = ht_list_all(original_structure) test_structure = hash_table(2003) for name, phone in all_records: ht_insert(test_structure, name, phone) elif structure_name == "bst": all_records = bst_list_all(original_structure) test_structure = None for name, phone in all_records: test_structure = bst_insert(test_structure, name, phone) start = time.perf_counter() for name in delete_names: if structure_name == "linked_list": test_structure = ll_delete(test_structure, name) elif structure_name == "hash_table": ht_delete(test_structure, name) elif structure_name == "bst": test_structure = bst_delete(test_structure, name) end = time.perf_counter() times.append(end - start) return times print(f"Текущая рабочая директория: {os.getcwd()}") print(f"Путь к файлу: {os.path.abspath(__file__)}") def run_experiment(): """ запуск всех экспериментов и сохранение результатов """ current_dir = os.path.dirname(__file__) docs_dir = os.path.dirname(current_dir) csv_file = os.path.join(docs_dir, "experiment_results.csv") os.makedirs(docs_dir, exist_ok=True) print("ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ СТРУКТУР ДАННЫХ") print("Телефонный справочник - 10000 записей") print(f"\nРезультаты будут сохранены в: {csv_file}") # генерация данных print("\n1. Генерация тестовых данных...") shuffled_records, sorted_records = generate_records(10000) print(f"Сгенерировано 10000 записей") print(f"Уникальных имён: {len(set([r[0] for r in shuffled_records]))}") # подготовка имён для поиска и удаления random.seed(42) existing_names = [shuffled_records[i][0] for i in random.sample(range(10000), 100)] nonexisting_names = [f"NotExist_{i}" for i in range(10)] search_names = existing_names + nonexisting_names delete_names = [shuffled_records[i][0] for i in random.sample(range(10000), 50)] results = [["Структура", "Режим", "Операция", "Замер1(с)", "Замер2(с)", "Замер3(с)", "Замер4(с)", "Замер5(с)", "Среднее(с)"]] # тестирование для каждого режима for mode_name, records in [("случайный", shuffled_records), ("отсортированный", sorted_records)]: print(f"\n2. Тестирование режима: {mode_name}") for struct_name in ["linked_list", "hash_table", "bst"]: print(f"\n {struct_name.upper()}:") # вставка print("Вставка 10000 записей...") insert_times, filled_struct = measure_insertion(struct_name, records) avg_insert = sum(insert_times) / 5 print(f"Время: {avg_insert:.4f} сек (среднее)") # поиск print("Поиск 110 записей (100 существующих + 10 которых нет)...") search_times = measure_search(struct_name, filled_struct, search_names) avg_search = sum(search_times) / 5 print(f"Время: {avg_search:.4f} сек (среднее)") # удаление print("Удаление 50 случайных записей...") delete_times = measure_deletion(struct_name, filled_struct, delete_names) avg_delete = sum(delete_times) / 5 print(f"Время: {avg_delete:.4f} сек (среднее)") # сохраняем результаты results.append([struct_name, mode_name, "вставка"] + insert_times + [avg_insert]) results.append([struct_name, mode_name, "поиск"] + search_times + [avg_search]) results.append([struct_name, mode_name, "удаление"] + delete_times + [avg_delete]) # сохранение CSV print("\n3. Сохранение результатов...") try: with open(csv_file, "w", newline="", encoding="utf-8-sig") as f: writer = csv.writer(f, delimiter=';') writer.writerows(results) print(f"Результаты сохранены в: {csv_file}") except Exception as e: print(f"Ошибка сохранения: {e}") # вывод табл. print("СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ") print(f"{'Структура':<15} {'Режим':<12} {'Операция':<10} {'Среднее время (сек)':<20}") for row in results[1:]: struct, mode, op, t1, t2, t3, t4, t5, avg = row print(f"{struct:<15} {mode:<12} {op:<10} {avg:<20.6f}") return results def create_report_table(results): """Создание сводной таблицы""" print("СВОДНАЯ ТАБЛИЦА (среднее время в секундах)") print(f"{'Структура':<12} {'Режим':<12} {'Вставка':<12} {'Поиск':<12} {'Удаление':<12}") summary = {} for row in results[1:]: struct, mode, op, _, _, _, _, _, avg = row key = (struct, mode) if key not in summary: summary[key] = {} summary[key][op] = avg names = {'linked_list': 'LinkedList', 'hash_table': 'HashTable', 'bst': 'BST'} for (struct, mode), ops in summary.items(): print(f"{names[struct]:<12} {mode:<12} {ops.get('вставка', 0):<12.6f} {ops.get('поиск', 0):<12.6f} {ops.get('удаление', 0):<12.6f}") def print_analysis(): """вывод краткого анализа""" print("АНАЛИЗ РЕЗУЛЬТАТОВ") print(""" 1. Влияние порядка данных на BST: - На случайных данных: быстро O(log n) - На отсортированных: деградация до O(n) (дерево вырождается в список) 2. Хеш-таблица не чувствительна к порядку: - Хеш-функция случайно распределяет данные по bucket'ам - Порядок вставки не влияет на время операций 3. Связный список всегда медленен при поиске: - Поиск требует последовательного прохода O(n) - Нет индексов или сортировки для ускорения 4. Сравнение удаления: - Связный список: O(n) — нужен поиск элемента - Хеш-таблица: O(1) — прямой доступ по индексу - BST: O(log n) в среднем, O(n) на отсортированных 5. Рекомендация для реальных задач: - Хеш-таблица: частый поиск, словари, кэши - BST (сбалансированный): нужны отсортированные данные - Связный список: маленькие объёмы, очереди/стеки - Для телефонного справочника ЛУЧШЕ: ХЕШ-ТАБЛИЦА """) def create_graphs(results): """Построение столбчатых диаграмм""" import matplotlib.pyplot as plt import numpy as np data = {} for row in results[1:]: struct = row[0] mode = row[1] op = row[2] avg = row[8] if struct not in data: data[struct] = {} if mode not in data[struct]: data[struct][mode] = {} data[struct][mode][op] = avg # Настройки struct_names = {'linked_list': 'LinkedList', 'hash_table': 'HashTable', 'bst': 'BST'} colors = {'linked_list': '#3498db', 'hash_table': '#2ecc71', 'bst': '#e74c3c'} modes = ['случайный', 'отсортированный'] operations = ['вставка', 'поиск', 'удаление'] op_titles = ['Вставка (10000 записей)', 'Поиск (110 запросов)', 'Удаление (50 записей)'] fig, axes = plt.subplots(1, 3, figsize=(14, 5)) fig.suptitle('Сравнение производительности структур данных', fontsize=14, fontweight='bold') for idx, (op, title) in enumerate(zip(operations, op_titles)): ax = axes[idx] x = np.arange(len(modes)) width = 0.25 multiplier = 0 for struct in ['linked_list', 'hash_table', 'bst']: values = [data[struct][mode][op] for mode in modes] bars = ax.bar(x + multiplier * width, values, width, label=struct_names[struct], color=colors[struct]) for bar, val in zip(bars, values): if val < 0.001: ax.text(bar.get_x() + bar.get_width()/2, bar.get_height(), f'{val:.6f}', ha='center', va='bottom', fontsize=7) elif val < 0.01: ax.text(bar.get_x() + bar.get_width()/2, bar.get_height(), f'{val:.5f}', ha='center', va='bottom', fontsize=7) else: ax.text(bar.get_x() + bar.get_width()/2, bar.get_height(), f'{val:.3f}', ha='center', va='bottom', fontsize=8) multiplier += 1 ax.set_title(title) ax.set_ylabel('Время (сек)') ax.set_yscale('log') ax.set_ylim(1e-5, 10) ax.set_xticks(x + width) ax.set_xticklabels(['Случайный', 'Отсортированный']) ax.legend() ax.grid(True, alpha=0.3, axis='y') plt.tight_layout() current_dir = os.path.dirname(__file__) docs_dir = os.path.dirname(current_dir) path = os.path.join(docs_dir, 'graphs.png') plt.savefig(path, dpi=150) plt.close() print(f"\nГрафики сохранены: {path}") return path if __name__ == "__main__": results = run_experiment() create_report_table(results) create_graphs(results) print_analysis() print("ЭКСПЕРИМЕНТ ВЫПОЛНЕН ПОЛНОСТЬЮ!")