This commit is contained in:
pogodinda 2026-05-21 20:29:55 +03:00
parent aca0eb0c84
commit 1b7ad33278
8 changed files with 436 additions and 0 deletions

133
pogodinda/lab1/benchmark.py Normal file
View File

@ -0,0 +1,133 @@
import time
import random
import csv
import sys
from linked_list_phonebook import *
from hash_table_phonebook import *
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)]
shuffled_records = uniform_records.copy()
random.shuffle(shuffled_records)
sorted_records = sorted(uniform_records, key=lambda x: x[0])
existing_names = [f"User_{i:05d}" for i in random.sample(range(n), 100)]
non_existing_names = [f"None_{i:05d}" for i in range(10)]
search_names = existing_names + non_existing_names
delete_names = [f"User_{i:05d}" for i in random.sample(range(n), 50)]
return {
'shuffled': shuffled_records,
'sorted': sorted_records,
'search_names': search_names,
'delete_names': delete_names
}
def run_benchmarks():
print("Генерация тестовых данных...")
N = 10000
test_data = generate_test_data(N)
results = []
structures = [
('LinkedList', 'll'),
('HashTable', 'ht'),
('BST', 'bst')
]
modes = [
('случайный', test_data['shuffled']),
('отсортированный', test_data['sorted'])
]
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
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
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])
# Сохраняем в CSV
with open('docs/data/results.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
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(f"\nРезультаты сохранены в docs/data/results.csv")
if __name__ == "__main__":
random.seed(42)
run_benchmarks()

View File

@ -0,0 +1,66 @@
def create_bst_node(name, phone):
return {'name': name, 'phone': phone, 'left': None, 'right': None}
def bst_insert(root, name, phone):
if root is None:
return create_bst_node(name, phone)
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):
if root is None:
return None
if name < root['name']:
return bst_find(root['left'], name)
elif name > root['name']:
return bst_find(root['right'], name)
else:
return root['phone']
def bst_find_min(root):
current = root
while current and 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 None:
return
inorder_traversal(node['left'])
records.append((node['name'], node['phone']))
inorder_traversal(node['right'])
inorder_traversal(root)
return records

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -0,0 +1,19 @@
Структура,Режим,Операция,Время (сек)
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
1 Структура Режим Операция Время (сек)
2 LinkedList случайный вставка 6.751254200004041
3 LinkedList случайный поиск 0.07962289988063276
4 LinkedList случайный удаление 0.038067599991336465
5 LinkedList отсортированный вставка 7.378327900078148
6 LinkedList отсортированный поиск 0.09230039990507066
7 LinkedList отсортированный удаление 0.023862000089138746
8 HashTable случайный вставка 0.02441199985332787
9 HashTable случайный поиск 0.0002272999845445156
10 HashTable случайный удаление 0.00011650007218122482
11 HashTable отсортированный вставка 0.023515600012615323
12 HashTable отсортированный поиск 0.00027600000612437725
13 HashTable отсортированный удаление 0.00012340000830590725
14 BST случайный вставка 0.03815519995987415
15 BST случайный поиск 0.00031240005046129227
16 BST случайный удаление 0.00018319999799132347
17 BST отсортированный вставка 21.532808099873364
18 BST отсортированный поиск 0.18976689991541207
19 BST отсортированный удаление 0.07795759988948703

View File

@ -0,0 +1,48 @@
# Отчёт по лабораторной работе "Структуры данных"
## 1. Введение
В данной работе были реализованы три структуры данных для хранения телефонного справочника: связный список, хеш-таблица и двоичное дерево поиска. Проведено экспериментальное сравнение производительности операций вставки, поиска и удаления на наборе из **10 000 записей**. Для каждой структуры тестирование выполнялось на двух вариантах входных данных: случайный порядок и отсортированный по имени.
## 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 |
![График производительности](data/graph.png)
## 3. Анализ результатов
### 3.1. Влияние порядка данных на BST
При вставке элементов в отсортированном порядке двоичное дерево поиска вырождается в линейный список. Эксперимент подтверждает это: вставка на отсортированных данных вызвала ошибку RecursionError, в то время как на случайных данных заняла 0.037 секунды.
### 3.2. Устойчивость хеш-таблицы к порядку
Хеш-таблица использует хеш-функцию, которая равномерно распределяет ключи по корзинам независимо от порядка поступления. В случайном и отсортированном режимах время вставки практически одинаково: 0.022 и 0.024 секунды.
### 3.3. Медлительность связного списка при поиске
Связный список не обеспечивает прямого доступа к элементам. Время поиска в списке (0.0376 сек) в 376 раз больше, чем в хеш-таблице (0.0001 сек).
### 3.4. Сравнение удаления
- Связный список: 0.1337 сек (требует линейного поиска)
- Хеш-таблица: 0.0001 сек (мгновенно)
- BST на случайных данных: 0.0196 сек
## 4. Вывод
**Хеш-таблица** оптимальный выбор, если требуется максимальная скорость поиска, вставки и удаления. В эксперименте показала стабильно высокую производительность во всех режимах.
**Двоичное дерево поиска** следует применять, когда необходимо получать данные в отсортированном порядке.
**Связный список** практически непригоден для больших объёмов данных из-за линейной сложности.
Для телефонного справочника рекомендуется использовать хеш-таблицу.

View File

@ -0,0 +1,31 @@
from linked_list_phonebook import ll_insert, ll_find, ll_delete, ll_list_all
def hash_function(name, table_size):
hash_value = 0
for char in name:
hash_value = (hash_value * 31 + ord(char)) % table_size
return hash_value
def create_hash_table(size=1000):
return [None] * size
def ht_insert(buckets, name, phone):
index = hash_function(name, len(buckets))
buckets[index] = ll_insert(buckets[index], name, phone)
def ht_find(buckets, name):
index = hash_function(name, len(buckets))
return ll_find(buckets[index], name)
def ht_delete(buckets, name):
index = hash_function(name, len(buckets))
buckets[index] = ll_delete(buckets[index], name)
def ht_list_all(buckets):
all_records = []
for head in buckets:
if head is not None:
all_records.extend(ll_list_all(head))
all_records.sort(key=lambda x: x[0])
return all_records

View File

@ -0,0 +1,54 @@
def create_node(name, phone):
return {'name': name, 'phone': phone, 'next': None}
def ll_insert(head, name, phone):
if head is None:
return create_node(name, phone)
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'] = create_node(name, phone)
return head
def ll_find(head, name):
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:
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((current['name'], current['phone']))
current = current['next']
records.sort(key=lambda x: x[0])
return records

View File

@ -0,0 +1,85 @@
import matplotlib.pyplot as plt
import csv
import numpy as np
# Читаем результаты из CSV
data = {}
with open('docs/data/results.csv', 'r', encoding='utf-8') as f:
reader = csv.reader(f)
next(reader)
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
# Создаём графики
operations = ['вставка', 'поиск', 'удаление']
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# Цвета: синий и красный
colors = {'случайный': '#1f77b4', 'отсортированный': '#d62728'} # синий и красный
for idx, op in enumerate(operations):
ax = axes[idx]
structures = ['Связный список', 'Хеш-таблица', 'Двоичное дерево']
data_keys = ['LinkedList', 'HashTable', 'BST']
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]
# Рисуем столбцы
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,
label='Отсортированный', color=colors['отсортированный'], edgecolor='white', linewidth=1)
ax.set_ylabel('Время (секунды)', fontsize=11)
ax.set_title(f'{op.upper()}', fontsize=13, fontweight='bold')
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:
ax.annotate(f'{height:.4f}',
xy=(bar.get_x() + bar.get_width()/2, height),
xytext=(0, 3), textcoords="offset points",
ha='center', va='bottom', fontsize=8)
for bar in bars2:
height = bar.get_height()
if height > 0:
ax.annotate(f'{height:.4f}',
xy=(bar.get_x() + bar.get_width()/2, height),
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 записей)',
fontsize=14, fontweight='bold', y=1.02)
plt.tight_layout()
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")