forked from UNN/2026-rff_mp
[1] lab1
This commit is contained in:
parent
aca0eb0c84
commit
1b7ad33278
133
pogodinda/lab1/benchmark.py
Normal file
133
pogodinda/lab1/benchmark.py
Normal 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()
|
||||
66
pogodinda/lab1/bst_phonebook.py
Normal file
66
pogodinda/lab1/bst_phonebook.py
Normal 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
|
||||
BIN
pogodinda/lab1/docs/data/graph.png
Normal file
BIN
pogodinda/lab1/docs/data/graph.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
19
pogodinda/lab1/docs/data/results.csv
Normal file
19
pogodinda/lab1/docs/data/results.csv
Normal 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
|
||||
|
48
pogodinda/lab1/docs/report.md
Normal file
48
pogodinda/lab1/docs/report.md
Normal 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 |
|
||||
|
||||

|
||||
|
||||
## 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. Вывод
|
||||
|
||||
**Хеш-таблица** – оптимальный выбор, если требуется максимальная скорость поиска, вставки и удаления. В эксперименте показала стабильно высокую производительность во всех режимах.
|
||||
|
||||
**Двоичное дерево поиска** – следует применять, когда необходимо получать данные в отсортированном порядке.
|
||||
|
||||
**Связный список** – практически непригоден для больших объёмов данных из-за линейной сложности.
|
||||
|
||||
Для телефонного справочника рекомендуется использовать хеш-таблицу.
|
||||
31
pogodinda/lab1/hash_table_phonebook.py
Normal file
31
pogodinda/lab1/hash_table_phonebook.py
Normal 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
|
||||
54
pogodinda/lab1/linked_list_phonebook.py
Normal file
54
pogodinda/lab1/linked_list_phonebook.py
Normal 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
|
||||
85
pogodinda/lab1/plot_results.py
Normal file
85
pogodinda/lab1/plot_results.py
Normal 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")
|
||||
Loading…
Reference in New Issue
Block a user