diff --git a/svetlakovkyu/docs/data/01/codes/BST.py b/svetlakovkyu/docs/data/01/codes/BST.py new file mode 100644 index 0000000..b1fb9ae --- /dev/null +++ b/svetlakovkyu/docs/data/01/codes/BST.py @@ -0,0 +1,63 @@ +def bst_insert(root, name, phone): + if root is None: + return {'name': name, 'phone': phone, 'left': None, 'right': None} + 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 root['phone'] + + if name < root['name']: + return bst_find(root['left'], name) + else: + return bst_find(root['right'], name) + +def get_min(node): + current = node + while 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'] + if root['right'] is None: + return root['left'] + successor = get_min(root['right']) + root['name'] = successor['name'] + root['phone'] = successor['phone'] + root['right'] = bst_delete(root['right'], successor['name']) + return root + +def bst_list_all(root, res = None): + if res is None: + res = [] + if root is not None: + bst_list_all(root['left'], res) + res.append({'name': root['name'], 'phone': root['phone']}) + bst_list_all(root['right'], res) + #сортировка уже сделана + return res + +#проверка +# root = None +# root = bst_insert(root, "Ivan", "111") +# root = bst_insert(root, "Anna", "222") # Уйдет влево +# root = bst_insert(root, "Zina", "333") # Уйдет вправо +# print(root) \ No newline at end of file diff --git a/svetlakovkyu/docs/data/01/codes/HT.py b/svetlakovkyu/docs/data/01/codes/HT.py new file mode 100644 index 0000000..8680c91 --- /dev/null +++ b/svetlakovkyu/docs/data/01/codes/HT.py @@ -0,0 +1,28 @@ +from codes.LL import ll_insert, ll_find, ll_delete +def ht_insert(buckets, name, phone): + index = hash(name) % len(buckets) + current_head = buckets[index] + new_head = ll_insert(current_head, name, phone) + buckets[index] = new_head +# print("-"*100) +# print(buskets) + +def ht_find(buckets, name): + index = hash(name)%len(buckets) + slot_head = buckets[index] + res_ph = ll_find(slot_head, name) + return res_ph + +def ht_delete(buskets, name): + index = hash(name)%len(buskets) + buskets[index] = ll_delete(buskets[index], name) + +def ht_list_all(buckets): + all_rec = [] + for head in buckets: + current = head + while current is not None: + all_rec.append((current['name'], current['phone'])) + current = current['next'] + all_rec.sort() + return all_rec \ No newline at end of file diff --git a/svetlakovkyu/docs/data/01/codes/LL.py b/svetlakovkyu/docs/data/01/codes/LL.py new file mode 100644 index 0000000..61116b8 --- /dev/null +++ b/svetlakovkyu/docs/data/01/codes/LL.py @@ -0,0 +1,46 @@ +def ll_insert(head, name, phone): + current = head + while current is not None: + if current['name'] == name: + current['phone'] = phone + return head + current = current['next'] + return {'name': name, 'phone': phone, 'next': head} +# проверка +# my_list = None +# my_list = ll_insert(my_list, "Ivan", "555") +# my_list = ll_insert(my_list, "An", "666") +# print(my_list) + +def ll_find(head, name): + current = head + while current is not None: + if current['name'] == name: + return current['phone'] + current = current['next'] + return None +# print(ll_find(my_list,"An")) +# buskets = [None]*10 +# print(buskets) + +def ll_delete(head, name): + if head is None: + return None + current = head + if head['name'] == name: + return head['next'] + while current['next'] is not None: + if current['next']['name'] == name: + current['next'] = current['next']['next'] + break + current = current['next'] + return head + +def ll_list_all(head): + res = [] + current = head + while current is not None: + res.append((current['name'], current['phone'])) + current = current['next'] + res.sort() + return res diff --git a/svetlakovkyu/docs/data/01/codes/__init__.py b/svetlakovkyu/docs/data/01/codes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/svetlakovkyu/docs/data/01/codes/experiment.py b/svetlakovkyu/docs/data/01/codes/experiment.py new file mode 100644 index 0000000..edd4002 --- /dev/null +++ b/svetlakovkyu/docs/data/01/codes/experiment.py @@ -0,0 +1,101 @@ +import random +import time +import csv +import sys + +sys.setrecursionlimit(20000) + +from codes.LL import ll_insert, ll_find, ll_delete +from codes.HT import ht_insert, ht_delete, ht_find +from codes.BST import bst_delete, bst_find, bst_insert + +# Экспериментальная часть +def generate_records(N): + records =[] + for i in range(N): + name = f"User_{i:05d}" + phone =str(random.randint(100000,999999)) + records.append((name,phone)) + return records + + +def run_test(structure_name, mode, input_data, search_names, delete_names): + print(f"Тест {structure_name} в режиме {mode}") + ins_times = [] + find_times = [] + del_times = [] + for _ in range(5): + if structure_name == "LL": + container = None + elif structure_name == "BST": + container = None + elif structure_name == "HT": + container = [None]*150 + #А + start = time.perf_counter() + for name, phone in input_data: + if structure_name == "LL": + container = ll_insert(container, name, phone) + elif structure_name == "BST": + container = bst_insert(container, name, phone) + elif structure_name == "HT": + ht_insert(container, name, phone) + ins_times.append(time.perf_counter() - start) + #Б + start = time.perf_counter() + for name in search_names: + if structure_name == "LL": ll_find(container, name) + elif structure_name == "BST": bst_find(container, name) + else: ht_find(container, name) + find_times.append(time.perf_counter() - start) + #В + start = time.perf_counter() + for name in delete_names: + if structure_name == "LL": container = ll_delete(container, name) + elif structure_name == "BST": container = bst_delete(container, name) + else: ht_delete(container, name) + del_times.append(time.perf_counter() - start) + + results = [] + + for i in range(5): + results.append([structure_name, mode, f"Вставка (попытка {i+1})", ins_times[i]]) + results.append([structure_name, mode, "Вставка СРЕДНЕЕ", sum(ins_times) / 5]) + + for i in range(5): + results.append([structure_name, mode, f"Поиск (попытка {i+1})", find_times[i]]) + results.append([structure_name, mode, "Поиск СРЕДНЕЕ", sum(find_times) / 5]) + + for i in range(5): + results.append([structure_name, mode, f"Удаление (попытка {i+1})", del_times[i]]) + results.append([structure_name, mode, "Удаление СРЕДНЕЕ", sum(del_times) / 5]) + + return results + +def main_experiment(): + N = 10000 + + data = generate_records(N) + random.shuffle(data) + data_sort = sorted(data, key = lambda x: x[0]) + + search_names = [r[0] for r in random.sample(data, 100)] + [f"None_{i}" for i in range(10)] + delete_names = [r[0] for r in random.sample(data, 50)] + + results = [["Structure", "Mode", "Operation", "Time"]] + + for mode_name, mode_data in [("shufled", data), ("sorted", data_sort)]: + results += run_test("LL", mode_name, mode_data, search_names, delete_names) + results += run_test("BST", mode_name, mode_data, search_names, delete_names) + results += run_test("HT", mode_name, mode_data, search_names, delete_names) + + + + with open("results.csv", "w", newline = "") as f: + writer = csv.writer(f) + writer.writerows(results) + print("Результаты сохранены в файл") + + +if __name__ == "__main__": + main_experiment() \ No newline at end of file diff --git a/svetlakovkyu/docs/data/01/codes/plot.py b/svetlakovkyu/docs/data/01/codes/plot.py new file mode 100644 index 0000000..adf93c3 --- /dev/null +++ b/svetlakovkyu/docs/data/01/codes/plot.py @@ -0,0 +1,59 @@ +import numpy as np +import matplotlib.pyplot as plt +import pandas as pd + +# Графики + +def plot_all(df: pd.DataFrame): + structures = ["BST", "HT", "LL"] + operations = ["Вставка", "Поиск", "Удаление"] + op_keys = ["Вставка СРЕДНЕЕ", "Поиск СРЕДНЕЕ", "Удаление СРЕДНЕЕ"] + filenames = ["./insert_plot.png", "./search_plot.png", "./delete_plot.png"] + + avg = df[df["Operation"].isin(op_keys)] + + data = { + (row.Structure, row.Mode, row.Operation): row.Time + for row in avg.itertuples(index=False) + } + + x = np.arange(len(structures)) + width = 0.35 + + for op_label, op_key, filename in zip(operations, op_keys, filenames): + fig, ax = plt.subplots(figsize=(8, 5)) + + times_shuffled = [data.get((s, "shufled", op_key), float('nan')) for s in structures] + times_sorted = [data.get((s, "sorted", op_key), 0) for s in structures] + + bars1 = ax.bar(x - width/2, times_shuffled, width, label="Случайный", color="steelblue") + bars2 = ax.bar(x + width/2, times_sorted, width, label="Отсортированный", color="red") + + for bar in bars1: + height = bar.get_height() + ax.text(bar.get_x() + bar.get_width() / 2, height, + f"{height:.6f}", ha="center", va="bottom", fontsize=8) + + for bar in bars2: + height = bar.get_height() + ax.text(bar.get_x() + bar.get_width() / 2, height, + f"{height:.6f}", ha="center", va="bottom", fontsize=8) + + ax.set_yscale("log") + ax.set_title(op_label) + ax.set_ylabel("Время (сек)") + ax.set_xticks(x) + ax.set_xticklabels(structures) + ax.legend() + ax.grid(axis="y", linestyle="--", alpha=0.7) + + plt.tight_layout() + plt.savefig(filename) + print(f"График сохранён: {filename}") + +def main_plot(): + results = pd.read_csv("./results.csv") + plot_all(results) + +if __name__ == "__main__": + main_plot() \ No newline at end of file diff --git a/svetlakovkyu/docs/data/01/delete_plot.png b/svetlakovkyu/docs/data/01/delete_plot.png new file mode 100644 index 0000000..678a8fc Binary files /dev/null and b/svetlakovkyu/docs/data/01/delete_plot.png differ diff --git a/svetlakovkyu/docs/data/01/insert_plot.png b/svetlakovkyu/docs/data/01/insert_plot.png new file mode 100644 index 0000000..44305c6 Binary files /dev/null and b/svetlakovkyu/docs/data/01/insert_plot.png differ diff --git a/svetlakovkyu/docs/data/01/main.py b/svetlakovkyu/docs/data/01/main.py new file mode 100644 index 0000000..9a9f368 --- /dev/null +++ b/svetlakovkyu/docs/data/01/main.py @@ -0,0 +1,6 @@ +from codes.plot import main_plot +from codes.experiment import main_experiment + +if __name__ == "__main__": + main_experiment() + main_plot() diff --git a/svetlakovkyu/docs/data/01/results.csv b/svetlakovkyu/docs/data/01/results.csv new file mode 100644 index 0000000..5451205 --- /dev/null +++ b/svetlakovkyu/docs/data/01/results.csv @@ -0,0 +1,109 @@ +Structure,Mode,Operation,Time +LL,shufled,Вставка (попытка 1),2.4339829910004482 +LL,shufled,Вставка (попытка 2),2.4039403810002113 +LL,shufled,Вставка (попытка 3),2.458547864999673 +LL,shufled,Вставка (попытка 4),2.413923469999645 +LL,shufled,Вставка (попытка 5),2.4440171969999938 +LL,shufled,Вставка СРЕДНЕЕ,2.430882380799994 +LL,shufled,Поиск (попытка 1),0.031343970999841986 +LL,shufled,Поиск (попытка 2),0.03304426499926194 +LL,shufled,Поиск (попытка 3),0.03239262500028417 +LL,shufled,Поиск (попытка 4),0.03132577799988212 +LL,shufled,Поиск (попытка 5),0.03151892799996858 +LL,shufled,Поиск СРЕДНЕЕ,0.03192511339984776 +LL,shufled,Удаление (попытка 1),0.01929760500024713 +LL,shufled,Удаление (попытка 2),0.019222485999307537 +LL,shufled,Удаление (попытка 3),0.019446178000180225 +LL,shufled,Удаление (попытка 4),0.019156967000526492 +LL,shufled,Удаление (попытка 5),0.019182482000360324 +LL,shufled,Удаление СРЕДНЕЕ,0.01926114360012434 +BST,shufled,Вставка (попытка 1),0.02289151100012532 +BST,shufled,Вставка (попытка 2),0.0229584500002602 +BST,shufled,Вставка (попытка 3),0.02520326100056991 +BST,shufled,Вставка (попытка 4),0.03390770399983012 +BST,shufled,Вставка (попытка 5),0.022019966999323515 +BST,shufled,Вставка СРЕДНЕЕ,0.025396178600021812 +BST,shufled,Поиск (попытка 1),0.000189066000530147 +BST,shufled,Поиск (попытка 2),0.00023037999926600605 +BST,shufled,Поиск (попытка 3),0.00018933499995910097 +BST,shufled,Поиск (попытка 4),0.0001907400001073256 +BST,shufled,Поиск (попытка 5),0.00024124399988068035 +BST,shufled,Поиск СРЕДНЕЕ,0.000208152999948652 +BST,shufled,Удаление (попытка 1),0.00011542900028871372 +BST,shufled,Удаление (попытка 2),0.0001963419999810867 +BST,shufled,Удаление (попытка 3),0.00011738299963326426 +BST,shufled,Удаление (попытка 4),0.00011437200009822845 +BST,shufled,Удаление (попытка 5),0.00015602500025124755 +BST,shufled,Удаление СРЕДНЕЕ,0.00013991020005050815 +HT,shufled,Вставка (попытка 1),0.0291972099994382 +HT,shufled,Вставка (попытка 2),0.02882972299994435 +HT,shufled,Вставка (попытка 3),0.028241992999937793 +HT,shufled,Вставка (попытка 4),0.02896075100034068 +HT,shufled,Вставка (попытка 5),0.029606111000248347 +HT,shufled,Вставка СРЕДНЕЕ,0.028967157599981874 +HT,shufled,Поиск (попытка 1),0.0014172729997881106 +HT,shufled,Поиск (попытка 2),0.0003571010001905961 +HT,shufled,Поиск (попытка 3),0.0004458979992705281 +HT,shufled,Поиск (попытка 4),0.0005671679991792189 +HT,shufled,Поиск (попытка 5),0.0004517190000115079 +HT,shufled,Поиск СРЕДНЕЕ,0.0006478317996879923 +HT,shufled,Удаление (попытка 1),0.000430808000601246 +HT,shufled,Удаление (попытка 2),0.000223173999984283 +HT,shufled,Удаление (попытка 3),0.0001869629995780997 +HT,shufled,Удаление (попытка 4),0.00021918600032222457 +HT,shufled,Удаление (попытка 5),0.00026948999948217534 +HT,shufled,Удаление СРЕДНЕЕ,0.0002659241999936057 +LL,sorted,Вставка (попытка 1),2.546284423999168 +LL,sorted,Вставка (попытка 2),2.527255480000349 +LL,sorted,Вставка (попытка 3),2.4814426879993334 +LL,sorted,Вставка (попытка 4),2.501784361999853 +LL,sorted,Вставка (попытка 5),2.480424575000143 +LL,sorted,Вставка СРЕДНЕЕ,2.507438305799769 +LL,sorted,Поиск (попытка 1),0.030172253000273486 +LL,sorted,Поиск (попытка 2),0.030333151999911934 +LL,sorted,Поиск (попытка 3),0.030028871000467916 +LL,sorted,Поиск (попытка 4),0.032277871000587766 +LL,sorted,Поиск (попытка 5),0.030364938999809965 +LL,sorted,Поиск СРЕДНЕЕ,0.030635417200210215 +LL,sorted,Удаление (попытка 1),0.02085002199964947 +LL,sorted,Удаление (попытка 2),0.021086609999656503 +LL,sorted,Удаление (попытка 3),0.02087502099948324 +LL,sorted,Удаление (попытка 4),0.021417887000097835 +LL,sorted,Удаление (попытка 5),0.020822566000788356 +LL,sorted,Удаление СРЕДНЕЕ,0.02101042119993508 +BST,sorted,Вставка (попытка 1),11.841748112999994 +BST,sorted,Вставка (попытка 2),11.75695861999975 +BST,sorted,Вставка (попытка 3),11.649890955999581 +BST,sorted,Вставка (попытка 4),11.535229737999543 +BST,sorted,Вставка (попытка 5),11.569677194999713 +BST,sorted,Вставка СРЕДНЕЕ,11.670700924399716 +BST,sorted,Поиск (попытка 1),0.08916782800042711 +BST,sorted,Поиск (попытка 2),0.09862517000055959 +BST,sorted,Поиск (попытка 3),0.08865728399996442 +BST,sorted,Поиск (попытка 4),0.08748506499978248 +BST,sorted,Поиск (попытка 5),0.08942283200030943 +BST,sorted,Поиск СРЕДНЕЕ,0.09067163580020861 +BST,sorted,Удаление (попытка 1),0.046499003000462835 +BST,sorted,Удаление (попытка 2),0.04566383200017299 +BST,sorted,Удаление (попытка 3),0.04534191499988083 +BST,sorted,Удаление (попытка 4),0.04480698100087466 +BST,sorted,Удаление (попытка 5),0.045543646000623994 +BST,sorted,Удаление СРЕДНЕЕ,0.04557107540040306 +HT,sorted,Вставка (попытка 1),0.03348540300066816 +HT,sorted,Вставка (попытка 2),0.03959386299993639 +HT,sorted,Вставка (попытка 3),0.026414189000206534 +HT,sorted,Вставка (попытка 4),0.028397822999977507 +HT,sorted,Вставка (попытка 5),0.0283703630002492 +HT,sorted,Вставка СРЕДНЕЕ,0.031252328200207555 +HT,sorted,Поиск (попытка 1),0.00092922399926465 +HT,sorted,Поиск (попытка 2),0.0005907800004933961 +HT,sorted,Поиск (попытка 3),0.00031636099993193056 +HT,sorted,Поиск (попытка 4),0.00034901999970315956 +HT,sorted,Поиск (попытка 5),0.0003578830001060851 +HT,sorted,Поиск СРЕДНЕЕ,0.0005086535998998443 +HT,sorted,Удаление (попытка 1),0.0003968310002164799 +HT,sorted,Удаление (попытка 2),0.0009146930005954346 +HT,sorted,Удаление (попытка 3),0.000194480000573094 +HT,sorted,Удаление (попытка 4),0.00020164000034128549 +HT,sorted,Удаление (попытка 5),0.00023484999928768957 +HT,sorted,Удаление СРЕДНЕЕ,0.0003884988002027967 diff --git a/svetlakovkyu/docs/data/01/search_plot.png b/svetlakovkyu/docs/data/01/search_plot.png new file mode 100644 index 0000000..9210f4a Binary files /dev/null and b/svetlakovkyu/docs/data/01/search_plot.png differ diff --git a/svetlakovkyu/docs/report.md b/svetlakovkyu/docs/report.md new file mode 100644 index 0000000..a24e6e9 --- /dev/null +++ b/svetlakovkyu/docs/report.md @@ -0,0 +1,267 @@ +# Задание 1: Структуры данных + + +>Выполнил: Светлаков Кирилл +> +>Студент 426 группы + + + +## Цель работы + +Реализовать три различные структуры данных «с нуля», применить их для хранения записей телефонного справочника и экспериментально сравнить производительность основных операций. Необходимо собственными руками написать код, чтобы понять внутреннее устройство связного списка, хеш-таблицы и двоичного дерева поиска, а также осознать их сильные и слабые стороны на практике. + +--- + +## 1. Реализация структур данных + +### 1.1 Связный список (Linked List) + +```python +def ll_insert(head, name, phone): + current = head + while current is not None: + if current['name'] == name: + current['phone'] = phone + return head + current = current['next'] + return {'name': name, 'phone': phone, 'next': 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 + current = head + if head['name'] == name: + return head['next'] + while current['next'] is not None: + if current['next']['name'] == name: + current['next'] = current['next']['next'] + break + current = current['next'] + return head + +def ll_list_all(head): + res = [] + current = head + while current is not None: + res.append((current['name'], current['phone'])) + current = current['next'] + res.sort() + return res +``` + +--- + +### 1.2 Хеш-таблица (Hash Table) + +```python +def ht_insert(buckets, name, phone): + index = hash(name) % len(buckets) + current_head = buckets[index] + new_head = ll_insert(current_head, name, phone) + buckets[index] = new_head + +def ht_find(buckets, name): + index = hash(name)%len(buckets) + slot_head = buckets[index] + res_ph = ll_find(slot_head, name) + return res_ph + +def ht_delete(buskets, name): + index = hash(name)%len(buskets) + buskets[index] = ll_delete(buskets[index], name) + +def ht_list_all(buckets): + all_rec = [] + for head in buckets: + current = head + while current is not None: + all_rec.append((current['name'], current['phone'])) + current = current['next'] + all_rec.sort() + return all_rec +``` + +--- + +### 1.3 Двоичное дерево поиска (BST) +```python +def bst_insert(root, name, phone): + if root is None: + return {'name': name, 'phone': phone, 'left': None, 'right': None} + 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 root['phone'] + + if name < root['name']: + return bst_find(root['left'], name) + else: + return bst_find(root['right'], name) + +def get_min(node): + current = node + while 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'] + if root['right'] is None: + return root['left'] + successor = get_min(root['right']) + root['name'] = successor['name'] + root['phone'] = successor['phone'] + root['right'] = bst_delete(root['right'], successor['name']) + return root + +def bst_list_all(root, res = None): + if res is None: + res = [] + if root is not None: + bst_list_all(root['left'], res) + res.append({'name': root['name'], 'phone': root['phone']}) + bst_list_all(root['right'], res) + #сортировка уже сделана + return res +``` + +--- + +## 2. Эксперимент + +Для экспериментального сравнения были сгенерированы 10 000 записей вида `(User_XXXXX, номер_телефона)`. + +Каждый тест проводился в двух режимах входных данных: +- Случайный (shuffled) - записи перемешаны в произвольном порядке +- Отсортированный (sorted) - записи отсортированы по имени + +Для каждой структуры и режима измерялось время выполнения трёх операций: +- Вставка - 10 000 элементов +- Поиск - 110 запросов (100 существующих + 10 несуществующих) +- Удаление - 50 элементов + +Каждый замер повторялся 5 раз, итоговое значение - среднее арифметическое. + +В файл `results.py` записывались в следующем виде +| Structure | Mode | Operation | Time | +|---|---|---|---| +| Название структуры: LL, BST, HT | Режим данных: shufled, sorted | Операция и номер попытки или среднее | Время выполнения в секундах | +| LL | shufled | Вставка (попытка 1) | 2.730272 | +| LL | shufled | Вставка (попытка 2) | 2.675253 | +| LL | shufled | Вставка (попытка 3) | 2.628982 | +| LL | shufled | Вставка (попытка 4) | 2.673355 | +| LL | shufled | Вставка (попытка 5) | 2.636129 | +| LL | shufled | Вставка СРЕДНЕЕ | 2.668798 | +| ... | ... | ... | ... | + +--- + + +## 3. Результаты + +### 3.1 Вставка + +![График 1](insert_plot.png) + + +### 3.2 Поиск + +![График 2](search_plot.png) + + +### 3.3 Удаление + +![График 3](delete_plot.png) + + +--- + +## 4. Анализ результатов + +### 4.1 Влияние порядка данных на BST: деградация до O(n) + +При вставке случайных данных BST ведёт себя как сбалансированное дерево: каждый новый ключ с равной вероятностью уходит влево или вправо, глубина дерева составляет ~log₂(10000) ≈ 13. Операция вставки 10 000 записей заняла 0.025 сек. + +При вставке отсортированных данных каждый новый ключ оказывается больше предыдущего и всегда уходит вправо. Дерево вырождается в линейный список глубиной 10 000. Каждая следующая вставка проходит на один шаг дальше, итоговая сложность становится O(1 + 2 + ... + n) = O(n²). Именно поэтому вставка отсортированных данных заняла 13.16 сек - более чем в 500 раз медленнее случайных. + +Это фундаментальный недостаток, реализованного BST. + +--- + +### 4.2 Нечувствительность хеш-таблицы к порядку данных + +Хеш-таблица вычисляет позицию элемента через `hash(name) % len(buckets)`. Функция `hash()` в Python зависит только от значения ключа, но никак не от порядка вставки. Независимо от того, отсортированы данные или перемешаны, каждый ключ попадает в тот же бакет с той же скоростью. + +Это подтверждается полученными результатами: время вставки для случайных данных - 0.0281 сек, для отсортированных - 0.0286 сек, что практически одинаково. + +В графике для поиска и удаления, моожно заметить примено такой же результат - время работы программы для отсортированных и не для неотсортированных данных одинаково. + +--- + +### 4.3 Медленный поиск в связном списке + +Связный список не имеет структуры, позволяющей перейти к нужному элементу. При поиске приходится последовательно проходить узел за узлом от головы до нужного элемента - это линейный поиск O(n). + +При N = 10 000 и 110 запросах среднее время поиска составило 0.03 сек, в ~150 раз медленнее поиска в BST и в ~75 раз медленнее поиска в HT на случайных данных. + +Порядок данных на LL практически не влияет: в любом случае нужно проходить примерно половину списка для найденных и весь список для несуществующих записей. + +--- + +### 4.4 Удаление в каждой структуре + +Связный список: удаление требует сначала найти удаляемый элемент - O(n). Затем достаточно переключить одну ссылку у предшественника. Итог: O(n) за счёт поиска. При 50 удалениях время составило ~0.02 сек. + +Хеш-таблица: вычисляем бакет за O(1), затем удаляем элемент из короткой цепочки. Итог: O(1) амортизированно. При 50 удалениях время составило 0.00003 сек для случайных и 0.00004 для отсортированых данных, что значительно быстрее связного списка, но медленее BST(для неотсортированных данных). + +BST (случайные данные): находим узел за O(log n). Если у него два потомка - находим минимум правого поддерева, копируем его значение и рекурсивно удаляем его. Итог: O(log n). При 50 удалениях время составило 0.00012 сек. + +BST (отсортированные данные): дерево вырождено в список, глубина O(n). Каждое удаление - O(n), 50 удалений - 0.060 сек, что медленнее даже связного списка. + +--- + +## 5. Выводы + +Из результатов эксперимента, можно сделать вывод, что нет универсальной структуры данных, и под конкретную задачу надо выбирать определенную. + + +Хеш-таблица - лучший выбор, если нужны быстрые вставка, поиск и удаление и порядок хранения данных не важен. Идеальна для кешей, словарей, телефонных справочников, где операции выполняются в O(1). Не подходит для задач, требующих обхода данных в отсортированном порядке. + +BST - лучший выбор, когда важен порядок данных: обход дерева in-order даёт отсортированную последовательность за O(n), можно быстро найти минимум/максимум или диапазон ключей. Подходит для задач типа «найти все записи от A до B». Критически важно использовать только на случайных или специально перемешанных данных. + +Связный список - подходит для задач, где данные постоянно добавляются и удаляются с известной позиции (начало/конец списка), а поиск по значению происходит редко. В телефонном справочнике с 10 000 записей является наихудшим вариантом из трёх. + +| Задача | Лучшая структура | +|---|---| +| Частые вставки и удаления по ключу | Хеш-таблица | +| Частый поиск по ключу | Хеш-таблица | +| Обход данных в отсортированном порядке | BST | +| Поиск по диапазону ключей | BST | +| Встава/удаление в начало/конец | Связный список | +| Данные приходят отсортированными, нужен быстрый поиск | Хеш-таблица |