288 lines
15 KiB
Python
288 lines
15 KiB
Python
|
|
import csv
|
|||
|
|
import os
|
|||
|
|
import numpy as np
|
|||
|
|
from matplotlib import pyplot as plt
|
|||
|
|
|
|||
|
|
def ensure_directories():
|
|||
|
|
os.makedirs('docs/data', exist_ok=True)
|
|||
|
|
|
|||
|
|
def save_to_csv(results, filename="docs/data/results.csv"):
|
|||
|
|
ensure_directories()
|
|||
|
|
|
|||
|
|
with open(filename, 'w', newline='', encoding='utf-8') as f:
|
|||
|
|
writer = csv.writer(f)
|
|||
|
|
writer.writerow(['Структура', 'Режим', 'Операция',
|
|||
|
|
'Повтор1', 'Повтор2', 'Повтор3', 'Повтор4', 'Повтор5',
|
|||
|
|
'Среднее', 'Стд_откл'])
|
|||
|
|
|
|||
|
|
for res in results:
|
|||
|
|
struct_name = res['structure']
|
|||
|
|
mode = res['mode']
|
|||
|
|
|
|||
|
|
for op, times, mean, std in [
|
|||
|
|
('вставка', res['insert_all'], res['insert_mean'], res['insert_std']),
|
|||
|
|
('поиск', res['find_all'], res['find_mean'], res['find_std']),
|
|||
|
|
('удаление', res['delete_all'], res['delete_mean'], res['delete_std'])
|
|||
|
|
]:
|
|||
|
|
row = [struct_name, mode, op] + times + [mean, std]
|
|||
|
|
writer.writerow(row)
|
|||
|
|
|
|||
|
|
def plot_results(results, filename="docs/performance_chart.png"):
|
|||
|
|
ensure_directories()
|
|||
|
|
struct_names = {
|
|||
|
|
'linkedlist': 'LinkedList',
|
|||
|
|
'hashtable': 'HashTable',
|
|||
|
|
'bst': 'BST'
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
operations = ['insert', 'find', 'delete']
|
|||
|
|
op_names = {'insert': 'Вставка', 'find': 'Поиск', 'delete': 'Удаление'}
|
|||
|
|
random_data = {}
|
|||
|
|
sorted_data = {}
|
|||
|
|
|
|||
|
|
for res in results:
|
|||
|
|
struct_name = struct_names.get(res['structure'], res['structure'])
|
|||
|
|
mode = res['mode']
|
|||
|
|
|
|||
|
|
if mode == 'случайный':
|
|||
|
|
random_data[struct_name] = {
|
|||
|
|
'insert': res['insert_mean'],
|
|||
|
|
'find': res['find_mean'],
|
|||
|
|
'delete': res['delete_mean']
|
|||
|
|
}
|
|||
|
|
else:
|
|||
|
|
sorted_data[struct_name] = {
|
|||
|
|
'insert': res['insert_mean'],
|
|||
|
|
'find': res['find_mean'],
|
|||
|
|
'delete': res['delete_mean']
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
structure_order = ['LinkedList', 'HashTable', 'BST']
|
|||
|
|
|
|||
|
|
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
|||
|
|
|
|||
|
|
for idx, op in enumerate(operations):
|
|||
|
|
ax = axes[idx]
|
|||
|
|
|
|||
|
|
x = np.arange(len(structure_order))
|
|||
|
|
width = 0.35
|
|||
|
|
|
|||
|
|
random_means = []
|
|||
|
|
sorted_means = []
|
|||
|
|
|
|||
|
|
for struct in structure_order:
|
|||
|
|
if struct in random_data:
|
|||
|
|
random_means.append(random_data[struct][op])
|
|||
|
|
else:
|
|||
|
|
random_means.append(0)
|
|||
|
|
|
|||
|
|
if struct in sorted_data:
|
|||
|
|
sorted_means.append(sorted_data[struct][op])
|
|||
|
|
else:
|
|||
|
|
sorted_means.append(0)
|
|||
|
|
|
|||
|
|
if not random_means and not sorted_means:
|
|||
|
|
print(f" Нет данных для операции {op}")
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
bars1 = ax.bar(x - width/2, random_means, width,
|
|||
|
|
label='Случайный порядок', color='skyblue')
|
|||
|
|
bars2 = ax.bar(x + width/2, sorted_means, width,
|
|||
|
|
label='Отсортированный порядок', color='salmon')
|
|||
|
|
|
|||
|
|
ax.set_xlabel('Структура данных')
|
|||
|
|
ax.set_ylabel('Время (секунды)')
|
|||
|
|
ax.set_title(f'{op_names.get(op, op)}')
|
|||
|
|
ax.set_xticks(x)
|
|||
|
|
ax.set_xticklabels(structure_order)
|
|||
|
|
ax.legend()
|
|||
|
|
|
|||
|
|
for bar in bars1 + bars2:
|
|||
|
|
height = bar.get_height()
|
|||
|
|
if height > 0:
|
|||
|
|
ax.annotate(f'{height:.3f}',
|
|||
|
|
xy=(bar.get_x() + bar.get_width() / 2, height),
|
|||
|
|
xytext=(0, 3), textcoords="offset points",
|
|||
|
|
ha='center', va='bottom', fontsize=8)
|
|||
|
|
|
|||
|
|
plt.tight_layout()
|
|||
|
|
plt.savefig(filename, dpi=150)
|
|||
|
|
plt.show()
|
|||
|
|
|
|||
|
|
def save_report_md(results, filename="docs/report.md"):
|
|||
|
|
ensure_directories()
|
|||
|
|
|
|||
|
|
results_dict = {}
|
|||
|
|
for res in results:
|
|||
|
|
key = (res['structure'], res['mode'])
|
|||
|
|
results_dict[key] = res
|
|||
|
|
|
|||
|
|
def get_val(struct, mode, field):
|
|||
|
|
key = (struct, mode)
|
|||
|
|
if key in results_dict:return results_dict[key][field]
|
|||
|
|
return 0.0
|
|||
|
|
|
|||
|
|
ll_random_insert = get_val('linkedlist', 'случайный', 'insert_mean')
|
|||
|
|
ll_random_find = get_val('linkedlist', 'случайный', 'find_mean')
|
|||
|
|
ll_random_delete = get_val('linkedlist', 'случайный', 'delete_mean')
|
|||
|
|
ll_sorted_insert = get_val('linkedlist', 'отсортированный', 'insert_mean')
|
|||
|
|
ll_sorted_find = get_val('linkedlist', 'отсортированный', 'find_mean')
|
|||
|
|
ll_sorted_delete = get_val('linkedlist', 'отсортированный', 'delete_mean')
|
|||
|
|
|
|||
|
|
ht_random_insert = get_val('hashtable', 'случайный', 'insert_mean')
|
|||
|
|
ht_random_find = get_val('hashtable', 'случайный', 'find_mean')
|
|||
|
|
ht_random_delete = get_val('hashtable', 'случайный', 'delete_mean')
|
|||
|
|
ht_sorted_insert = get_val('hashtable', 'отсортированный', 'insert_mean')
|
|||
|
|
ht_sorted_find = get_val('hashtable', 'отсортированный', 'find_mean')
|
|||
|
|
ht_sorted_delete = get_val('hashtable', 'отсортированный', 'delete_mean')
|
|||
|
|
|
|||
|
|
bst_random_insert = get_val('bst', 'случайный', 'insert_mean')
|
|||
|
|
bst_random_find = get_val('bst', 'случайный', 'find_mean')
|
|||
|
|
bst_random_delete = get_val('bst', 'случайный', 'delete_mean')
|
|||
|
|
bst_sorted_insert = get_val('bst', 'отсортированный', 'insert_mean')
|
|||
|
|
bst_sorted_find = get_val('bst', 'отсортированный', 'find_mean')
|
|||
|
|
bst_sorted_delete = get_val('bst', 'отсортированный', 'delete_mean')
|
|||
|
|
|
|||
|
|
from datetime import datetime
|
|||
|
|
|
|||
|
|
report_content = f"""# Отчёт по лабораторной работе
|
|||
|
|
|
|||
|
|
## Цель работы
|
|||
|
|
|
|||
|
|
Реализовать три структуры данных «с нуля» (связный список, хеш-таблица, двоичное дерево поиска), применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций.
|
|||
|
|
|
|||
|
|
## Параметры эксперимента
|
|||
|
|
|
|||
|
|
- Количество записей: 10000
|
|||
|
|
- Количество повторов каждого теста: 5
|
|||
|
|
- Размер хеш-таблицы: 1000 корзин
|
|||
|
|
|
|||
|
|
## Результаты экспериментов
|
|||
|
|
|
|||
|
|
### 1. Связный список
|
|||
|
|
|
|||
|
|
| Режим | Вставка (сек) | Поиск (сек) | Удаление (сек) |
|
|||
|
|
|-------|---------------|-------------|----------------|
|
|||
|
|
| Случайный | {ll_random_insert:.4f} | {ll_random_find:.4f} | {ll_random_delete:.4f} |
|
|||
|
|
| Отсортированный | {ll_sorted_insert:.4f} | {ll_sorted_find:.4f} | {ll_sorted_delete:.4f} |
|
|||
|
|
|
|||
|
|
### 2. Хеш-таблица
|
|||
|
|
|
|||
|
|
| Режим | Вставка (сек) | Поиск (сек) | Удаление (сек) |
|
|||
|
|
|-------|---------------|-------------|----------------|
|
|||
|
|
| Случайный | {ht_random_insert:.4f} | {ht_random_find:.4f} | {ht_random_delete:.4f} |
|
|||
|
|
| Отсортированный | {ht_sorted_insert:.4f} | {ht_sorted_find:.4f} | {ht_sorted_delete:.4f} |
|
|||
|
|
|
|||
|
|
### 3. Двоичное дерево поиска (BST)
|
|||
|
|
|
|||
|
|
| Режим | Вставка (сек) | Поиск (сек) | Удаление (сек) |
|
|||
|
|
|-------|---------------|-------------|----------------|
|
|||
|
|
| Случайный | {bst_random_insert:.4f} | {bst_random_find:.4f} | {bst_random_delete:.4f} |
|
|||
|
|
| Отсортированный | {bst_sorted_insert:.4f} | {bst_sorted_find:.4f} | {bst_sorted_delete:.4f} |
|
|||
|
|
|
|||
|
|
## Анализ результатов
|
|||
|
|
|
|||
|
|
### 1. Влияние порядка данных на BST
|
|||
|
|
При добавлении уже отсортированных элементов BST вырождается в линейную структуру — сложность падает с O(log n) до O(n).
|
|||
|
|
Время вставки выросло с {bst_random_insert:.4f} до {bst_sorted_insert:.4f} секунд — замедление в {bst_sorted_insert/bst_random_insert:.1f} раза.
|
|||
|
|
|
|||
|
|
### 2. Почему хеш-таблица не чувствительна к порядку
|
|||
|
|
|
|||
|
|
Хеш-функция равномерно распределяет ключи по корзинам независимо от их исходного порядка. Поэтому последовательность добавления практически не влияет на производительность.
|
|||
|
|
Сравнение случайного и упорядоченного ввода:
|
|||
|
|
- Случайный режим: {ht_random_insert:.4f} с
|
|||
|
|
- Упорядоченный режим: {ht_sorted_insert:.4f} с
|
|||
|
|
- Различие: {ht_sorted_insert/ht_random_insert:.2f}
|
|||
|
|
|
|||
|
|
### 3. Почему связный список медленный при поиске
|
|||
|
|
|
|||
|
|
Поиск в связном списке требует линейного обхода O(n) — структура не поддерживает произвольный доступ. Это делает его непригодным для крупных справочников, где нужен быстрый поиск по ключу.
|
|||
|
|
|
|||
|
|
Сравнение скорости поиска на случайных данных:
|
|||
|
|
- LinkedList: {ll_random_find:.4f} сек
|
|||
|
|
- HashTable: {ht_random_find:.4f} сек (преимущество в {ll_random_find/ht_random_find:.1f})
|
|||
|
|
- BST: {bst_random_find:.4f} сек
|
|||
|
|
|
|||
|
|
### 4. Сравнение удаления
|
|||
|
|
|
|||
|
|
| Структура | Сложность | Время на 50 удалений (случайные данные) |
|
|||
|
|
|-----------|-----------|------------------------------------------|
|
|||
|
|
| Связный список | O(n) | {ll_random_delete:.4f} сек|
|
|||
|
|
| Хеш-таблица | O(1) в среднем | {ht_random_delete:.4f} сек |
|
|||
|
|
| BST | O(log n) в среднем | {bst_random_delete:.4f} сек |
|
|||
|
|
|
|||
|
|
## Вывод:
|
|||
|
|
|
|||
|
|
| Задача | Рекомендация | Почему |
|
|||
|
|
|--------|-------------|--------|
|
|||
|
|
| Частый поиск | Хеш-таблица | O(1) в среднем, не зависит от порядка |
|
|||
|
|
| Частые вставки/удаления | Хеш-таблица | Амортизированное O(1) |
|
|||
|
|
| Нужен отсортированный вывод | Сбалансированное дерево (AVL/Red-Black) | In-order обход даёт сортировку |
|
|||
|
|
| Мало данных (<100 элементов) | Связный список или массив | Простота, накладные расходы не оправданы |
|
|||
|
|
| Последовательный доступ (очередь/стек) | Связный список | Вставка/удаление в начало/конец за O(1) |
|
|||
|
|
|
|||
|
|
## Заключение
|
|||
|
|
Проведённый эксперимент подтверждает теоретические оценки сложности:
|
|||
|
|
|
|||
|
|
1. **Небалансированное BST это плохой выбор** при работе с реальными данными, которые могут оказаться упорядоченными. Деградация до O(n) делает его непригодным для надёжных систем.
|
|||
|
|
|
|||
|
|
2. **Хеш-таблица показывает стабильные результаты** вне зависимости от порядка входных данных — ключевое преимущество для телефонного справочника с произвольными именами абонентов.
|
|||
|
|
|
|||
|
|
3. **Связный список — нишевый инструмент**, эффективный только при работе с малыми объёмами данных.
|
|||
|
|
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
with open(filename, 'w', encoding='utf-8') as f:
|
|||
|
|
f.write(report_content)
|
|||
|
|
|
|||
|
|
def print_analysis(results):
|
|||
|
|
print("\n" + "="*60)
|
|||
|
|
print("Анализ результатов")
|
|||
|
|
print("="*60)
|
|||
|
|
|
|||
|
|
best_insert = min(results, key=lambda x: x['insert_mean'])
|
|||
|
|
best_find = min(results, key=lambda x: x['find_mean'])
|
|||
|
|
best_delete = min(results, key=lambda x: x['delete_mean'])
|
|||
|
|
|
|||
|
|
print(f"\n Лучшая для вставки: {best_insert['structure']} ({best_insert['mode']}) - {best_insert['insert_mean']:.4f} сек")
|
|||
|
|
print(f" Лучшая для поиска: {best_find['structure']} ({best_find['mode']}) - {best_find['find_mean']:.4f} сек")
|
|||
|
|
print(f" Лучшая для удаления: {best_delete['structure']} ({best_delete['mode']}) - {best_delete['delete_mean']:.4f} сек")
|
|||
|
|
|
|||
|
|
bst_random = None
|
|||
|
|
bst_sorted = None
|
|||
|
|
for res in results:
|
|||
|
|
if res['structure'] == 'bst' and res['mode'] == 'случайный':
|
|||
|
|
bst_random = res
|
|||
|
|
elif res['structure'] == 'bst' and res['mode'] == 'отсортированный':
|
|||
|
|
bst_sorted = res
|
|||
|
|
|
|||
|
|
if bst_random and bst_sorted:
|
|||
|
|
print("\n Влияние порядка данных на BST:")
|
|||
|
|
print(f" Вставка: случайный {bst_random['insert_mean']:.4f} сек vs отсортированный {bst_sorted['insert_mean']:.4f} сек")
|
|||
|
|
print(f" Деградация в {bst_sorted['insert_mean']/bst_random['insert_mean']:.1f}x")
|
|||
|
|
|
|||
|
|
ht_random = None
|
|||
|
|
ht_sorted = None
|
|||
|
|
for res in results:
|
|||
|
|
if res['structure'] == 'hashtable' and res['mode'] == 'случайный':
|
|||
|
|
ht_random = res
|
|||
|
|
elif res['structure'] == 'hashtable' and res['mode'] == 'отсортированный':
|
|||
|
|
ht_sorted = res
|
|||
|
|
|
|||
|
|
if ht_random and ht_sorted:
|
|||
|
|
print("\n Чувствительность хеш-таблицы к порядку:")
|
|||
|
|
print(f" Вставка: случайный {ht_random['insert_mean']:.4f} сек vs отсортированный {ht_sorted['insert_mean']:.4f} сек")
|
|||
|
|
print(f" Отношение: {ht_sorted['insert_mean']/ht_random['insert_mean']:.2f}x (почти не чувствительна)")
|
|||
|
|
|
|||
|
|
ll_random = None
|
|||
|
|
for res in results:
|
|||
|
|
if res['structure'] == 'linkedlist' and res['mode'] == 'случайный':
|
|||
|
|
ll_random = res
|
|||
|
|
elif res['structure'] == 'hashtable' and res['mode'] == 'случайный':
|
|||
|
|
ht_random = res
|
|||
|
|
|
|||
|
|
if ll_random and ht_random:
|
|||
|
|
print("\n Сравнение скорости поиска:")
|
|||
|
|
print(f" LinkedList: {ll_random['find_mean']:.4f} сек")
|
|||
|
|
print(f" HashTable: {ht_random['find_mean']:.4f} сек")
|
|||
|
|
print(f" HashTable быстрее в {ll_random['find_mean']/ht_random['find_mean']:.1f} раз")
|