Merge pull request '[1]' (#294) from novikovsd/2026-rff_mp:lab1 into develop

Reviewed-on: UNN/2026-rff_mp#294
This commit is contained in:
VladimirGub 2026-05-30 12:00:48 +00:00
commit 34eade7e8d
4 changed files with 422 additions and 0 deletions

25
novikovsd/answers.txt Normal file
View File

@ -0,0 +1,25 @@
В двоичном дереве поиска (BST) порядок добавления элементов определяет форму дерева. Если данные поступают в случайном порядке, дерево получается примерно сбалансированным: высота ~ O(log N), и вставка выполняется за O(log N) в среднем.
Если же данные отсортированы (по возрастанию или убыванию), каждый новый элемент становится либо самым правым, либо самым левым потомком. Дерево вырождается в линейный связный список (так называемое "вырожденное дерево"). В этом случае вставка каждого нового элемента требует прохода по всем уже вставленным узлам, то есть O(N) на операцию. Суммарная вставка N элементов O(N²). В эксперименте для отсортированного режима BST будет работать значительно медленнее, чем для случайного.
В моей реализации bst_insert итеративная, но алгоритм сохраняет эту зависимость: при отсортированных именах (User_00000, User_00001, …) каждый новый ключ больше всех предыдущих, поэтому поиск места вставки каждый раз обходит всю текущую цепочку правых потомков, что приводит к квадратичной сложности.
Хеш‑таблица вычисляет индекс (хеш) от имени и сразу помещает запись в соответствующую корзину. Порядок поступления данных никак не влияет на значение хеша и распределение по корзинам. Даже если имена идут подряд (отсортированы), хеш‑функция (например, (h*31 + ord(ch)) % size) рассеивает их по разным индексам почти равномерно. Поэтому время вставки, поиска и удаления остаётся O(1) в среднем (с учётом разрешения коллизий цепочками), независимо от того, отсортированы данные или перемешаны.
В связном списке поиск элемента по имени требует последовательного просмотра узлов от головы до тех пор, пока не найдётся нужный или не достигнут конец. В худшем случае (элемент отсутствует или находится в конце) нужно проверить все N узлов. Сложность поиска O(N) в среднем. Никакая предобработка или порядок вставки не улучшают этот показатель, потому что структура не поддерживает эффективного индексирования. Даже если список отсортировать вручную (но у нас нет сортировки при вставке), поиск останется линейным, так как нельзя выполнить бинарный поиск без возможности прямого доступа по индексу.
· Связный список: удаление узла по имени требует линейного поиска (O(N)). Найденный узел исключается перенаправлением указателя предыдущего узла на следующий. Для удаления головы особая обработка. В моей реализации ll_delete возвращает новую голову. Время O(N).
· Хеш‑таблица: удаление сводится к вычислению индекса корзины (O(1)) и вызову ll_delete для связного списка этой корзины. Средняя длина цепочки N / bucket_count (обычно небольшая константа). Поэтому удаление в среднем O(1). В коде: buckets[idx] = ll_delete(buckets[idx], name).
· BST: удаление сложнее. Сначала ищется узел (O(log N) в сбалансированном дереве, O(N) в вырожденном). Если узел найден, то:
· Нет потомков просто удаляем.
· Один потомок заменяем удаляемый узел на потомка.
· Два потомка находим минимальный узел в правом поддереве (или максимальный в левом), копируем его данные в удаляемый узел, затем рекурсивно удаляем этот минимальный узел.
Моя реализация bst_delete рекурсивна, сложность совпадает со сложностью поиска. Для сбалансированного дерева O(log N), для вырожденного O(N).
· Связный список использовать, только если нужны частые вставки/удаления в начало/конец (например, очередь или стек) и поиск почти не требуется. Для телефонного справочника он не пригоден из-за линейного поиска.
· Хеш‑таблица идеальный выбор для задач, где важны быстрые вставка, поиск и удаление по ключу (O(1) в среднем) и не требуется получать записи в отсортированном порядке. Примеры: словари, кэши, таблицы символов, базы данных «ключ‑значение». Телефонный справочник с частыми поисками по имени отличное
· BST (особенно самобалансирующиеся варианты, такие как AVL или красно‑чёрное дерево) выбирают, когда нужны обе возможности: быстрый поиск (O(log N)) и возможность прохода по данным в отсортированном порядке без дополнительной сортировки. Также дерево может поддерживать операции поиска диапазона («все имена между A и B»). Но для простого справочника без требования сортировки на лету хеш‑таблица обычно предпочтительнее из-за константного времени. В данной реализации простой BST деградирует на упорядоченных данных, поэтому в реальной жизни используют сбалансированные деревья.
Вывод: Если нужен только доступ по ключу хеш‑таблица. Если нужен отсортированный вывод или диапазонные запросы сбалансированное дерево. Связный список для этой задачи неприменим.

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -0,0 +1,91 @@
Structure,Mode,Operation,Repeat,Time_sec
LinkedList,shuffled,insert,1,2.7093895999996676
LinkedList,shuffled,find,1,0.03374979999989591
LinkedList,shuffled,delete,1,0.013558000000102766
LinkedList,shuffled,insert,2,2.7178337999998803
LinkedList,shuffled,find,2,0.034134699999867735
LinkedList,shuffled,delete,2,0.014517000000068947
LinkedList,shuffled,insert,3,2.714019300000018
LinkedList,shuffled,find,3,0.033463599999777216
LinkedList,shuffled,delete,3,0.013073999999960506
LinkedList,shuffled,insert,4,2.7287836000000425
LinkedList,shuffled,find,4,0.03680309999981546
LinkedList,shuffled,delete,4,0.015249600000061037
LinkedList,shuffled,insert,5,2.722151500000109
LinkedList,shuffled,find,5,0.03397850000010294
LinkedList,shuffled,delete,5,0.015159399999902234
LinkedList,sorted,insert,1,2.5469852999999603
LinkedList,sorted,find,1,0.054332700000031764
LinkedList,sorted,delete,1,0.013600199999928009
LinkedList,sorted,insert,2,2.5274411999998847
LinkedList,sorted,find,2,0.05538109999997687
LinkedList,sorted,delete,2,0.014902900000379304
LinkedList,sorted,insert,3,2.516689800000222
LinkedList,sorted,find,3,0.05497689999992872
LinkedList,sorted,delete,3,0.012883400000191614
LinkedList,sorted,insert,4,2.528048000000126
LinkedList,sorted,find,4,0.05493479999995543
LinkedList,sorted,delete,4,0.012835600000016711
LinkedList,sorted,insert,5,2.524865200000022
LinkedList,sorted,find,5,0.05850929999996879
LinkedList,sorted,delete,5,0.015247499999986758
HashTable,shuffled,insert,1,0.014068699999825185
HashTable,shuffled,find,1,0.00015149999990171636
HashTable,shuffled,delete,1,7.469999991371878e-05
HashTable,shuffled,insert,2,0.014089899999817135
HashTable,shuffled,find,2,0.00014630000032411772
HashTable,shuffled,delete,2,7.090000008247443e-05
HashTable,shuffled,insert,3,0.013962699999865436
HashTable,shuffled,find,3,0.00014389999978448031
HashTable,shuffled,delete,3,6.919999987076153e-05
HashTable,shuffled,insert,4,0.01387350000004517
HashTable,shuffled,find,4,0.00014590000000680448
HashTable,shuffled,delete,4,7.129999994504033e-05
HashTable,shuffled,insert,5,0.014038799999980256
HashTable,shuffled,find,5,0.00014629999986937037
HashTable,shuffled,delete,5,7.400000004054164e-05
HashTable,sorted,insert,1,0.01933809999991354
HashTable,sorted,find,1,0.0001700000002529123
HashTable,sorted,delete,1,8.489999981975416e-05
HashTable,sorted,insert,2,0.014241200000014942
HashTable,sorted,find,2,0.00016050000022005406
HashTable,sorted,delete,2,7.110000024113106e-05
HashTable,sorted,insert,3,0.013520700000299257
HashTable,sorted,find,3,0.0001594999998815183
HashTable,sorted,delete,3,6.890000031489762e-05
HashTable,sorted,insert,4,0.014047699999991892
HashTable,sorted,find,4,0.00015880000000834116
HashTable,sorted,delete,4,6.900000016685226e-05
HashTable,sorted,insert,5,0.013919299999997747
HashTable,sorted,find,5,0.0001606000000720087
HashTable,sorted,delete,5,7.239999968078337e-05
BST,shuffled,insert,1,0.021964499999739928
BST,shuffled,find,1,0.00016349999987141928
BST,shuffled,delete,1,0.00017139999999926658
BST,shuffled,insert,2,0.022091499999987718
BST,shuffled,find,2,0.00016019999975469545
BST,shuffled,delete,2,0.00015999999959603883
BST,shuffled,insert,3,0.02204540000002453
BST,shuffled,find,3,0.00016659999982948648
BST,shuffled,delete,3,0.00015170000006037299
BST,shuffled,insert,4,0.022226300000056654
BST,shuffled,find,4,0.00016219999997701962
BST,shuffled,delete,4,0.0001567000003888097
BST,shuffled,insert,5,0.021780500000204484
BST,shuffled,find,5,0.00015780000012455275
BST,shuffled,delete,5,0.0001606000000720087
BST,sorted,insert,1,6.614551799999845
BST,sorted,find,1,0.0005606999998235551
BST,sorted,delete,1,0.0634210999996867
BST,sorted,insert,2,6.625495499999943
BST,sorted,find,2,0.0005660000001626031
BST,sorted,delete,2,0.06643010000016147
BST,sorted,insert,3,6.6205589999999575
BST,sorted,find,3,0.0005686999998033571
BST,sorted,delete,3,0.06744570000000749
BST,sorted,insert,4,6.639703100000133
BST,sorted,find,4,0.0005636999999296677
BST,sorted,delete,4,0.0661270999999033
BST,sorted,insert,5,6.6624039000002995
BST,sorted,find,5,0.0005601999996542872
BST,sorted,delete,5,0.057285699999738426
1 Structure Mode Operation Repeat Time_sec
2 LinkedList shuffled insert 1 2.7093895999996676
3 LinkedList shuffled find 1 0.03374979999989591
4 LinkedList shuffled delete 1 0.013558000000102766
5 LinkedList shuffled insert 2 2.7178337999998803
6 LinkedList shuffled find 2 0.034134699999867735
7 LinkedList shuffled delete 2 0.014517000000068947
8 LinkedList shuffled insert 3 2.714019300000018
9 LinkedList shuffled find 3 0.033463599999777216
10 LinkedList shuffled delete 3 0.013073999999960506
11 LinkedList shuffled insert 4 2.7287836000000425
12 LinkedList shuffled find 4 0.03680309999981546
13 LinkedList shuffled delete 4 0.015249600000061037
14 LinkedList shuffled insert 5 2.722151500000109
15 LinkedList shuffled find 5 0.03397850000010294
16 LinkedList shuffled delete 5 0.015159399999902234
17 LinkedList sorted insert 1 2.5469852999999603
18 LinkedList sorted find 1 0.054332700000031764
19 LinkedList sorted delete 1 0.013600199999928009
20 LinkedList sorted insert 2 2.5274411999998847
21 LinkedList sorted find 2 0.05538109999997687
22 LinkedList sorted delete 2 0.014902900000379304
23 LinkedList sorted insert 3 2.516689800000222
24 LinkedList sorted find 3 0.05497689999992872
25 LinkedList sorted delete 3 0.012883400000191614
26 LinkedList sorted insert 4 2.528048000000126
27 LinkedList sorted find 4 0.05493479999995543
28 LinkedList sorted delete 4 0.012835600000016711
29 LinkedList sorted insert 5 2.524865200000022
30 LinkedList sorted find 5 0.05850929999996879
31 LinkedList sorted delete 5 0.015247499999986758
32 HashTable shuffled insert 1 0.014068699999825185
33 HashTable shuffled find 1 0.00015149999990171636
34 HashTable shuffled delete 1 7.469999991371878e-05
35 HashTable shuffled insert 2 0.014089899999817135
36 HashTable shuffled find 2 0.00014630000032411772
37 HashTable shuffled delete 2 7.090000008247443e-05
38 HashTable shuffled insert 3 0.013962699999865436
39 HashTable shuffled find 3 0.00014389999978448031
40 HashTable shuffled delete 3 6.919999987076153e-05
41 HashTable shuffled insert 4 0.01387350000004517
42 HashTable shuffled find 4 0.00014590000000680448
43 HashTable shuffled delete 4 7.129999994504033e-05
44 HashTable shuffled insert 5 0.014038799999980256
45 HashTable shuffled find 5 0.00014629999986937037
46 HashTable shuffled delete 5 7.400000004054164e-05
47 HashTable sorted insert 1 0.01933809999991354
48 HashTable sorted find 1 0.0001700000002529123
49 HashTable sorted delete 1 8.489999981975416e-05
50 HashTable sorted insert 2 0.014241200000014942
51 HashTable sorted find 2 0.00016050000022005406
52 HashTable sorted delete 2 7.110000024113106e-05
53 HashTable sorted insert 3 0.013520700000299257
54 HashTable sorted find 3 0.0001594999998815183
55 HashTable sorted delete 3 6.890000031489762e-05
56 HashTable sorted insert 4 0.014047699999991892
57 HashTable sorted find 4 0.00015880000000834116
58 HashTable sorted delete 4 6.900000016685226e-05
59 HashTable sorted insert 5 0.013919299999997747
60 HashTable sorted find 5 0.0001606000000720087
61 HashTable sorted delete 5 7.239999968078337e-05
62 BST shuffled insert 1 0.021964499999739928
63 BST shuffled find 1 0.00016349999987141928
64 BST shuffled delete 1 0.00017139999999926658
65 BST shuffled insert 2 0.022091499999987718
66 BST shuffled find 2 0.00016019999975469545
67 BST shuffled delete 2 0.00015999999959603883
68 BST shuffled insert 3 0.02204540000002453
69 BST shuffled find 3 0.00016659999982948648
70 BST shuffled delete 3 0.00015170000006037299
71 BST shuffled insert 4 0.022226300000056654
72 BST shuffled find 4 0.00016219999997701962
73 BST shuffled delete 4 0.0001567000003888097
74 BST shuffled insert 5 0.021780500000204484
75 BST shuffled find 5 0.00015780000012455275
76 BST shuffled delete 5 0.0001606000000720087
77 BST sorted insert 1 6.614551799999845
78 BST sorted find 1 0.0005606999998235551
79 BST sorted delete 1 0.0634210999996867
80 BST sorted insert 2 6.625495499999943
81 BST sorted find 2 0.0005660000001626031
82 BST sorted delete 2 0.06643010000016147
83 BST sorted insert 3 6.6205589999999575
84 BST sorted find 3 0.0005686999998033571
85 BST sorted delete 3 0.06744570000000749
86 BST sorted insert 4 6.639703100000133
87 BST sorted find 4 0.0005636999999296677
88 BST sorted delete 4 0.0661270999999033
89 BST sorted insert 5 6.6624039000002995
90 BST sorted find 5 0.0005601999996542872
91 BST sorted delete 5 0.057285699999738426

306
novikovsd/hashtab.py Normal file
View File

@ -0,0 +1,306 @@
import time
import random
import csv
import os
import sys
sys.setrecursionlimit(30000)
def ll_insert(head, name, phone):
curr = head
while curr is not None:
if curr['name'] == name:
curr['phone'] = phone
return head
curr = curr['next']
new_node = {'name': name, 'phone': phone, 'next': head}
return new_node
def ll_find(head, name):
curr = head
while curr is not None:
if curr['name'] == name:
return curr['phone']
curr = curr['next']
return None
def ll_delete(head, name):
if head is None:
return None
if head['name'] == name:
return head['next']
prev = head
curr = head['next']
while curr is not None:
if curr['name'] == name:
prev['next'] = curr['next']
return head
prev = curr
curr = curr['next']
return head
def ll_list_all(head):
entries = []
curr = head
while curr is not None:
entries.append((curr['name'], curr['phone']))
curr = curr['next']
entries.sort(key=lambda x: x[0])
return entries
def _hash(name, bucket_count):
h = 0
for ch in name:
h = (h * 31 + ord(ch)) % bucket_count
return h
def ht_create(bucket_count=2000):
return [None] * bucket_count
def ht_insert(buckets, name, phone):
idx = _hash(name, len(buckets))
buckets[idx] = ll_insert(buckets[idx], name, phone)
def ht_find(buckets, name):
idx = _hash(name, len(buckets))
return ll_find(buckets[idx], name)
def ht_delete(buckets, name):
idx = _hash(name, len(buckets))
buckets[idx] = ll_delete(buckets[idx], name)
def ht_list_all(buckets):
entries = []
for head in buckets:
curr = head
while curr is not None:
entries.append((curr['name'], curr['phone']))
curr = curr['next']
entries.sort(key=lambda x: x[0])
return entries
def bst_insert(root, name, phone):
new_node = {'name': name, 'phone': phone, 'left': None, 'right': None}
if root is None:
return new_node
parent = None
curr = root
while curr is not None:
parent = curr
if name < curr['name']:
curr = curr['left']
elif name > curr['name']:
curr = curr['right']
else:
curr['phone'] = phone
return root
if name < parent['name']:
parent['left'] = new_node
else:
parent['right'] = new_node
return root
def bst_find(root, name):
while root is not None:
if name == root['name']:
return root['phone']
elif name < root['name']:
root = root['left']
else:
root = root['right']
return None
def _bst_min_node(node):
while node and node['left'] is not None:
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']
min_node = _bst_min_node(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):
def inorder(node, res):
if node is None:
return
inorder(node['left'], res)
res.append((node['name'], node['phone']))
inorder(node['right'], res)
result = []
inorder(root, result)
return result
def generate_test_data(n=10000):
records = [(f"User_{i:05d}", f"+7-999-{i:05d}") for i in range(n)]
records_sorted = records[:]
records_shuffled = records[:]
random.shuffle(records_shuffled)
return records_sorted, records_shuffled
def measure_insert(struct_name, records):
start = time.perf_counter()
if struct_name == "LinkedList":
head = None
for name, phone in records:
head = ll_insert(head, name, phone)
obj = head
elif struct_name == "HashTable":
buckets = ht_create(bucket_count=2000)
for name, phone in records:
ht_insert(buckets, name, phone)
obj = buckets
elif struct_name == "BST":
root = None
for name, phone in records:
root = bst_insert(root, name, phone)
obj = root
else:
raise ValueError(f"Unknown structure: {struct_name}")
elapsed = time.perf_counter() - start
return elapsed, obj
def measure_find(obj, struct_name, existing_names, nonexisting_names):
start = time.perf_counter()
for name in existing_names:
if struct_name == "LinkedList":
ll_find(obj, name)
elif struct_name == "HashTable":
ht_find(obj, name)
else:
bst_find(obj, name)
for name in nonexisting_names:
if struct_name == "LinkedList":
ll_find(obj, name)
elif struct_name == "HashTable":
ht_find(obj, name)
else:
bst_find(obj, name)
return time.perf_counter() - start
def measure_delete(obj, struct_name, names_to_delete):
start = time.perf_counter()
if struct_name == "LinkedList":
for name in names_to_delete:
obj = ll_delete(obj, name)
elif struct_name == "HashTable":
for name in names_to_delete:
ht_delete(obj, name)
else:
for name in names_to_delete:
obj = bst_delete(obj, name)
elapsed = time.perf_counter() - start
return elapsed, obj
def run_experiment(n=10000, repeats=5):
records_sorted, records_shuffled = generate_test_data(n)
existing_names = [name for name, _ in records_sorted[:100]]
nonexisting_names = [f"None_{i}" for i in range(10)]
all_names = [name for name, _ in records_sorted]
structures = ["LinkedList", "HashTable", "BST"]
modes = [("shuffled", records_shuffled), ("sorted", records_sorted)]
results = []
for struct_name in structures:
for mode_name, records in modes:
for rep in range(repeats):
insert_time, obj = measure_insert(struct_name, records)
results.append([struct_name, mode_name, "insert", rep+1, insert_time])
find_time = measure_find(obj, struct_name, existing_names, nonexisting_names)
results.append([struct_name, mode_name, "find", rep+1, find_time])
random.seed(rep)
to_delete = random.sample(all_names, 50)
delete_time, obj = measure_delete(obj, struct_name, to_delete)
results.append([struct_name, mode_name, "delete", rep+1, delete_time])
return results
def save_results_to_csv(results, filename="docs/data/results.csv"):
os.makedirs(os.path.dirname(filename), exist_ok=True)
with open(filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow(["Structure", "Mode", "Operation", "Repeat", "Time_sec"])
writer.writerows(results)
print(f"Результаты сохранены в {filename}")
def aggregate_results(results):
from collections import defaultdict
agg = defaultdict(list)
for row in results:
struct, mode, op, rep, t = row
agg[(struct, mode, op)].append(t)
means = {k: sum(v)/len(v) for k, v in agg.items()}
return means
def plot_results(means, output_dir="docs"):
try:
import matplotlib.pyplot as plt
import numpy as np
except ImportError:
print("Matplotlib не установлен. Графики не построены.")
return
operations = ["insert", "find", "delete"]
structures = ["LinkedList", "HashTable", "BST"]
modes = ["shuffled", "sorted"]
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
for idx, op in enumerate(operations):
ax = axes[idx]
x = np.arange(len(structures))
width = 0.35
shuffled_means = [means.get((struct, "shuffled", op), 0) for struct in structures]
sorted_means = [means.get((struct, "sorted", op), 0) for struct in structures]
ax.bar(x - width/2, shuffled_means, width, label='случайный порядок', color='skyblue')
ax.bar(x + width/2, sorted_means, width, label='отсортированный порядок', color='salmon')
ax.set_xticks(x)
ax.set_xticklabels(structures, rotation=15)
ax.set_ylabel('Время (сек)')
ax.set_title(f'{op.upper()}')
ax.legend()
ax.grid(axis='y', linestyle='--', alpha=0.7)
plt.tight_layout()
plt.savefig(os.path.join(output_dir, "comparison.png"), dpi=150)
plt.show()
if __name__ == "__main__":
results = run_experiment(n=10000, repeats=5)
save_results_to_csv(results)
means = aggregate_results(results)
print("\nСреднее время по операциям (сек):")
for (struct, mode, op), t in sorted(means.items()):
print(f"{struct:12} {mode:8} {op:6} : {t:.6f}")
plot_results(means)