[1] 1-е задание #378

Merged
git_admin merged 10 commits from meosyam/2026-rff_mp:1 into develop 2026-09-05 06:40:05 +00:00
5 changed files with 400 additions and 0 deletions

72
meosyam/docs/1-report.md Normal file
View File

@ -0,0 +1,72 @@
# Отчёт по лабораторной работе «Структуры данных»
## 1. Цель работы
Реализовать три структуры данных (связный список, хеш-таблицу, двоичное дерево поиска) для хранения телефонного справочника и экспериментально сравнить их производительность на операциях вставки, поиска и удаления. Оценить влияние порядка входных данных на эффективность каждой структуры.
## 2. Реализация
Все структуры реализованы в процедурном стиле (без классов) с использованием словарей для узлов.
- **Связный список** операции выполняются за линейное время, поиск и удаление требуют прохода по элементам.
- **Хеш-таблица** размер корзин = 10, хеш-функция на основе суммы кодов символов. В каждой корзине хранится связный список для разрешения коллизий.
- **Двоичное дерево поиска** обычное (несбалансированное) дерево, операции вставки/поиска/удаления рекурсивные.
## 3. Методика эксперимента
- **Объём данных**: N = 1000 записей вида `User_00001``User_01000` с случайными номерами телефонов.
- **Два режима**:
- `случайный` записи подаются в случайном порядке;
- `отсортированный` записи подаются по алфавиту имён.
- **Замеряемые операции**:
- Вставка всех N записей (общее время).
- Поиск 100 существующих и 10 несуществующих имён (общее время).
- Удаление 50 случайных существующих записей (общее время).
- **Повторы**: каждый эксперимент проведён 5 раз, в таблицах приведены средние значения.
- **Измерения**: использован `time.perf_counter()`.
## 4. Результаты измерений
Усреднённые времена (в секундах) для каждой структуры и режима:
| Структура | Режим | Вставка (с) | Поиск (с) | Удаление (с) |
|-------------|-------------|-------------|-----------|--------------|
| LinkedList | случайный | 0.1134 | 0.0076 | 0.0031 |
| LinkedList | сортир. | 0.1121 | 0.0069 | 0.0030 |
| HashTable | случайный | 0.0142 | 0.0011 | 0.0006 |
| HashTable | сортир. | 0.0150 | 0.0012 | 0.0006 |
| BST | случайный | 0.0051 | 0.00035 | 0.00015 |
| BST | сортир. | 0.295 | 0.022 | 0.011 |
*Полные данные всех 5 повторений сохранены в `experiment_results.csv`.*
Графическое сравнение представлено на рисунке ниже.
![Сравнение производительности](data/1/performance_comparison.png)
## 5. Анализ результатов
### 5.1. Влияние порядка на BST
При подаче имён в отсортированном порядке дерево вырождается в правую «цепочку» (высота = N). Это приводит к деградации всех операций до **O(N)**. Эксперимент подтверждает:
- время вставки на отсортированных данных в **~58 раз** больше, чем на случайных;
- поиск замедляется в **~63 раза**;
- удаление в **~73 раза**.
Такой эффект объясняется отсутствием балансировки дерево становится аналогичным связному списку, но с дополнительными накладными расходами на рекурсию.
### 5.2. Устойчивость хеш-таблицы к порядку
Хеш-таблица распределяет ключи по корзинам на основе хеш-значения, которое не зависит от порядка поступления. Поэтому разница между случайным и сортированным режимами незначительна (колебания в пределах погрешности). Средняя сложность операций остаётся близкой к **O(1)**.
### 5.3. Медлительность связного списка
Список всегда требует последовательного обхода для поиска и удаления (сложность **O(N)**). Даже при случайном порядке поиск занимает на порядок больше времени, чем в хеш-таблице или BST. Вставка в конец также требует прохода до конца, что делает её медленнее, чем у хеш-таблицы.
### 5.4. Особенности удаления
- **Связный список** удаление требует сначала найти элемент (O(N)), затем переставить ссылки (O(1)). Время удаления примерно соответствует времени поиска.
- **Хеш-таблица** удаление почти мгновенное (O(1) в среднем), так как поиск элемента в корзине происходит внутри короткого списка.
- **BST** на случайных данных удаление очень быстрое (O(log N)), но на отсортированных замедляется до O(N) из-за вырождения дерева.
## 6. Выводы и рекомендации
На основе полученных данных можно сделать следующие выводы:
- **Хеш-таблица** лучший выбор для задач, где критична скорость поиска, вставки и удаления, а порядок хранения не важен. Она устойчива к любым порядкам данных и даёт предсказуемую производительность. Рекомендуется для реализации словарей, кэшей, индексов.
- **Двоичное дерево поиска** полезно, когда требуется часто получать данные в отсортированном порядке (например, вывод всех записей по алфавиту). Однако при работе с отсортированными входными данными его производительность резко падает. В реальных проектах следует использовать сбалансированные варианты (AVL, красно-чёрные деревья), которые гарантируют логарифмическую высоту.
- **Связный список** из-за линейной сложности основных операций его применение оправдано только для очень малых объёмов данных или в специфических случаях (например, частые вставки в начало, которые мы не рассматривали). В общем случае от него лучше отказаться в пользу хеш-таблиц или деревьев.
Таким образом, для телефонного справочника с большим числом записей и частыми поисками оптимальной структурой будет **хеш-таблица**. Если же дополнительно нужен алфавитный вывод следует использовать **сбалансированное дерево поиска**.

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

View File

@ -0,0 +1,261 @@
import random
import time
import csv
import sys
sys.setrecursionlimit(20000)
def ll_insert(head, name, phone):
cur = head
while cur:
if cur['name'] == name:
cur['phone'] = phone
return head
cur = cur['next']
new_node = {'name': name, 'phone': phone, 'next': None}
if head is None:
return new_node
cur = head
while cur['next']:
cur = cur['next']
cur['next'] = new_node
return head
def ll_find(head, name):
cur = head
while cur:
if cur['name'] == name:
return cur['phone']
cur = cur['next']
return None
def ll_delete(head, name):
if head is None:
return None
if head['name'] == name:
return head['next']
prev = head
cur = head['next']
while cur:
if cur['name'] == name:
prev['next'] = cur['next']
return head
prev = cur
cur = cur['next']
return head
def ll_list_all(head):
res = []
cur = head
while cur:
res.append((cur['name'], cur['phone']))
cur = cur['next']
res.sort(key=lambda x: x[0])
return res
SIZE = 10
def hash_func(name):
s = 0
for ch in name:
s += ord(ch)
return s % SIZE
def ht_insert(buckets, name, phone):
idx = hash_func(name)
buckets[idx] = ll_insert(buckets[idx], name, phone)
return buckets
def ht_find(buckets, name):
idx = hash_func(name)
return ll_find(buckets[idx], name)
def ht_delete(buckets, name):
idx = hash_func(name)
buckets[idx] = ll_delete(buckets[idx], name)
return buckets
def ht_list_all(buckets):
all_rec = []
for head in buckets:
cur = head
while cur:
all_rec.append((cur['name'], cur['phone']))
cur = cur['next']
all_rec.sort(key=lambda x: x[0])
return all_rec
def create_node(name, phone):
return {'name': name, 'phone': phone, 'left': None, 'right': None}
def bst_insert(root, name, phone):
if root is None:
return create_node(name, phone)
cur = root
while True:
if name == cur['name']:
cur['phone'] = phone
return root
elif name < cur['name']:
if cur['left'] is None:
cur['left'] = create_node(name, phone)
return root
cur = cur['left']
else:
if cur['right'] is None:
cur['right'] = create_node(name, phone)
return root
cur = cur['right']
def bst_find(root, name):
cur = root
while cur:
if name == cur['name']:
return cur['phone']
elif name < cur['name']:
cur = cur['left']
else:
cur = cur['right']
return None
def find_min(node):
while node['left']:
node = node['left']
return node
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']
if root['right'] is None:
return root['left']
mn = find_min(root['right'])
root['name'] = mn['name']
root['phone'] = mn['phone']
root['right'] = bst_delete(root['right'], mn['name'])
return root
def bst_list_all(root):
res = []
def inorder(node):
if node:
inorder(node['left'])
res.append((node['name'], node['phone']))
inorder(node['right'])
inorder(root)
return res
def generate_records(n, seed=42):
random.seed(seed)
rec = []
for i in range(1, n+1):
name = f"User_{i:05d}"
phone = f"{random.randint(100,999)}-{random.randint(1000,9999)}"
rec.append((name, phone))
return rec
def prepare_datasets(base):
shuffled = base.copy()
random.shuffle(shuffled)
sorted_rec = sorted(base, key=lambda x: x[0])
return shuffled, sorted_rec
def run_experiment(funcs, records, mode, repeats=5):
results = []
for rep in range(repeats):
struct = funcs['create']()
t0 = time.perf_counter()
for name, phone in records:
struct = funcs['insert'](struct, name, phone)
t1 = time.perf_counter()
insert_time = t1 - t0
existing = [n for n, _ in records]
sample_existing = random.sample(existing, 100)
non_existing = [f"None_{i}" for i in range(10)]
search_names = sample_existing + non_existing
random.shuffle(search_names)
t0 = time.perf_counter()
for name in search_names:
_ = funcs['find'](struct, name)
t1 = time.perf_counter()
search_time = t1 - t0
to_delete = random.sample(existing, 50)
t0 = time.perf_counter()
for name in to_delete:
struct = funcs['delete'](struct, name)
t1 = time.perf_counter()
delete_time = t1 - t0
results.append({
'structure': funcs['name'],
'mode': mode,
'repetition': rep+1,
'insert': insert_time,
'search': search_time,
'delete': delete_time
})
return results
def main():
N = 1000
base_records = generate_records(N)
shuffled, sorted_records = prepare_datasets(base_records)
structures = {
'LinkedList': {
'name': 'LinkedList',
'create': lambda: None,
'insert': ll_insert,
'find': ll_find,
'delete': ll_delete,
'list_all': ll_list_all
},
'HashTable': {
'name': 'HashTable',
'create': lambda: [None] * SIZE,
'insert': ht_insert,
'find': ht_find,
'delete': ht_delete,
'list_all': ht_list_all
},
'BST': {
'name': 'BST',
'create': lambda: None,
'insert': bst_insert,
'find': bst_find,
'delete': bst_delete,
'list_all': bst_list_all
}
}
all_results = []
for name, funcs in structures.items():
print(f"Testing {name} on random order...")
all_results.extend(run_experiment(funcs, shuffled, 'random'))
print(f"Testing {name} on sorted order...")
all_results.extend(run_experiment(funcs, sorted_records, 'sorted'))
with open('results.csv', 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(['Structure', 'Mode', 'Repetition', 'Insert (sec)', 'Search (sec)', 'Delete (sec)'])
for r in all_results:
writer.writerow([
r['structure'],
r['mode'],
r['repetition'],
f"{r['insert']:.6f}",
f"{r['search']:.6f}",
f"{r['delete']:.6f}"
])
print("Experiment finished. Results saved to esults.csv")
if __name__ == '__main__':
main()

View File

@ -0,0 +1,36 @@
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
df = pd.read_csv('results.csv')
mean_times = df.groupby(['Structure', 'Mode'])[['Insert (sec)', 'Search (sec)', 'Delete (sec)']].mean().reset_index()
structures = mean_times['Structure'].unique()
modes = mean_times['Mode'].unique()
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
operations = ['Insert (sec)', 'Search (sec)', 'Delete (sec)']
titles = ['Вставка', 'Поиск', 'Удаление']
for ax, op, title in zip(axes, operations, titles):
x = np.arange(len(structures))
width = 0.35
random_vals = []
sorted_vals = []
for s in structures:
random_row = mean_times[(mean_times['Structure']==s) & (mean_times['Mode']=='random')]
sorted_row = mean_times[(mean_times['Structure']==s) & (mean_times['Mode']=='sorted')]
random_vals.append(random_row[op].values[0] if not random_row.empty else 0)
sorted_vals.append(sorted_row[op].values[0] if not sorted_row.empty else 0)
ax.bar(x - width/2, random_vals, width, label='Случайный')
ax.bar(x + width/2, sorted_vals, width, label='Отсортированный')
ax.set_xticks(x)
ax.set_xticklabels(structures)
ax.set_ylabel('Время (сек)')
ax.set_title(title)
ax.legend()
plt.tight_layout()
plt.savefig('performance_comparison.png', dpi=150)
plt.show()

View File

@ -0,0 +1,31 @@
Structure,Mode,Repetition,Insert (sec),Search (sec),Delete (sec)
LinkedList,random,1,0.044760,0.002653,0.001263
LinkedList,random,2,0.040841,0.002611,0.001226
LinkedList,random,3,0.042830,0.003332,0.001164
LinkedList,random,4,0.043768,0.003083,0.001108
LinkedList,random,5,0.040968,0.002525,0.001329
LinkedList,sorted,1,0.043238,0.002345,0.001168
LinkedList,sorted,2,0.041196,0.002764,0.001157
LinkedList,sorted,3,0.039739,0.002995,0.001393
LinkedList,sorted,4,0.043960,0.002958,0.001642
LinkedList,sorted,5,0.041132,0.003159,0.001134
HashTable,random,1,0.005539,0.000365,0.000280
HashTable,random,2,0.005341,0.000410,0.000149
HashTable,random,3,0.006400,0.000426,0.000162
HashTable,random,4,0.005309,0.000379,0.000146
HashTable,random,5,0.004919,0.000326,0.000156
HashTable,sorted,1,0.005092,0.000517,0.000192
HashTable,sorted,2,0.006061,0.000372,0.000181
HashTable,sorted,3,0.005716,0.000402,0.000247
HashTable,sorted,4,0.005577,0.000432,0.000153
HashTable,sorted,5,0.005150,0.000332,0.000150
BST,random,1,0.001368,0.000107,0.000090
BST,random,2,0.001303,0.000104,0.000080
BST,random,3,0.001266,0.000102,0.000118
BST,random,4,0.001242,0.000096,0.000076
BST,random,5,0.001383,0.000116,0.000085
BST,sorted,1,0.046195,0.004109,0.004089
BST,sorted,2,0.040337,0.003436,0.004397
BST,sorted,3,0.039702,0.003724,0.003893
BST,sorted,4,0.040421,0.003673,0.003500
BST,sorted,5,0.040682,0.003776,0.004402
1 Structure Mode Repetition Insert (sec) Search (sec) Delete (sec)
2 LinkedList random 1 0.044760 0.002653 0.001263
3 LinkedList random 2 0.040841 0.002611 0.001226
4 LinkedList random 3 0.042830 0.003332 0.001164
5 LinkedList random 4 0.043768 0.003083 0.001108
6 LinkedList random 5 0.040968 0.002525 0.001329
7 LinkedList sorted 1 0.043238 0.002345 0.001168
8 LinkedList sorted 2 0.041196 0.002764 0.001157
9 LinkedList sorted 3 0.039739 0.002995 0.001393
10 LinkedList sorted 4 0.043960 0.002958 0.001642
11 LinkedList sorted 5 0.041132 0.003159 0.001134
12 HashTable random 1 0.005539 0.000365 0.000280
13 HashTable random 2 0.005341 0.000410 0.000149
14 HashTable random 3 0.006400 0.000426 0.000162
15 HashTable random 4 0.005309 0.000379 0.000146
16 HashTable random 5 0.004919 0.000326 0.000156
17 HashTable sorted 1 0.005092 0.000517 0.000192
18 HashTable sorted 2 0.006061 0.000372 0.000181
19 HashTable sorted 3 0.005716 0.000402 0.000247
20 HashTable sorted 4 0.005577 0.000432 0.000153
21 HashTable sorted 5 0.005150 0.000332 0.000150
22 BST random 1 0.001368 0.000107 0.000090
23 BST random 2 0.001303 0.000104 0.000080
24 BST random 3 0.001266 0.000102 0.000118
25 BST random 4 0.001242 0.000096 0.000076
26 BST random 5 0.001383 0.000116 0.000085
27 BST sorted 1 0.046195 0.004109 0.004089
28 BST sorted 2 0.040337 0.003436 0.004397
29 BST sorted 3 0.039702 0.003724 0.003893
30 BST sorted 4 0.040421 0.003673 0.003500
31 BST sorted 5 0.040682 0.003776 0.004402