diff --git a/pogodinda/lab1/benchmark.py b/pogodinda/lab1/benchmark.py index c415c19..969263b 100644 --- a/pogodinda/lab1/benchmark.py +++ b/pogodinda/lab1/benchmark.py @@ -8,8 +8,6 @@ from bst_phonebook import * sys.setrecursionlimit(100000) - - def generate_test_data(n=10000): """Генерация тестовых данных""" uniform_records = [(f"User_{i:05d}", f"+7-999-{i:07d}") for i in range(n)] @@ -50,83 +48,96 @@ def run_benchmarks(): ('отсортированный', test_data['sorted']) ] + REPEATS = 5 # Количество повторов + for struct_name, struct_type in structures: print(f"\n=== Тестирование {struct_name} ===") for mode_name, records in modes: print(f" Режим: {mode_name}") - # Создаем структуру и меряем вставку - if struct_type == 'll': - structure = None - start = time.perf_counter() - for name, phone in records: - structure = ll_insert(structure, name, phone) - end = time.perf_counter() - insert_time = end - start + # Запускаем 5 повторов для каждой комбинации + for rep in range(1, REPEATS + 1): + print(f" Повтор {rep}/{REPEATS}...") - elif struct_type == 'ht': - structure = create_hash_table(5000) - start = time.perf_counter() - for name, phone in records: - ht_insert(structure, name, phone) - end = time.perf_counter() - insert_time = end - start + # Создаем структуру и меряем вставку + if struct_type == 'll': + structure = None + start = time.perf_counter() + for name, phone in records: + structure = ll_insert(structure, name, phone) + end = time.perf_counter() + insert_time = end - start + + elif struct_type == 'ht': + structure = create_hash_table(5000) + start = time.perf_counter() + for name, phone in records: + ht_insert(structure, name, phone) + end = time.perf_counter() + insert_time = end - start + + elif struct_type == 'bst': + structure = None + start = time.perf_counter() + for name, phone in records: + structure = bst_insert(structure, name, phone) + end = time.perf_counter() + insert_time = end - start - elif struct_type == 'bst': - structure = None + results.append([struct_name, mode_name, "вставка", insert_time, rep]) + + # Поиск start = time.perf_counter() - for name, phone in records: - structure = bst_insert(structure, name, phone) + for name in test_data['search_names']: + if struct_type == 'll': + ll_find(structure, name) + elif struct_type == 'ht': + ht_find(structure, name) + elif struct_type == 'bst': + bst_find(structure, name) end = time.perf_counter() - insert_time = end - start - - print(f" Вставка: {insert_time:.6f} сек") - results.append([struct_name, mode_name, "вставка", insert_time]) - - # Поиск - start = time.perf_counter() - for name in test_data['search_names']: - if struct_type == 'll': - ll_find(structure, name) - elif struct_type == 'ht': - ht_find(structure, name) - elif struct_type == 'bst': - bst_find(structure, name) - end = time.perf_counter() - find_time = end - start - print(f" Поиск (60 запросов): {find_time:.6f} сек") - results.append([struct_name, mode_name, "поиск", find_time]) - - # Удаление - start = time.perf_counter() - for name in test_data['delete_names']: - if struct_type == 'll': - structure = ll_delete(structure, name) - elif struct_type == 'ht': - ht_delete(structure, name) - elif struct_type == 'bst': - structure = bst_delete(structure, name) - end = time.perf_counter() - delete_time = end - start - print(f" Удаление (30 записей): {delete_time:.6f} сек") - results.append([struct_name, mode_name, "удаление", delete_time]) + find_time = end - start + results.append([struct_name, mode_name, "поиск", find_time, rep]) + + # Удаление + start = time.perf_counter() + for name in test_data['delete_names']: + if struct_type == 'll': + structure = ll_delete(structure, name) + elif struct_type == 'ht': + ht_delete(structure, name) + elif struct_type == 'bst': + structure = bst_delete(structure, name) + end = time.perf_counter() + delete_time = end - start + results.append([struct_name, mode_name, "удаление", delete_time, rep]) - # Сохраняем в CSV + # Сохраняем в CSV с колонкой "Повтор" with open('docs/data/results.csv', 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f) - writer.writerow(['Структура', 'Режим', 'Операция', 'Время (сек)']) + writer.writerow(['Структура', 'Режим', 'Операция', 'Время (сек)', 'Повтор']) writer.writerows(results) - print("\n" + "="*60) - print("ИТОГОВЫЕ РЕЗУЛЬТАТЫ (в секундах)") - print("="*60) - print(f"{'Структура':12} {'Режим':12} {'Операция':10} {'Время':>10}") - print("-"*50) - for row in results: - print(f"{row[0]:12} {row[1]:12} {row[2]:10} {row[3]:10.6f}") + # Подсчет средних значений + print("\n" + "="*70) + print("СРЕДНИЕ ЗНАЧЕНИЯ (по 5 повторам)") + print("="*70) - print(f"\nРезультаты сохранены в docs/data/results.csv") + # Собираем данные для средних + from collections import defaultdict + avg_data = defaultdict(list) + for row in results: + key = (row[0], row[1], row[2]) + avg_data[key].append(row[3]) + + print(f"{'Структура':15} {'Режим':13} {'Операция':10} {'Среднее время':>12}") + print("-"*55) + for (struct, mode, op), times in avg_data.items(): + avg_time = sum(times) / len(times) + print(f"{struct:15} {mode:13} {op:10} {avg_time:12.6f}") + + print(f"\nВсе замеры (5 повторов) сохранены в docs/data/results.csv") if __name__ == "__main__": random.seed(42) diff --git a/pogodinda/lab1/docs/data/graph.png b/pogodinda/lab1/docs/data/graph.png index 646b5d5..8164fda 100644 Binary files a/pogodinda/lab1/docs/data/graph.png and b/pogodinda/lab1/docs/data/graph.png differ diff --git a/pogodinda/lab1/docs/data/results.csv b/pogodinda/lab1/docs/data/results.csv index 3fc2edc..8ba392f 100644 --- a/pogodinda/lab1/docs/data/results.csv +++ b/pogodinda/lab1/docs/data/results.csv @@ -1,19 +1,91 @@ -Структура,Режим,Операция,Время (сек) -LinkedList,случайный,вставка,6.751254200004041 -LinkedList,случайный,поиск,0.07962289988063276 -LinkedList,случайный,удаление,0.038067599991336465 -LinkedList,отсортированный,вставка,7.378327900078148 -LinkedList,отсортированный,поиск,0.09230039990507066 -LinkedList,отсортированный,удаление,0.023862000089138746 -HashTable,случайный,вставка,0.02441199985332787 -HashTable,случайный,поиск,0.0002272999845445156 -HashTable,случайный,удаление,0.00011650007218122482 -HashTable,отсортированный,вставка,0.023515600012615323 -HashTable,отсортированный,поиск,0.00027600000612437725 -HashTable,отсортированный,удаление,0.00012340000830590725 -BST,случайный,вставка,0.03815519995987415 -BST,случайный,поиск,0.00031240005046129227 -BST,случайный,удаление,0.00018319999799132347 -BST,отсортированный,вставка,21.532808099873364 -BST,отсортированный,поиск,0.18976689991541207 -BST,отсортированный,удаление,0.07795759988948703 +Структура,Режим,Операция,Время (сек),Повтор +LinkedList,случайный,вставка,4.9375476999994135,1 +LinkedList,случайный,поиск,0.04131099999358412,1 +LinkedList,случайный,удаление,0.02149870000721421,1 +LinkedList,случайный,вставка,4.644251200006693,2 +LinkedList,случайный,поиск,0.042833100000279956,2 +LinkedList,случайный,удаление,0.020811700000194833,2 +LinkedList,случайный,вставка,4.78751110000303,3 +LinkedList,случайный,поиск,0.041008800006238744,3 +LinkedList,случайный,удаление,0.02328480000142008,3 +LinkedList,случайный,вставка,5.261260200000834,4 +LinkedList,случайный,поиск,0.043706600001314655,4 +LinkedList,случайный,удаление,0.022936199995456263,4 +LinkedList,случайный,вставка,4.584412900003372,5 +LinkedList,случайный,поиск,0.10296139999991283,5 +LinkedList,случайный,удаление,0.06556309999723453,5 +LinkedList,отсортированный,вставка,4.5104472000093665,1 +LinkedList,отсортированный,поиск,0.03982529998756945,1 +LinkedList,отсортированный,удаление,0.016976200000499375,1 +LinkedList,отсортированный,вставка,4.366683700005524,2 +LinkedList,отсортированный,поиск,0.06564230000367388,2 +LinkedList,отсортированный,удаление,0.028787899995222688,2 +LinkedList,отсортированный,вставка,4.719926499994472,3 +LinkedList,отсортированный,поиск,0.04211149999173358,3 +LinkedList,отсортированный,удаление,0.01897859999735374,3 +LinkedList,отсортированный,вставка,4.7542686000088,4 +LinkedList,отсортированный,поиск,0.036636300006648526,4 +LinkedList,отсортированный,удаление,0.018097999985911883,4 +LinkedList,отсортированный,вставка,4.634292700007791,5 +LinkedList,отсортированный,поиск,0.038695100010954775,5 +LinkedList,отсортированный,удаление,0.0167280000023311,5 +HashTable,случайный,вставка,0.02125880001403857,1 +HashTable,случайный,поиск,0.0002066000015474856,1 +HashTable,случайный,удаление,0.0001053000014508143,1 +HashTable,случайный,вставка,0.02124099999491591,2 +HashTable,случайный,поиск,0.00018730000010691583,2 +HashTable,случайный,удаление,9.57000011112541e-05,2 +HashTable,случайный,вставка,0.022729699994670227,3 +HashTable,случайный,поиск,0.00018990000535268337,3 +HashTable,случайный,удаление,9.289999434258789e-05,3 +HashTable,случайный,вставка,0.02114750001055654,4 +HashTable,случайный,поиск,0.00018650000856723636,4 +HashTable,случайный,удаление,8.990000060293823e-05,4 +HashTable,случайный,вставка,0.022626199992373586,5 +HashTable,случайный,поиск,0.0002082999999402091,5 +HashTable,случайный,удаление,0.00010770000517368317,5 +HashTable,отсортированный,вставка,0.020416200000909157,1 +HashTable,отсортированный,поиск,0.0001990000018849969,1 +HashTable,отсортированный,удаление,9.100000897888094e-05,1 +HashTable,отсортированный,вставка,0.0198104000010062,2 +HashTable,отсортированный,поиск,0.00022190000163391232,2 +HashTable,отсортированный,удаление,0.00010359998850617558,2 +HashTable,отсортированный,вставка,0.020307500002672896,3 +HashTable,отсортированный,поиск,0.00020939999376423657,3 +HashTable,отсортированный,удаление,9.639999188948423e-05,3 +HashTable,отсортированный,вставка,0.020547599997371435,4 +HashTable,отсортированный,поиск,0.00019010000687558204,4 +HashTable,отсортированный,удаление,8.830000297166407e-05,4 +HashTable,отсортированный,вставка,0.021012699988204986,5 +HashTable,отсортированный,поиск,0.00023970000620465726,5 +HashTable,отсортированный,удаление,0.00011470000026747584,5 +BST,случайный,вставка,0.0366175000090152,1 +BST,случайный,поиск,0.00028440001187846065,1 +BST,случайный,удаление,0.0001773999974830076,1 +BST,случайный,вставка,0.03504180000163615,2 +BST,случайный,поиск,0.00026760000037029386,2 +BST,случайный,удаление,0.00017100000695791095,2 +BST,случайный,вставка,0.10903169999073725,3 +BST,случайный,поиск,0.00026849999267142266,3 +BST,случайный,удаление,0.00016820000018924475,3 +BST,случайный,вставка,0.03673420000995975,4 +BST,случайный,поиск,0.00029830000130459666,4 +BST,случайный,удаление,0.00018350000027567148,4 +BST,случайный,вставка,0.03608160000294447,5 +BST,случайный,поиск,0.00028360000578686595,5 +BST,случайный,удаление,0.00017559999832883477,5 +BST,отсортированный,вставка,19.357352699997136,1 +BST,отсортированный,поиск,0.17716789999394678,1 +BST,отсортированный,удаление,0.0909034999931464,1 +BST,отсортированный,вставка,17.69543930000509,2 +BST,отсортированный,поиск,0.14151260000653565,2 +BST,отсортированный,удаление,0.0668835999967996,2 +BST,отсортированный,вставка,18.86925250000786,3 +BST,отсортированный,поиск,0.16006389999529347,3 +BST,отсортированный,удаление,0.06768140000349376,3 +BST,отсортированный,вставка,17.811097199999494,4 +BST,отсортированный,поиск,0.16981530000339262,4 +BST,отсортированный,удаление,0.0726349000033224,4 +BST,отсортированный,вставка,16.240639600000577,5 +BST,отсортированный,поиск,0.1427488000044832,5 +BST,отсортированный,удаление,0.062093499989714473,5 diff --git a/pogodinda/lab1/docs/report.md b/pogodinda/lab1/docs/report.md index ce56766..19b31e4 100644 --- a/pogodinda/lab1/docs/report.md +++ b/pogodinda/lab1/docs/report.md @@ -2,18 +2,20 @@ ## 1. Введение -В данной работе были реализованы три структуры данных для хранения телефонного справочника: связный список, хеш-таблица и двоичное дерево поиска. Проведено экспериментальное сравнение производительности операций вставки, поиска и удаления на наборе из **10 000 записей**. Для каждой структуры тестирование выполнялось на двух вариантах входных данных: случайный порядок и отсортированный по имени. +В данной работе были реализованы три структуры данных для хранения телефонного справочника: связный список, хеш-таблица и двоичное дерево поиска. Проведено экспериментальное сравнение производительности операций вставки, поиска и удаления на наборе из **10 000 записей**. + +Для каждой структуры тестирование выполнялось на двух вариантах входных данных: случайный порядок и отсортированный по имени. Каждый эксперимент повторялся **5 раз**, в таблице приведены средние значения. ## 2. Результаты измерений | Структура | Режим | Вставка, с | Поиск, с | Удаление, с | |-----------|-------|------------|----------|-------------| -| Связный список | случайный | 7.50 | 0.0376 | 0.1337 | -| Связный список | отсортированный | 7.12 | 0.0323 | 0.0263 | -| Хеш-таблица | случайный | 0.022 | 0.0001 | 0.0001 | -| Хеш-таблица | отсортированный | 0.024 | 0.0001 | 0.0001 | -| Двоичное дерево | случайный | 0.037 | 0.0001 | 0.0196 | -| Двоичное дерево | отсортированный | RecursionError | RecursionError | RecursionError | +| Связный список | случайный | 4.84 | 0.0544 | 0.0308 | +| Связный список | отсортированный | 4.60 | 0.0446 | 0.0199 | +| Хеш-таблица | случайный | 0.0218 | 0.000196 | 0.000096 | +| Хеш-таблица | отсортированный | 0.0204 | 0.000212 | 0.000098 | +| Двоичное дерево | случайный | 0.0507 | 0.000280 | 0.000175 | +| Двоичное дерево | отсортированный | 17.99 | 0.1583 | 0.0720 | ![График производительности](data/graph.png) @@ -21,28 +23,29 @@ ### 3.1. Влияние порядка данных на BST -При вставке элементов в отсортированном порядке двоичное дерево поиска вырождается в линейный список. Эксперимент подтверждает это: вставка на отсортированных данных вызвала ошибку RecursionError, в то время как на случайных данных заняла 0.037 секунды. +При вставке элементов в отсортированном порядке двоичное дерево поиска вырождается в линейный список. Эксперимент подтверждает это: вставка на отсортированных данных заняла **17.99 секунды**, что в **355 раз** медленнее, чем на случайных данных (0.0507 секунды). Поиск и удаление также замедлились примерно в 500 раз. ### 3.2. Устойчивость хеш-таблицы к порядку -Хеш-таблица использует хеш-функцию, которая равномерно распределяет ключи по корзинам независимо от порядка поступления. В случайном и отсортированном режимах время вставки практически одинаково: 0.022 и 0.024 секунды. +Хеш-таблица использует хеш-функцию, которая равномерно распределяет ключи по корзинам независимо от порядка поступления. В случайном и отсортированном режимах время вставки практически одинаково: 0.0218 и 0.0204 секунды. Разница находится в пределах погрешности измерений. -### 3.3. Медлительность связного списка при поиске +### 3.3. Медлительность связного списка -Связный список не обеспечивает прямого доступа к элементам. Время поиска в списке (0.0376 сек) в 376 раз больше, чем в хеш-таблице (0.0001 сек). +Связный список не обеспечивает прямого доступа к элементам. Вставка 10000 записей заняла **4.84 секунды** в случайном режиме. Время поиска в списке (0.0544 сек) в **278 раз** больше, чем в хеш-таблице (0.000196 сек). Удаление также значительно медленнее. ### 3.4. Сравнение удаления -- Связный список: 0.1337 сек (требует линейного поиска) -- Хеш-таблица: 0.0001 сек (мгновенно) -- BST на случайных данных: 0.0196 сек +- **Связный список**: 0.0308 сек (случайный) — требуется линейный поиск +- **Хеш-таблица**: 0.000096 сек — практически мгновенно +- **BST на случайных данных**: 0.000175 сек — очень быстро +- **BST на отсортированных данных**: 0.0720 сек — в 400 раз медленнее, чем на случайных -## 4. Вывод +## 4. Выводы -**Хеш-таблица** – оптимальный выбор, если требуется максимальная скорость поиска, вставки и удаления. В эксперименте показала стабильно высокую производительность во всех режимах. +**Хеш-таблица** – оптимальный выбор, если требуется максимальная скорость поиска, вставки и удаления. В эксперименте показала стабильно высокую производительность во всех режимах (около 0.02 секунды на вставку 10000 записей). -**Двоичное дерево поиска** – следует применять, когда необходимо получать данные в отсортированном порядке. +**Двоичное дерево поиска** – эффективно на случайных данных, но критически деградирует на отсортированных. При необходимости работы с отсортированными данными следует использовать сбалансированные деревья (AVL, красно-чёрные). -**Связный список** – практически непригоден для больших объёмов данных из-за линейной сложности. +**Связный список** – практически непригоден для больших объёмов данных из-за линейной сложности (4.84 секунды на вставку 10000 записей). -Для телефонного справочника рекомендуется использовать хеш-таблицу. \ No newline at end of file +**Для телефонного справочника рекомендуется использовать хеш-таблицу** как наиболее быстрое и предсказуемое решение. \ No newline at end of file diff --git a/pogodinda/lab1/plot_results.py b/pogodinda/lab1/plot_results.py index fbd30d8..9925972 100644 --- a/pogodinda/lab1/plot_results.py +++ b/pogodinda/lab1/plot_results.py @@ -1,26 +1,42 @@ import matplotlib.pyplot as plt import csv import numpy as np +from collections import defaultdict + +# Читаем результаты из CSV и усредняем по 5 повторам +data = defaultdict(lambda: defaultdict(lambda: defaultdict(list))) -# Читаем результаты из CSV -data = {} with open('docs/data/results.csv', 'r', encoding='utf-8') as f: reader = csv.reader(f) - next(reader) + header = next(reader) # пропускаем заголовок + + # Проверяем, есть ли колонка "Повтор" + has_repeat = 'Повтор' in header + for row in reader: - struct, mode, op, time = row[0], row[1], row[2], float(row[3]) - if op not in data: - data[op] = {} - if struct not in data[op]: - data[op][struct] = {} - data[op][struct][mode] = time + struct = row[0] + mode = row[1] + op = row[2] + time_val = float(row[3]) + + # Сохраняем все замеры + data[op][struct][mode].append(time_val) + +# Усредняем +avg_data = {} +for op in data: + avg_data[op] = {} + for struct in data[op]: + avg_data[op][struct] = {} + for mode in data[op][struct]: + times = data[op][struct][mode] + avg_data[op][struct][mode] = sum(times) / len(times) # Создаём графики operations = ['вставка', 'поиск', 'удаление'] fig, axes = plt.subplots(1, 3, figsize=(15, 5)) -# Цвета: синий и красный -colors = {'случайный': '#1f77b4', 'отсортированный': '#d62728'} # синий и красный +colors = {'случайный': '#1f77b4', 'отсортированный': '#d62728'} for idx, op in enumerate(operations): ax = axes[idx] @@ -31,10 +47,9 @@ for idx, op in enumerate(operations): x = np.arange(len(structures)) width = 0.35 - random_times = [data[op][key].get('случайный', 0) for key in data_keys] - sorted_times = [data[op][key].get('отсортированный', 0) for key in data_keys] + random_times = [avg_data[op][key].get('случайный', 0) for key in data_keys] + sorted_times = [avg_data[op][key].get('отсортированный', 0) for key in data_keys] - # Рисуем столбцы bars1 = ax.bar(x - width/2, random_times, width, label='Случайный', color=colors['случайный'], edgecolor='white', linewidth=1) bars2 = ax.bar(x + width/2, sorted_times, width, @@ -45,15 +60,12 @@ for idx, op in enumerate(operations): ax.set_xticks(x) ax.set_xticklabels(structures, fontsize=10) - # Добавляем сетку ax.grid(True, axis='y', alpha=0.3, linestyle='--') ax.set_axisbelow(True) - # Убираем верхнюю и правую рамку ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False) - # Добавляем значения на столбцы for bar in bars1: height = bar.get_height() if height > 0: @@ -70,16 +82,15 @@ for idx, op in enumerate(operations): xytext=(0, 3), textcoords="offset points", ha='center', va='bottom', fontsize=8) -# Легенда ПОД графиками, чтобы не накладывалась fig.legend(labels=['Случайный', 'Отсортированный'], loc='lower center', bbox_to_anchor=(0.5, -0.05), ncol=2, fontsize=11, frameon=True, fancybox=True, shadow=True) -plt.suptitle('Сравнение производительности структур данных (10000 записей)', +plt.suptitle('Сравнение производительности структур данных (10000 записей, среднее по 5 повторам)', fontsize=14, fontweight='bold', y=1.02) plt.tight_layout() -plt.subplots_adjust(bottom=0.12) # Оставляем место для легенды снизу +plt.subplots_adjust(bottom=0.12) plt.savefig('docs/data/graph.png', dpi=150, bbox_inches='tight', facecolor='white') plt.show() -print("График сохранён в docs/data/graph.png") \ No newline at end of file +print("График сохранён в docs/data/graph.png (использованы средние значения по 5 повторам)") \ No newline at end of file