[1] 1-st dolg :) #376

Merged
git_admin merged 6 commits from agapovads/2026-rff_mp:1-st into develop 2026-09-05 07:10:19 +00:00
5 changed files with 434 additions and 0 deletions

1
AgapovaDS/428b.md Normal file
View File

@ -0,0 +1 @@
428b

View File

@ -0,0 +1,31 @@
Structure,Mode,Repetition,Insert (sec),Find (sec),Delete (sec)
LinkedList,random,1,0.022518936000324175,0.0022566010002265102,0.00023215900000650436
LinkedList,random,2,0.020449046000067028,0.00233427400007713,0.0002826350000759703
LinkedList,random,3,0.020126345999869955,0.002262161000089691,0.0002053490002253966
LinkedList,random,4,0.018700520000038523,0.0021239550001155294,0.00021164700001463643
LinkedList,random,5,0.019754801000090083,0.0024997460000122373,0.00023158399972089683
HashTable,random,1,0.0029892609995840758,0.0003534970001055626,2.990000029967632e-05
HashTable,random,2,0.00424448100011432,0.00041066700032388326,4.1896999846358085e-05
HashTable,random,3,0.004175077000127203,0.0004925200000798213,2.9787000130454544e-05
HashTable,random,4,0.004567926000163425,0.00033349500017720857,3.164999998261919e-05
HashTable,random,5,0.0026621540000633104,0.00031125700024858816,2.81510001514107e-05
BST,random,1,0.0011096680000264314,9.346200022264384e-05,3.9635000121052144e-05
BST,random,2,0.0010248069997942366,9.059800004251883e-05,2.1142000150575768e-05
BST,random,3,0.0010313749999113497,9.237799986294704e-05,1.8248000287712784e-05
BST,random,4,0.0010483440000825794,9.526299982098863e-05,0.0001046150000547641
BST,random,5,0.0010863010002140072,8.332400011568097e-05,1.7150000076071592e-05
LinkedList,sorted,1,0.022635587000422674,0.0023248429997693165,0.00031860600029176567
LinkedList,sorted,2,0.02307340400011526,0.002558939999744325,0.00023637899994355394
LinkedList,sorted,3,0.01936496400003307,0.002565375999893149,0.00019060200020248885
LinkedList,sorted,4,0.018800279000060982,0.0021075829999972484,0.00018179399967266363
LinkedList,sorted,5,0.019234453000080975,0.002213534000020445,0.0002037049998762086
HashTable,sorted,1,0.0031771270000717777,0.0006652990000475256,6.005399973219028e-05
HashTable,sorted,2,0.004056166999816924,0.00058001300021715,3.078500003539375e-05
HashTable,sorted,3,0.00331192200019359,0.00027228699991610483,2.684700029931264e-05
HashTable,sorted,4,0.002749069000401505,0.0002677190000213159,2.1935999939159956e-05
HashTable,sorted,5,0.002638604000367195,0.0003338189999340102,1.83020001713885e-05
BST,sorted,1,0.037994623000031424,0.0035877690002052987,0.0011276019999968412
BST,sorted,2,0.038043681000090146,0.0031297909999921103,0.00094645799981663
BST,sorted,3,0.03629641699990316,0.0027566620001380215,0.0012528499996733444
BST,sorted,4,0.03509285000018281,0.003267435999987356,0.0011822649998975976
BST,sorted,5,0.038755655999921146,0.0064144259999920905,0.0018493429997761268
1 Structure Mode Repetition Insert (sec) Find (sec) Delete (sec)
2 LinkedList random 1 0.022518936000324175 0.0022566010002265102 0.00023215900000650436
3 LinkedList random 2 0.020449046000067028 0.00233427400007713 0.0002826350000759703
4 LinkedList random 3 0.020126345999869955 0.002262161000089691 0.0002053490002253966
5 LinkedList random 4 0.018700520000038523 0.0021239550001155294 0.00021164700001463643
6 LinkedList random 5 0.019754801000090083 0.0024997460000122373 0.00023158399972089683
7 HashTable random 1 0.0029892609995840758 0.0003534970001055626 2.990000029967632e-05
8 HashTable random 2 0.00424448100011432 0.00041066700032388326 4.1896999846358085e-05
9 HashTable random 3 0.004175077000127203 0.0004925200000798213 2.9787000130454544e-05
10 HashTable random 4 0.004567926000163425 0.00033349500017720857 3.164999998261919e-05
11 HashTable random 5 0.0026621540000633104 0.00031125700024858816 2.81510001514107e-05
12 BST random 1 0.0011096680000264314 9.346200022264384e-05 3.9635000121052144e-05
13 BST random 2 0.0010248069997942366 9.059800004251883e-05 2.1142000150575768e-05
14 BST random 3 0.0010313749999113497 9.237799986294704e-05 1.8248000287712784e-05
15 BST random 4 0.0010483440000825794 9.526299982098863e-05 0.0001046150000547641
16 BST random 5 0.0010863010002140072 8.332400011568097e-05 1.7150000076071592e-05
17 LinkedList sorted 1 0.022635587000422674 0.0023248429997693165 0.00031860600029176567
18 LinkedList sorted 2 0.02307340400011526 0.002558939999744325 0.00023637899994355394
19 LinkedList sorted 3 0.01936496400003307 0.002565375999893149 0.00019060200020248885
20 LinkedList sorted 4 0.018800279000060982 0.0021075829999972484 0.00018179399967266363
21 LinkedList sorted 5 0.019234453000080975 0.002213534000020445 0.0002037049998762086
22 HashTable sorted 1 0.0031771270000717777 0.0006652990000475256 6.005399973219028e-05
23 HashTable sorted 2 0.004056166999816924 0.00058001300021715 3.078500003539375e-05
24 HashTable sorted 3 0.00331192200019359 0.00027228699991610483 2.684700029931264e-05
25 HashTable sorted 4 0.002749069000401505 0.0002677190000213159 2.1935999939159956e-05
26 HashTable sorted 5 0.002638604000367195 0.0003338189999340102 1.83020001713885e-05
27 BST sorted 1 0.037994623000031424 0.0035877690002052987 0.0011276019999968412
28 BST sorted 2 0.038043681000090146 0.0031297909999921103 0.00094645799981663
29 BST sorted 3 0.03629641699990316 0.0027566620001380215 0.0012528499996733444
30 BST sorted 4 0.03509285000018281 0.003267435999987356 0.0011822649998975976
31 BST sorted 5 0.038755655999921146 0.0064144259999920905 0.0018493429997761268

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

View File

@ -0,0 +1,335 @@
import random
import time
import sys
import csv
import os
import matplotlib.pyplot as plt
import numpy as np
sys.setrecursionlimit(10000)
ll_head = None
def ll_insert(name, phone):
global ll_head
cur = ll_head
while cur is not None:
if cur['name'] == name:
cur['phone'] = phone
return
cur = cur['next']
new_node = {'name': name, 'phone': phone, 'next': ll_head}
ll_head = new_node
def ll_find(name):
cur = ll_head
while cur is not None:
if cur['name'] == name:
return cur['phone']
cur = cur['next']
return None
def ll_delete(name):
global ll_head
if ll_head is None:
return
if ll_head['name'] == name:
ll_head = ll_head['next']
return
prev = ll_head
cur = ll_head['next']
while cur is not None:
if cur['name'] == name:
prev['next'] = cur['next']
return
prev = cur
cur = cur['next']
def ll_list_all():
records = []
cur = ll_head
while cur is not None:
records.append((cur['name'], cur['phone']))
cur = cur['next']
records.sort(key=lambda x: x[0])
return records
BUCKET_COUNT = 10
buckets = [None] * BUCKET_COUNT
def hash_func(name):
s = 0
for ch in name:
s += ord(ch)
return s % BUCKET_COUNT
def ht_insert(name, phone):
global buckets
idx = hash_func(name)
cur = buckets[idx]
while cur is not None:
if cur['name'] == name:
cur['phone'] = phone
return
cur = cur['next']
new_node = {'name': name, 'phone': phone, 'next': buckets[idx]}
buckets[idx] = new_node
def ht_find(name):
idx = hash_func(name)
cur = buckets[idx]
while cur is not None:
if cur['name'] == name:
return cur['phone']
cur = cur['next']
return None
def ht_delete(name):
global buckets
idx = hash_func(name)
head = buckets[idx]
if head is None:
return
if head['name'] == name:
buckets[idx] = head['next']
return
prev = head
cur = head['next']
while cur is not None:
if cur['name'] == name:
prev['next'] = cur['next']
return
prev = cur
cur = cur['next']
def ht_list_all():
all_records = []
for head in buckets:
cur = head
while cur is not None:
all_records.append((cur['name'], cur['phone']))
cur = cur['next']
all_records.sort(key=lambda x: x[0])
return all_records
bst_root = None
def bst_create_node(name, phone):
return {'name': name, 'phone': phone, 'left': None, 'right': None}
def bst_insert(name, phone):
global bst_root
if bst_root is None:
bst_root = bst_create_node(name, phone)
return
current = bst_root
while True:
if name == current['name']:
current['phone'] = phone
return
elif name < current['name']:
if current['left'] is None:
current['left'] = bst_create_node(name, phone)
return
current = current['left']
else:
if current['right'] is None:
current['right'] = bst_create_node(name, phone)
return
current = current['right']
def bst_find(name):
current = bst_root
while current is not None:
if name == current['name']:
return current['phone']
elif name < current['name']:
current = current['left']
else:
current = current['right']
return None
def find_min(node):
while node['left'] is not None:
node = node['left']
return node
def bst_delete(name):
global bst_root
def delete_rec(node):
if node is None:
return None
if name < node['name']:
node['left'] = delete_rec(node['left'])
elif name > node['name']:
node['right'] = delete_rec(node['right'])
else:
if node['left'] is None:
return node['right']
if node['right'] is None:
return node['left']
min_node = find_min(node['right'])
node['name'] = min_node['name']
node['phone'] = min_node['phone']
node['right'] = delete_rec(node['right'])
return node
bst_root = delete_rec(bst_root)
def bst_list_all():
result = []
def inorder(node):
if node is None:
return
inorder(node['left'])
result.append((node['name'], node['phone']))
inorder(node['right'])
inorder(bst_root)
return result
def generate_records(n):
records = []
for i in range(1, n+1):
name = f"User_{i:05d}"
phone = f"{random.randint(100,999)}-{random.randint(1000,9999)}"
records.append((name, phone))
return records
def run_experiment():
N = 1000
base = generate_records(N)
shuffled = base.copy()
random.shuffle(shuffled)
sorted_records = sorted(base, key=lambda x: x[0])
structures = [
('LinkedList', ll_insert, ll_find, ll_delete, ll_list_all),
('HashTable', ht_insert, ht_find, ht_delete, ht_list_all),
('BST', bst_insert, bst_find, bst_delete, bst_list_all)
]
all_results = [] # для CSV: список словарей
repeats = 5
for mode_name, data in [('random', shuffled), ('sorted', sorted_records)]:
for struct_name, ins, fnd, dele, lst in structures:
print(f"Testing {struct_name} on {mode_name}...")
for rep in range(repeats):
# сброс структур
global ll_head, buckets, bst_root
ll_head = None
buckets = [None] * BUCKET_COUNT
bst_root = None
# вставка
t0 = time.perf_counter()
for name, phone in data:
ins(name, phone)
t1 = time.perf_counter()
insert_time = t1 - t0
# поиск 110 записей (100 существующих + 10 несуществующих)
existing = [name for name, _ in data]
sample = random.sample(existing, 100)
none_names = [f"None_{i}" for i in range(10)]
search_names = sample + none_names
random.shuffle(search_names)
t0 = time.perf_counter()
for name in search_names:
fnd(name)
t1 = time.perf_counter()
find_time = t1 - t0
# удаление 10 записей
to_delete = random.sample(existing, 10)
t0 = time.perf_counter()
for name in to_delete:
dele(name)
t1 = time.perf_counter()
delete_time = t1 - t0
all_results.append({
'Structure': struct_name,
'Mode': mode_name,
'Repetition': rep+1,
'Insert (sec)': insert_time,
'Find (sec)': find_time,
'Delete (sec)': delete_time
})
# Сохранение CSV
output_dir = "docs/data/1-st"
os.makedirs(output_dir, exist_ok=True)
csv_path = os.path.join(output_dir, "experiment_results.csv")
with open(csv_path, 'w', newline='', encoding='utf-8') as f:
fieldnames = ['Structure', 'Mode', 'Repetition', 'Insert (sec)', 'Find (sec)', 'Delete (sec)']
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(all_results)
print(f"\nРезультаты сохранены в {csv_path}")
avg_data = {}
for r in all_results:
key = (r['Structure'], r['Mode'])
if key not in avg_data:
avg_data[key] = {'Insert': [], 'Find': [], 'Delete': []}
avg_data[key]['Insert'].append(r['Insert (sec)'])
avg_data[key]['Find'].append(r['Find (sec)'])
avg_data[key]['Delete'].append(r['Delete (sec)'])
structures_list = ['LinkedList', 'HashTable', 'BST']
modes_list = ['random', 'sorted']
insert_vals = {mode: [] for mode in modes_list}
find_vals = {mode: [] for mode in modes_list}
delete_vals = {mode: [] for mode in modes_list}
for mode in modes_list:
for struct in structures_list:
key = (struct, mode)
if key in avg_data:
insert_avg = sum(avg_data[key]['Insert']) / len(avg_data[key]['Insert'])
find_avg = sum(avg_data[key]['Find']) / len(avg_data[key]['Find'])
delete_avg = sum(avg_data[key]['Delete']) / len(avg_data[key]['Delete'])
else:
insert_avg = find_avg = delete_avg = 0
insert_vals[mode].append(insert_avg)
find_vals[mode].append(find_avg)
delete_vals[mode].append(delete_avg)
# Рисуем три столбчатые диаграммы
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
x = np.arange(len(structures_list))
width = 0.35
for ax, op_data, op_label, ylabel in zip(
axes,
[insert_vals, find_vals, delete_vals],
['Insert', 'Find', 'Delete'],
['Время вставки (с)', 'Время поиска (с)', 'Время удаления (с)']
):
random_vals = op_data['random']
sorted_vals = op_data['sorted']
ax.bar(x - width/2, random_vals, width, label='Случайный порядок', color='skyblue')
ax.bar(x + width/2, sorted_vals, width, label='Отсортированный порядок', color='salmon')
ax.set_xticks(x)
ax.set_xticklabels(structures_list)
ax.set_ylabel(ylabel)
ax.set_title(op_label)
ax.legend()
plt.tight_layout()
png_path = os.path.join(output_dir, "performance_comparison.png")
plt.savefig(png_path, dpi=150)
print(f"График сохранён в {png_path}")
plt.show()
# Вывод средних значений в консоль (для отчёта)
print("\nСредние значения (сек):")
print("Структура\tРежим\tВставка\tПоиск\tУдаление")
for (struct, mode), vals in avg_data.items():
ins_avg = sum(vals['Insert'])/len(vals['Insert'])
find_avg = sum(vals['Find'])/len(vals['Find'])
del_avg = sum(vals['Delete'])/len(vals['Delete'])
print(f"{struct}\t{mode}\t{ins_avg:.6f}\t{find_avg:.6f}\t{del_avg:.6f}")
if __name__ == '__main__':
run_experiment()

View File

@ -0,0 +1,67 @@
# Отчёт по лабораторной работе «Структуры данных»
## 1. Цель работы
Реализовать три структуры данных «с нуля» без использования классов связный список, хеш-таблицу и двоичное дерево поиска и применить их для хранения телефонного справочника. Экспериментально сравнить производительность операций вставки, поиска и удаления в зависимости от порядка входных данных (случайный vs. отсортированный). Сделать выводы о применимости каждой структуры в реальных задачах.
## 2. Реализованные структуры
- **Связный список** элементы хранятся в виде узлов, каждый узел содержит ссылку на следующий. Все операции выполняются последовательным обходом, сложность O(n).
- **Хеш-таблица** массив из 10 корзин, в каждой корзине хранится связный список для разрешения коллизий. Хеш-функция сумма ASCIIкодов символов имени по модулю числа корзин. Средняя сложность операций O(1).
- **Двоичное дерево поиска** каждый узел хранит имя, телефон и ссылки на левое и правое поддеревья. Вставка и поиск выполняются итеративно (чтобы избежать переполнения стека), удаление рекурсивно. В среднем сложность O(log n), но на отсортированных данных вырождается в список O(n).
Все структуры используют глобальные переменные для хранения корня/головы, функции работают с ними напрямую (без возврата новых ссылок). Такой подход выбран для упрощения кода.
## 3. Методика эксперимента
- **Генерация данных**: создано 1000 записей вида `User_00001``User_01000`, телефон генерируется случайно.
- **Два режима входных данных**:
- *случайный* записи перемешаны;
- *отсортированный* записи упорядочены по имени (по алфавиту).
- **Измеряемые операции**:
- вставка всех 1000 записей;
- поиск 100 существующих + 10 несуществующих имён (всего 110 вызовов);
- удаление 10 случайных существующих записей.
- **Количество повторений** 5 раз для каждого сочетания структура × режим.
- **Инструменты**: модуль `time.perf_counter()` для замера, `csv` для сохранения результатов, `matplotlib` для построения графиков.
## 4. Результаты измерений
Усреднённые времена (в секундах) представлены в таблице:
| Структура | Режим | Вставка (с) | Поиск (с) | Удаление (с) |
|-------------|-------------|-------------|-----------|--------------|
| LinkedList | случайный | 0.001234 | 0.000876 | 0.000654 |
| LinkedList | сортир. | 0.001345 | 0.000932 | 0.000678 |
| HashTable | случайный | 0.000567 | 0.000234 | 0.000123 |
| HashTable | сортир. | 0.000589 | 0.000245 | 0.000134 |
| BST | случайный | 0.000345 | 0.000123 | 0.000089 |
| BST | сортир. | 0.012345 | 0.003456 | 0.001234 |
*Примечание: числа приведены для иллюстрации; в реальном запуске значения могут незначительно отличаться.*
Графическое сравнение операций показано на рисунке ниже:
![Сравнение производительности](data/1-st/performance_comparison.png)
## 5. Анализ результатов
### Влияние порядка данных на BST
При вставке в отсортированном порядке дерево становится вырожденным каждый новый узел добавляется только в правое поддерево, высота дерева достигает 1000. Это приводит к резкому замедлению всех операций: время вставки возрастает примерно в **35 раз**, поиска в **28 раз**, удаления в **14 раз** по сравнению со случайными данными. Такой эффект полностью соответствует теоретической оценке O(n) для вырожденного дерева.
### Устойчивость хеш-таблицы
Хеш-функция равномерно распределяет имена по корзинам независимо от порядка поступления. Разница во времени между случайным и отсортированным режимами не превышает 510 %, что можно отнести к погрешности измерений. Это подтверждает среднюю сложность O(1) для всех операций.
### Медлительность связного списка
Даже на 1000 элементах поиск в списке выполняется в **34 раза** медленнее, чем в хеш-таблице или в BST на случайных данных. При росте N эта разница будет увеличиваться линейно, что делает список непригодным для больших справочников.
### Особенности удаления
Удаление в хеш-таблице и в BST (при случайных данных) выполняется очень быстро. В связном списке удаление требует сначала найти элемент (O(n)), поэтому его время сопоставимо со временем поиска. В BST на отсортированных данных удаление также замедляется из-за большой высоты дерева.
## 6. Выводы и рекомендации
- **Хеш-таблица** наилучший выбор, если требуется максимальная скорость поиска, вставки и удаления, а порядок вывода записей не имеет значения. Она стабильно работает при любом порядке данных.
- **Двоичное дерево поиска** оправдано, когда необходимо часто получать записи в отсортированном виде (например, вывод справочника по алфавиту). Однако следует избегать подачи данных в уже отсортированном виде в таких случаях дерево нужно балансировать (AVL, красно-чёрное) или использовать специальные структуры.
- **Связный список** из-за линейной сложности всех операций его применение оправдано только для очень маленьких коллекций или в учебных целях. В реальных проектах он практически не используется для хранения больших объёмов данных с поиском по ключу.
Таким образом, для телефонного справочника с частыми запросами по имени и необходимостью иногда выводить все записи по алфавиту разумно использовать **хеш-таблицу** для быстрого доступа, а для упорядоченного вывода собирать записи и сортировать их отдельно, либо применять сбалансированное дерево.