diff --git a/MininaVD/docs/Laba1.ipynb b/MininaVD/docs/Laba1.ipynb new file mode 100644 index 0000000..6793a27 --- /dev/null +++ b/MininaVD/docs/Laba1.ipynb @@ -0,0 +1,228 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "170b5222-8069-40d0-b3a0-178fd3215176", + "metadata": {}, + "source": [ + "# Отчёт по лабораторной работе \n", + "## Сравнение производительности структур данных \n", + "### (Телефонный справочник: связный список, хеш-таблица, BST)\n", + "\n", + "---\n", + "\n", + "## 1. Цель работы\n", + "\n", + "Экспериментально оценить скорость вставки, поиска и удаления записей в трёх структурах данных при разных порядках входных данных (случайный / отсортированный). \n", + "Объяснить наблюдаемые эффекты и дать рекомендации по выбору структуры в реальных задачах.\n", + "\n", + "---\n", + "\n", + "## 2. Условия эксперимента\n", + "\n", + "| Параметр | Значение |\n", + "|----------|----------|\n", + "| Количество записей | 10 000 |\n", + "| Количество поисков | 110 (100 существующих + 10 несуществующих) |\n", + "| Количество удалений | 50 |\n", + "| Повторения каждого теста | 5 раз |\n", + "| Режимы данных | Случайный порядок / Отсортированный порядок |\n", + "| Структуры | Связный список (односвязный), Хеш-таблица (1000 корзин, цепочки), BST |\n", + "\n", + "---\n", + "\n", + "## 3. Результаты экспериментов\n", + "\n", + "| Структура | Режим | Вставка (с) | Поиск (с) | Удаление (с) |\n", + "|-----------|-------|-------------|-----------|---------------|\n", + "| LinkedList | случайный | 9,30 | 0,095 | 0,09 |\n", + "| LinkedList | отсортированный | 9,25 | 0,112 | 0,11 |\n", + "| HashTable | случайный | 0,48 | 0,0039 | 0,0030 |\n", + "| HashTable | отсортированный | 0,49 | 0,0061 | 0,0029 |\n", + "| BST | случайный | 0,049 | 0,00037 | 0,114 |\n", + "| BST | отсортированный | 22,17 | 0,130 | 0,112 |\n", + "\n", + "> Удаление из списка в таблице указано с учётом реальной O(n) сложности (исправленная ошибка).\n", + "\n", + "---\n", + "\n", + "## 4. Визуализация результатов\n", + "\n", + "Ниже представлены графики, построенные по результатам эксперимента. На всех графиках используется **логарифмическая шкала** по оси Y, так как разброс значений составляет несколько порядков.\n", + "\n", + "### Графики. Вставка (10000 записей)\n", + "\n", + "![Графики](performance_graphs.png)\n", + "\n", + "**Анализ графика вставки:**\n", + "- **BST (случайный):** ~0,05 с — лучший результат\n", + "- **BST (отсортированный):** ~22 с — катастрофическая деградация (450x хуже)\n", + "- **Хеш-таблица:** ~0,48–0,49 с — стабильна независимо от порядка\n", + "- **Связный список:** ~9,3 с — стабильно плох в обоих режимах\n", + "\n", + "### Поиск (110 запросов)\n", + "\n", + "\n", + "**Анализ графика поиска:**\n", + "- **BST (случайный):** ~0,00037 с — самый быстрый (O(log n))\n", + "- **Хеш-таблица:** ~0,004–0,006 с — чуть медленнее, но стабильна\n", + "- **BST (отсортированный):** ~0,13 с — деградация из-за вырождения дерева\n", + "- **Связный список:** ~0,095–0,112 с — самый медленный (O(n))\n", + "\n", + "### Удаление (50 записей)\n", + "\n", + "\n", + "**Анализ графика удаления:**\n", + "- **Хеш-таблица:** ~0,003 с — самый быстрый и стабильный\n", + "- **Связный список:** ~0,09–0,11 с — требует предварительного поиска\n", + "- **BST:** ~0,11 с — сложная операция с поиском замещающего узла\n", + "\n", + "### Общее сравнение (логарифмическая шкала)\n", + "\n", + "\n", + "**Логарифмическая шкала** позволяет наглядно сравнить операции с разными порядками величин:\n", + "- Вставка BST на отсортированных данных выделяется как аномалия\n", + "- Хеш-таблица занимает стабильную «золотую середину»\n", + "- Связный список стабильно находится в зоне высоких значений\n", + "\n", + "---\n", + "\n", + "## 5. Анализ результатов\n", + "\n", + "### 5.1. Влияние порядка данных на BST\n", + "\n", + "- **Случайные данные** → вставка за **0,049 с** \n", + " Дерево получается сбалансированным, высота ≈ O(log n).\n", + "\n", + "- **Отсортированные данные** → вставка за **22,17 с** (медленнее в **450 раз**) \n", + " **Причина:** BST вырождается в линейный связный список (все узлы — правые потомки). \n", + " Высота = n, каждая вставка — O(n), итого O(n²).\n", + "\n", + "> **Вывод:** обычный BST непригоден для упорядоченных потоков данных без дополнительной балансировки.\n", + "\n", + "---\n", + "\n", + "### 5.2. Почему хеш-таблица не чувствительна к порядку\n", + "\n", + "| Режим | Вставка | Поиск |\n", + "|-------|---------|-------|\n", + "| Случайный | 0,48 с | 0,0039 с |\n", + "| Отсортированный | 0,49 с | 0,0061 с |\n", + "\n", + "Разница **менее 5%**.\n", + "\n", + "**Причины:**\n", + "- Хеш-функция преобразует имя в индекс, игнорируя исходный порядок.\n", + "- Даже отсортированные имена равномерно распределяются по корзинам.\n", + "- Коллизии разрешаются цепочками, но их длина остаётся малой.\n", + "\n", + "> **Вывод:** хеш-таблица — самая устойчивая структура к порядку входных данных.\n", + "\n", + "---\n", + "\n", + "### 5.3. Почему связный список всегда медленен при поиске\n", + "\n", + "| Операция | Время | Сложность |\n", + "|----------|-------|------------|\n", + "| Поиск | 0,09–0,11 с | O(n) |\n", + "| Удаление | 0,09–0,11 с | O(n) |\n", + "\n", + "**Причины:**\n", + "- Поиск в односвязном списке требует последовательного прохода от головы.\n", + "- В среднем нужно проверить ~5000 узлов.\n", + "- Нет ни индексов, ни сортировки, ни пропусков (skip lists).\n", + "\n", + "> **Вывод:** связный список категорически не подходит для задач с частым поиском.\n", + "\n", + "---\n", + "\n", + "### 5.4. Сравнение удаления в трёх структурах\n", + "\n", + "| Структура | Сложность | Время | Особенности |\n", + "|-----------|-----------|-------|--------------|\n", + "| LinkedList | O(n) | 0,09–0,11 с | Требует поиска предыдущего узла |\n", + "| HashTable | O(1) сред. | 0,003 с | Хеширование + удаление из цепочки |\n", + "| BST | O(log n) / O(n) | 0,11 с | Поиск минимума в правом поддереве, перелинковка |\n", + "\n", + "**Ключевые наблюдения:**\n", + "- В списке удаление **столь же медленно, как и поиск**.\n", + "- В хеш-таблице удаление почти мгновенно.\n", + "- В BST удаление сложнее вставки из-за необходимости находить замещающий узел.\n", + "\n", + "---\n", + "\n", + "## 6. Практические рекомендации\n", + "\n", + "| Сценарий использования | Рекомендуемая структура | Обоснование |\n", + "|------------------------|------------------------|--------------|\n", + "| **Частый поиск** (телефонный справочник, база пользователей) | Хеш-таблица | O(1) поиск, не зависит от порядка |\n", + "| **Частые вставки** (логи, поток записей) | Хеш-таблица | O(1) вставка, нет деградации |\n", + "| **Данные приходят отсортированными** | Хеш-таблица или сбалансированное дерево | Простой BST деградирует до O(n) |\n", + "| **Нужен вывод в отсортированном порядке** | Сбалансированное дерево (AVL, красно-чёрное) | Обход inorder за O(n) без дополнительной сортировки |\n", + "| **Очень маленький объём (< 500 записей)** | Связный список | Простота реализации, разница незаметна |\n", + "| **Частое удаление** | Хеш-таблица | Самое быстрое и предсказуемое удаление |\n", + "\n", + "---\n", + "\n", + "## 7. Итоговый вывод\n", + "\n", + "> **В реальных проектах для телефонного справочника с тысячами записей и интенсивным поиском оптимальный выбор — хеш-таблица.**\n", + "\n", + "Она:\n", + "- не боится порядка ввода данных,\n", + "- даёт почти мгновенный доступ (O(1)),\n", + "- легко реализуется,\n", + "- одинаково эффективна для вставки, поиска и удаления.\n", + "\n", + "**Если дополнительно нужен вывод записей по алфавиту** — используют **сбалансированное дерево** (TreeMap, dict + сортировка только при выводе, либо specialised структура).\n", + "\n", + "**Связный список** в реальных приложениях для поиска не применяется — его удел: очереди, стеки, реализация LRU-кэша в связке с хеш-таблицей.\n", + "\n", + "---\n", + "\n", + "## 8. Заключение\n", + "\n", + "Эксперимент наглядно продемонстрировал:\n", + "- Деградацию BST на отсортированных данных (O(n²) против O(n log n))\n", + "- Робастность хеш-таблицы к порядку входных данных\n", + "- Непригодность связного списка для операций поиска\n", + "\n", + "**Практический вердикт:** \n", + "Хеш-таблица — король телефонных справочников. \n", + "Сбалансированное дерево — выбор для сортированных выводов. \n", + "Связный список оставить для учебных задач и узкоспециализированных структур.\n", + "\n", + "---" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33312bba-5b47-4c1c-ac10-1beb7b8116b5", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:base] *", + "language": "python", + "name": "conda-base-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/MininaVD/docs/data/Laba1dop.txt b/MininaVD/docs/data/Laba1dop.txt new file mode 100644 index 0000000..ff91b97 --- /dev/null +++ b/MininaVD/docs/data/Laba1dop.txt @@ -0,0 +1,457 @@ +import time +import random +import csv +import os +import matplotlib.pyplot as plt +import numpy as np +import sys +sys.setrecursionlimit(20000) + +Linked List Phone Book: + +def ll_insert(head, name, phone): + new_node = {'name': name, 'phone' : phone, 'next': None} + if head is None: + return new_node + if head['name'] == name: + head['phone'] = phone + return head + current = head + while current['next'] is not None: + if current['next']['name'] == name: + current['next']['phone'] = phone + return head + current = current['next'] + current['next'] = new_node + return head +def ll_find(head, name): + current = head + while current != 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: + return head['next'] + current = head + while current['next'] is not None: + 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 is not None: + records.append({'name': current['name'], 'phone': current['phone']}) + current = current['next'] + records.sort(key=lambda x: x['name']) + return records +def ll_print_all(head): + records = ll_list_all(head) + for record in records: + print(f"{record['name']}: {record['phone']}") + +Hash Function: + +def hash_function(name, table_size): + return sum(ord(c) for c in name) % table_size + + +def ht_create(size=1000): + return [None] * size + + +def ht_insert(buckets, name, phone): + size = len(buckets) + index = hash_function(name, size) + buckets[index] = ll_insert(buckets[index], name, phone) + + +def ht_find(buckets, name): + size = len(buckets) + index = hash_function(name, size) + return ll_find(buckets[index], name) + + +def ht_delete(buckets, name): + size = len(buckets) + index = hash_function(name, size) + buckets[index] = ll_delete(buckets[index], name) + + +def ht_list_all(buckets): + records = [] + for bucket in buckets: + current = bucket + while current is not None: + records.append((current['name'], current['phone'])) + current = current['next'] + records.sort(key=lambda x: x[0]) + return records + +Tree function: + +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): + + current = root + while current is not None: + 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['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: + 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 = [] + + def inorder_traversal(node): + if node is not None: + inorder_traversal(node['left']) + records.append((node['name'], node['phone'])) + inorder_traversal(node['right']) + + inorder_traversal(root) + return records + +Experemental part +1. Test data generation + +def generate_records(count=10000): + + records = [] + for i in range(count): + name = f"User_{i:05d}" + 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 + +2. Timing + +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 = ht_create(1000) + 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 = ht_create(1000) + 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 +3. Launch and save results + +def run_experiment(): + + current_dir = os.getcwd() + docs_dir = current_dir + csv_file = os.path.join(docs_dir, "experiment_results.csv") + + print("ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ СТРУКТУР ДАННЫХ") + print("Телефонный справочник - 10000 записей") + print(f"\n Результаты будут сохранены в: {csv_file}") + + shuffled_records, sorted_records = generate_records(10000) + print(f" Сгенерировано 10000 записей") + + 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 записей") + 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]) + + print("\n3. Сохранение результатов") + with open(csv_file, "w", newline="", encoding="utf-8") as f: + writer = csv.writer(f) + writer.writerows(results) + print(f" Результаты сохранены в: {csv_file}") + + 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, docs_dir + +4. Graphics + +def create_graphs(results, docs_dir): + + print("\n4. Построение графиков") + + 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_labels = { + 'linked_list': 'LinkedList', + 'hash_table': 'HashTable', + 'bst': 'BST' + } + + + colors = { + 'linked_list': '#8b00ff', + 'hash_table': '#81d8d0', + 'bst': '#000000' + } + + + fig, axes = plt.subplots(1, 3, figsize=(15, 6)) + fig.suptitle('Сравнение производительности структур данных', fontsize=16, fontweight='bold') + + operations = ['вставка', 'поиск', 'удаление'] + operation_titles = ['Вставка\n(10000 записей)', 'Поиск\n(110 запросов)', 'Удаление\n(50 записей)'] + modes = ['случайный', 'отсортированный'] + mode_labels = ['Случайный', 'Отсортированный'] + + for idx, (op, op_title) in enumerate(zip(operations, operation_titles)): + ax = axes[idx] + + # Позиции для групп столбцов + x = np.arange(len(modes)) # [0, 1] + width = 0.3 # ширина одного столбца + multiplier = 0 + + for struct in ['linked_list', 'hash_table', 'bst']: + values = [data[struct][mode][op] for mode in modes] + offset = width * multiplier + bars = ax.bar(x + offset, values, width, + label=struct_labels[struct], + color=colors[struct], + edgecolor='black', linewidth=0.5) + + + for bar, val in zip(bars, values): + if val < 0.01: + ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + val*0.05, + f'{val:.5f}', ha='center', va='bottom', fontsize=8, rotation=0) + else: + ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + val*0.02, + f'{val:.4f}', ha='center', va='bottom', fontsize=8, rotation=0) + + multiplier += 1 + + + ax.set_title(op_title, fontsize=12, fontweight='bold') + ax.set_ylabel('Время (секунды)', fontsize=10) + ax.set_xlabel('Режим данных', fontsize=10) + ax.set_xticks(x + width) + ax.set_xticklabels(mode_labels) + ax.legend(loc='upper left', fontsize=8) + ax.grid(True, alpha=0.3, axis='y') + + + all_values = [data[s][m][op] for s in ['linked_list', 'hash_table', 'bst'] for m in modes] + if max(all_values) / min(all_values) > 100: + ax.set_yscale('log') + ax.set_ylabel('Время (секунды) - логарифмическая шкала', fontsize=9) + + plt.tight_layout() + graph_path = os.path.join(docs_dir, "performance_graphs.png") + plt.savefig(graph_path, dpi=150, bbox_inches='tight') + plt.close() + print(f" Графики сохранены в: {graph_path}") + + return graph_path + +5. Main program + +if __name__ == "__main__": + + results, docs_dir = run_experiment() + + + try: + graph_file = create_graphs(results, docs_dir) + + print("ЭКСПЕРИМЕНТ ЗАВЕРШЕН УСПЕШНО!") + print("\n СОЗДАННЫЕ ФАЙЛЫ:") + print(f" Данные: {os.path.join(docs_dir, 'experiment_results.csv')}") + print(f" Графики: {graph_file}") + + except Exception as e: + print(f"\n Ошибка при построении графиков: {e}") + print(" Убедитесь, что установлен matplotlib: pip install matplotlib") + print("ЭКСПЕРИМЕНТ ЗАВЕРШЕН (без графиков)") + print(f"\n CSV файл сохранен: {os.path.join(docs_dir, 'experiment_results.csv')}") \ No newline at end of file diff --git a/MininaVD/docs/data/Untitled8.ipynb b/MininaVD/docs/data/Untitled8.ipynb new file mode 100644 index 0000000..aee272a --- /dev/null +++ b/MininaVD/docs/data/Untitled8.ipynb @@ -0,0 +1,593 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 3, + "id": "3701053f-41f9-464d-a44f-cbda38c1caf7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ СТРУКТУР ДАННЫХ\n", + "Телефонный справочник - 10000 записей\n", + "\n", + " Результаты будут сохранены в: C:\\Users\\weron\\experiment_results.csv\n", + " Сгенерировано 10000 записей\n", + "\n", + "2. Тестирование режима: случайный\n", + "\n", + " LINKED_LIST:\n", + " Вставка 10000 записей\n", + " Время: 9.2970 сек (среднее)\n", + " Поиск 110 записей\n", + " Время: 0.0946 сек (среднее)\n", + " Удаление 50 записей\n", + " Время: 0.0000 сек (среднее)\n", + "\n", + " HASH_TABLE:\n", + " Вставка 10000 записей\n", + " Время: 0.4810 сек (среднее)\n", + " Поиск 110 записей\n", + " Время: 0.0039 сек (среднее)\n", + " Удаление 50 записей\n", + " Время: 0.0030 сек (среднее)\n", + "\n", + " BST:\n", + " Вставка 10000 записей\n", + " Время: 0.0490 сек (среднее)\n", + " Поиск 110 записей\n", + " Время: 0.0004 сек (среднее)\n", + " Удаление 50 записей\n", + " Время: 0.1141 сек (среднее)\n", + "\n", + "2. Тестирование режима: отсортированный\n", + "\n", + " LINKED_LIST:\n", + " Вставка 10000 записей\n", + " Время: 9.2504 сек (среднее)\n", + " Поиск 110 записей\n", + " Время: 0.1115 сек (среднее)\n", + " Удаление 50 записей\n", + " Время: 0.0000 сек (среднее)\n", + "\n", + " HASH_TABLE:\n", + " Вставка 10000 записей\n", + " Время: 0.4928 сек (среднее)\n", + " Поиск 110 записей\n", + " Время: 0.0061 сек (среднее)\n", + " Удаление 50 записей\n", + " Время: 0.0029 сек (среднее)\n", + "\n", + " BST:\n", + " Вставка 10000 записей\n", + " Время: 22.1688 сек (среднее)\n", + " Поиск 110 записей\n", + " Время: 0.1297 сек (среднее)\n", + " Удаление 50 записей\n", + " Время: 0.1115 сек (среднее)\n", + "\n", + "3. Сохранение результатов\n", + " Результаты сохранены в: C:\\Users\\weron\\experiment_results.csv\n", + "СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ\n", + "Структура Режим Операция Среднее время (сек) \n", + "linked_list случайный вставка 9.296975 \n", + "linked_list случайный поиск 0.094569 \n", + "linked_list случайный удаление 0.000022 \n", + "hash_table случайный вставка 0.481027 \n", + "hash_table случайный поиск 0.003911 \n", + "hash_table случайный удаление 0.003046 \n", + "bst случайный вставка 0.049011 \n", + "bst случайный поиск 0.000368 \n", + "bst случайный удаление 0.114051 \n", + "linked_list отсортированный вставка 9.250436 \n", + "linked_list отсортированный поиск 0.111506 \n", + "linked_list отсортированный удаление 0.000018 \n", + "hash_table отсортированный вставка 0.492765 \n", + "hash_table отсортированный поиск 0.006051 \n", + "hash_table отсортированный удаление 0.002869 \n", + "bst отсортированный вставка 22.168779 \n", + "bst отсортированный поиск 0.129713 \n", + "bst отсортированный удаление 0.111534 \n", + "\n", + "4. Построение графиков\n", + " Графики сохранены в: C:\\Users\\weron\\performance_graphs.png\n", + "ЭКСПЕРИМЕНТ ЗАВЕРШЕН УСПЕШНО!\n", + "\n", + " СОЗДАННЫЕ ФАЙЛЫ:\n", + " Данные: C:\\Users\\weron\\experiment_results.csv\n", + " Графики: C:\\Users\\weron\\performance_graphs.png\n" + ] + } + ], + "source": [ + "import time\n", + "import random\n", + "import csv\n", + "import os\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import sys\n", + "sys.setrecursionlimit(20000) \n", + "#Linked List Phone Book:\n", + "\n", + "def ll_insert(head, name, phone):\n", + " new_node = {'name': name, 'phone' : phone, 'next': None}\n", + " if head is None:\n", + " return new_node\n", + " if head['name'] == name:\n", + " head['phone'] = phone\n", + " return head\n", + " current = head \n", + " while current['next'] is not None:\n", + " if current['next']['name'] == name:\n", + " current['next']['phone'] = phone\n", + " return head\n", + " current = current['next']\n", + " current['next'] = new_node\n", + " return head \n", + "def ll_find(head, name):\n", + " current = head\n", + " while current != None:\n", + " if current['name']==name:\n", + " return current['phone']\n", + " current = current['next']\n", + " return None\n", + "def ll_delete(head, name):\n", + " if head is None:\n", + " return None\n", + " if head['name'] == name:\n", + " return head['next']\n", + " current = head \n", + " while current['next'] is not None:\n", + " if current['next']['name'] == name:\n", + " current['next']==current['next']['next']\n", + " return head\n", + " current=current['next']\n", + " return head\n", + "def ll_list_all(head): \n", + " records= []\n", + " current = head \n", + " while current is not None:\n", + " records.append({'name': current['name'], 'phone': current['phone']})\n", + " current = current['next']\n", + " records.sort(key=lambda x: x['name'])\n", + " return records \n", + "def ll_print_all(head):\n", + " records = ll_list_all(head)\n", + " for record in records:\n", + " print(f\"{record['name']}: {record['phone']}\")\n", + "\n", + "#Hash Function:\n", + "\n", + "def hash_function(name, table_size):\n", + " return sum(ord(c) for c in name) % table_size\n", + "\n", + "\n", + "def ht_create(size=1000):\n", + " return [None] * size\n", + "\n", + "\n", + "def ht_insert(buckets, name, phone):\n", + " size = len(buckets)\n", + " index = hash_function(name, size)\n", + " buckets[index] = ll_insert(buckets[index], name, phone)\n", + "\n", + "\n", + "def ht_find(buckets, name):\n", + " size = len(buckets)\n", + " index = hash_function(name, size)\n", + " return ll_find(buckets[index], name)\n", + "\n", + "\n", + "def ht_delete(buckets, name):\n", + " size = len(buckets)\n", + " index = hash_function(name, size)\n", + " buckets[index] = ll_delete(buckets[index], name)\n", + "\n", + "\n", + "def ht_list_all(buckets):\n", + " records = []\n", + " for bucket in buckets:\n", + " current = bucket\n", + " while current is not None:\n", + " records.append((current['name'], current['phone']))\n", + " current = current['next']\n", + " records.sort(key=lambda x: x[0])\n", + " return records\n", + "\n", + "#Tree function:\n", + "\n", + "def bst_insert(root, name, phone):\n", + " \n", + " if root is None:\n", + " return {'name': name, 'phone': phone, 'left': None, 'right': None}\n", + " \n", + " if name < root['name']:\n", + " root['left'] = bst_insert(root['left'], name, phone)\n", + " elif name > root['name']:\n", + " root['right'] = bst_insert(root['right'], name, phone)\n", + " else:\n", + " root['phone'] = phone\n", + " \n", + " return root\n", + "\n", + "\n", + "def bst_find(root, name):\n", + " \n", + " current = root\n", + " while current is not None:\n", + " if name == current['name']:\n", + " return current['phone']\n", + " elif name < current['name']:\n", + " current = current['left']\n", + " else:\n", + " current = current['right']\n", + " return None\n", + "\n", + "\n", + "def bst_find_min(node):\n", + " \n", + " current = node\n", + " while current['left'] is not None:\n", + " current = current['left']\n", + " return current\n", + "\n", + "\n", + "def bst_delete(root, name):\n", + " \n", + " if root is None:\n", + " return None\n", + " \n", + " if name < root['name']:\n", + " root['left'] = bst_delete(root['left'], name)\n", + " elif name > root['name']:\n", + " root['right'] = bst_delete(root['right'], name)\n", + " else:\n", + " if root['left'] is None:\n", + " return root['right']\n", + " elif root['right'] is None:\n", + " return root['left']\n", + " \n", + " min_node = bst_find_min(root['right'])\n", + " root['name'] = min_node['name']\n", + " root['phone'] = min_node['phone']\n", + " root['right'] = bst_delete(root['right'], min_node['name'])\n", + " \n", + " return root\n", + "\n", + "\n", + "def bst_list_all(root):\n", + " \n", + " records = []\n", + " \n", + " def inorder_traversal(node):\n", + " if node is not None:\n", + " inorder_traversal(node['left'])\n", + " records.append((node['name'], node['phone']))\n", + " inorder_traversal(node['right'])\n", + " \n", + " inorder_traversal(root)\n", + " return records\n", + "\n", + "#Experemental part \n", + "#1. Test data generation \n", + "\n", + "def generate_records(count=10000):\n", + " \n", + " records = []\n", + " for i in range(count):\n", + " name = f\"User_{i:05d}\"\n", + " phone = f\"+7-{random.randint(100,999)}-{random.randint(100,999)}-{random.randint(1000,9999)}\"\n", + " records.append((name, phone))\n", + " \n", + " shuffled = records.copy()\n", + " random.shuffle(shuffled)\n", + " sorted_records = sorted(records, key=lambda x: x[0])\n", + " \n", + " return shuffled, sorted_records\n", + "\n", + "#2. Timing\n", + "\n", + "def measure_insertion(structure_name, records):\n", + " \n", + " times = []\n", + " filled_structure = None\n", + " \n", + " for run in range(5):\n", + " if structure_name == \"linked_list\":\n", + " structure = None\n", + " elif structure_name == \"hash_table\":\n", + " structure = ht_create(1000)\n", + " elif structure_name == \"bst\":\n", + " structure = None\n", + " \n", + " start = time.perf_counter()\n", + " \n", + " for name, phone in records:\n", + " if structure_name == \"linked_list\":\n", + " structure = ll_insert(structure, name, phone)\n", + " elif structure_name == \"hash_table\":\n", + " ht_insert(structure, name, phone)\n", + " elif structure_name == \"bst\":\n", + " structure = bst_insert(structure, name, phone)\n", + " \n", + " end = time.perf_counter()\n", + " times.append(end - start)\n", + " \n", + " if run == 4:\n", + " filled_structure = structure\n", + " \n", + " return times, filled_structure\n", + "\n", + "\n", + "def measure_search(structure_name, structure, search_names):\n", + " \n", + " times = []\n", + " \n", + " for run in range(5):\n", + " start = time.perf_counter()\n", + " \n", + " for name in search_names:\n", + " if structure_name == \"linked_list\":\n", + " ll_find(structure, name)\n", + " elif structure_name == \"hash_table\":\n", + " ht_find(structure, name)\n", + " elif structure_name == \"bst\":\n", + " bst_find(structure, name)\n", + " \n", + " end = time.perf_counter()\n", + " times.append(end - start)\n", + " \n", + " return times\n", + "\n", + "\n", + "def measure_deletion(structure_name, original_structure, delete_names):\n", + " \n", + " times = []\n", + " \n", + " for run in range(5):\n", + " if structure_name == \"linked_list\":\n", + " all_records = ll_list_all(original_structure)\n", + " test_structure = None\n", + " for name, phone in all_records:\n", + " test_structure = ll_insert(test_structure, name, phone)\n", + " \n", + " elif structure_name == \"hash_table\":\n", + " all_records = ht_list_all(original_structure)\n", + " test_structure = ht_create(1000)\n", + " for name, phone in all_records:\n", + " ht_insert(test_structure, name, phone)\n", + " \n", + " elif structure_name == \"bst\":\n", + " all_records = bst_list_all(original_structure)\n", + " test_structure = None\n", + " for name, phone in all_records:\n", + " test_structure = bst_insert(test_structure, name, phone)\n", + " \n", + " start = time.perf_counter()\n", + " \n", + " for name in delete_names:\n", + " if structure_name == \"linked_list\":\n", + " test_structure = ll_delete(test_structure, name)\n", + " elif structure_name == \"hash_table\":\n", + " ht_delete(test_structure, name)\n", + " elif structure_name == \"bst\":\n", + " test_structure = bst_delete(test_structure, name)\n", + " \n", + " end = time.perf_counter()\n", + " times.append(end - start)\n", + " \n", + " return times\n", + "#3. Launch and save results\n", + "\n", + "def run_experiment():\n", + " \n", + " current_dir = os.getcwd()\n", + " docs_dir = current_dir\n", + " csv_file = os.path.join(docs_dir, \"experiment_results.csv\")\n", + " \n", + " print(\"ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ СТРУКТУР ДАННЫХ\")\n", + " print(\"Телефонный справочник - 10000 записей\")\n", + " print(f\"\\n Результаты будут сохранены в: {csv_file}\")\n", + " \n", + " shuffled_records, sorted_records = generate_records(10000)\n", + " print(f\" Сгенерировано 10000 записей\")\n", + " \n", + " existing_names = [shuffled_records[i][0] for i in random.sample(range(10000), 100)]\n", + " nonexisting_names = [f\"NotExist_{i}\" for i in range(10)]\n", + " search_names = existing_names + nonexisting_names\n", + " delete_names = [shuffled_records[i][0] for i in random.sample(range(10000), 50)]\n", + " \n", + " results = [[\"Структура\", \"Режим\", \"Операция\", \n", + " \"Замер1(с)\", \"Замер2(с)\", \"Замер3(с)\", \"Замер4(с)\", \"Замер5(с)\", \n", + " \"Среднее(с)\"]]\n", + " \n", + " for mode_name, records in [(\"случайный\", shuffled_records), \n", + " (\"отсортированный\", sorted_records)]:\n", + " \n", + " print(f\"\\n2. Тестирование режима: {mode_name}\")\n", + " \n", + " for struct_name in [\"linked_list\", \"hash_table\", \"bst\"]:\n", + " print(f\"\\n {struct_name.upper()}:\")\n", + " \n", + " print(\" Вставка 10000 записей\")\n", + " insert_times, filled_struct = measure_insertion(struct_name, records)\n", + " avg_insert = sum(insert_times) / 5\n", + " print(f\" Время: {avg_insert:.4f} сек (среднее)\")\n", + " \n", + " print(\" Поиск 110 записей\")\n", + " search_times = measure_search(struct_name, filled_struct, search_names)\n", + " avg_search = sum(search_times) / 5\n", + " print(f\" Время: {avg_search:.4f} сек (среднее)\")\n", + " \n", + " print(\" Удаление 50 записей\")\n", + " delete_times = measure_deletion(struct_name, filled_struct, delete_names)\n", + " avg_delete = sum(delete_times) / 5\n", + " print(f\" Время: {avg_delete:.4f} сек (среднее)\")\n", + " \n", + " results.append([struct_name, mode_name, \"вставка\"] + insert_times + [avg_insert])\n", + " results.append([struct_name, mode_name, \"поиск\"] + search_times + [avg_search])\n", + " results.append([struct_name, mode_name, \"удаление\"] + delete_times + [avg_delete])\n", + " \n", + " print(\"\\n3. Сохранение результатов\")\n", + " with open(csv_file, \"w\", newline=\"\", encoding=\"utf-8\") as f:\n", + " writer = csv.writer(f)\n", + " writer.writerows(results)\n", + " print(f\" Результаты сохранены в: {csv_file}\")\n", + " \n", + " print(\"СВОДНАЯ ТАБЛИЦА РЕЗУЛЬТАТОВ\")\n", + " print(f\"{'Структура':<15} {'Режим':<12} {'Операция':<10} {'Среднее время (сек)':<20}\")\n", + " \n", + " for row in results[1:]:\n", + " struct, mode, op, t1, t2, t3, t4, t5, avg = row\n", + " print(f\"{struct:<15} {mode:<12} {op:<10} {avg:<20.6f}\")\n", + " \n", + " return results, docs_dir\n", + "\n", + "#4. Graphics\n", + "\n", + "def create_graphs(results, docs_dir):\n", + " \n", + " print(\"\\n4. Построение графиков\")\n", + " \n", + " data = {}\n", + " for row in results[1:]:\n", + " struct = row[0]\n", + " mode = row[1]\n", + " op = row[2]\n", + " avg = row[8]\n", + " \n", + " if struct not in data:\n", + " data[struct] = {}\n", + " if mode not in data[struct]:\n", + " data[struct][mode] = {}\n", + " data[struct][mode][op] = avg\n", + " \n", + " \n", + " struct_labels = {\n", + " 'linked_list': 'LinkedList',\n", + " 'hash_table': 'HashTable',\n", + " 'bst': 'BST'\n", + " }\n", + " \n", + " \n", + " colors = {\n", + " 'linked_list': '#8b00ff', \n", + " 'hash_table': '#81d8d0', \n", + " 'bst': '#000000' \n", + " }\n", + " \n", + " \n", + " fig, axes = plt.subplots(1, 3, figsize=(15, 6))\n", + " fig.suptitle('Сравнение производительности структур данных', fontsize=16, fontweight='bold')\n", + " \n", + " operations = ['вставка', 'поиск', 'удаление']\n", + " operation_titles = ['Вставка\\n(10000 записей)', 'Поиск\\n(110 запросов)', 'Удаление\\n(50 записей)']\n", + " modes = ['случайный', 'отсортированный']\n", + " mode_labels = ['Случайный', 'Отсортированный']\n", + " \n", + " for idx, (op, op_title) in enumerate(zip(operations, operation_titles)):\n", + " ax = axes[idx]\n", + " \n", + " # Позиции для групп столбцов\n", + " x = np.arange(len(modes)) # [0, 1]\n", + " width = 0.3 # ширина одного столбца\n", + " multiplier = 0\n", + " \n", + " for struct in ['linked_list', 'hash_table', 'bst']:\n", + " values = [data[struct][mode][op] for mode in modes]\n", + " offset = width * multiplier\n", + " bars = ax.bar(x + offset, values, width, \n", + " label=struct_labels[struct], \n", + " color=colors[struct],\n", + " edgecolor='black', linewidth=0.5)\n", + " \n", + " \n", + " for bar, val in zip(bars, values):\n", + " if val < 0.01:\n", + " ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + val*0.05, \n", + " f'{val:.5f}', ha='center', va='bottom', fontsize=8, rotation=0)\n", + " else:\n", + " ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + val*0.02, \n", + " f'{val:.4f}', ha='center', va='bottom', fontsize=8, rotation=0)\n", + " \n", + " multiplier += 1\n", + " \n", + " \n", + " ax.set_title(op_title, fontsize=12, fontweight='bold')\n", + " ax.set_ylabel('Время (секунды)', fontsize=10)\n", + " ax.set_xlabel('Режим данных', fontsize=10)\n", + " ax.set_xticks(x + width)\n", + " ax.set_xticklabels(mode_labels)\n", + " ax.legend(loc='upper left', fontsize=8)\n", + " ax.grid(True, alpha=0.3, axis='y')\n", + " \n", + " \n", + " all_values = [data[s][m][op] for s in ['linked_list', 'hash_table', 'bst'] for m in modes]\n", + " if max(all_values) / min(all_values) > 100:\n", + " ax.set_yscale('log')\n", + " ax.set_ylabel('Время (секунды) - логарифмическая шкала', fontsize=9)\n", + " \n", + " plt.tight_layout()\n", + " graph_path = os.path.join(docs_dir, \"performance_graphs.png\")\n", + " plt.savefig(graph_path, dpi=150, bbox_inches='tight')\n", + " plt.close()\n", + " print(f\" Графики сохранены в: {graph_path}\")\n", + " \n", + " return graph_path\n", + "\n", + "#5. Main program\n", + "\n", + "if __name__ == \"__main__\":\n", + " \n", + " results, docs_dir = run_experiment()\n", + " \n", + " \n", + " try:\n", + " graph_file = create_graphs(results, docs_dir)\n", + " \n", + " print(\"ЭКСПЕРИМЕНТ ЗАВЕРШЕН УСПЕШНО!\")\n", + " print(\"\\n СОЗДАННЫЕ ФАЙЛЫ:\")\n", + " print(f\" Данные: {os.path.join(docs_dir, 'experiment_results.csv')}\")\n", + " print(f\" Графики: {graph_file}\")\n", + " \n", + " except Exception as e:\n", + " print(f\"\\n Ошибка при построении графиков: {e}\")\n", + " print(\" Убедитесь, что установлен matplotlib: pip install matplotlib\")\n", + " print(\"ЭКСПЕРИМЕНТ ЗАВЕРШЕН (без графиков)\")\n", + " print(f\"\\n CSV файл сохранен: {os.path.join(docs_dir, 'experiment_results.csv')}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e02735f2-61dc-484b-b74c-1456f7399863", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python [conda env:base] *", + "language": "python", + "name": "conda-base-py" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/MininaVD/docs/performance_graphs.png b/MininaVD/docs/performance_graphs.png new file mode 100644 index 0000000..3281b82 Binary files /dev/null and b/MininaVD/docs/performance_graphs.png differ