diff --git a/fomichevks/426.md.txt b/fomichevks/426.md similarity index 100% rename from fomichevks/426.md.txt rename to fomichevks/426.md diff --git a/fomichevks/docs/performance_comparison.png b/fomichevks/docs/performance_comparison.png new file mode 100644 index 0000000..71e527b Binary files /dev/null and b/fomichevks/docs/performance_comparison.png differ diff --git a/fomichevks/docs/results.csv b/fomichevks/docs/results.csv new file mode 100644 index 0000000..d72b22a --- /dev/null +++ b/fomichevks/docs/results.csv @@ -0,0 +1,109 @@ +Структура,Режим,Операция,Время (сек) +LinkedList,случайный,insert,3.450394299999971 +LinkedList,случайный,find,0.02368320000005042 +LinkedList,случайный,delete,0.0178195999997115 +HashTable,случайный,insert,0.009212800000568677 +HashTable,случайный,find,6.169999960548012e-05 +HashTable,случайный,delete,3.860000015265541e-05 +BST,случайный,insert,0.015902500000265718 +BST,случайный,find,0.00014120000014372636 +BST,случайный,delete,8.770000022195745e-05 +LinkedList,случайный,insert,3.517222700000275 +LinkedList,случайный,find,0.026108199999725912 +LinkedList,случайный,delete,0.01791400000001886 +HashTable,случайный,insert,0.009205899999869871 +HashTable,случайный,find,5.869999949936755e-05 +HashTable,случайный,delete,3.609999930631602e-05 +BST,случайный,insert,0.017175400000269292 +BST,случайный,find,0.0001505999998698826 +BST,случайный,delete,9.200000022246968e-05 +LinkedList,случайный,insert,3.435324000000037 +LinkedList,случайный,find,0.026613500000166823 +LinkedList,случайный,delete,0.020348300000478048 +HashTable,случайный,insert,0.01095050000003539 +HashTable,случайный,find,6.169999960548012e-05 +HashTable,случайный,delete,3.9000000469968654e-05 +BST,случайный,insert,0.015578700000332901 +BST,случайный,find,0.0001768000001902692 +BST,случайный,delete,0.00010370000018156134 +LinkedList,случайный,insert,3.4476645999993707 +LinkedList,случайный,find,0.025092200000472076 +LinkedList,случайный,delete,0.017970900000364054 +HashTable,случайный,insert,0.008620399999927031 +HashTable,случайный,find,5.6599999879836105e-05 +HashTable,случайный,delete,3.699999979289714e-05 +BST,случайный,insert,0.016901099999813596 +BST,случайный,find,0.0001353999996354105 +BST,случайный,delete,8.39000003907131e-05 +LinkedList,случайный,insert,3.4527680000001055 +LinkedList,случайный,find,0.02482880000025034 +LinkedList,случайный,delete,0.01792089999980817 +HashTable,случайный,insert,0.00791659999958938 +HashTable,случайный,find,0.00012760000026901253 +HashTable,случайный,delete,6.010000015521655e-05 +BST,случайный,insert,0.01643800000056217 +BST,случайный,find,0.0001905999997688923 +BST,случайный,delete,9.900000077323057e-05 +LinkedList,отсортированный,insert,3.2146765999996205 +LinkedList,отсортированный,find,0.02251799999976356 +LinkedList,отсортированный,delete,0.016432399999757763 +HashTable,отсортированный,insert,0.008092500000202563 +HashTable,отсортированный,find,7.089999962772708e-05 +HashTable,отсортированный,delete,4.069999977218686e-05 +BST,отсортированный,insert,8.144065100000262 +BST,отсортированный,find,0.07145860000036919 +BST,отсортированный,delete,0.041536599999744794 +LinkedList,отсортированный,insert,3.2909168000005593 +LinkedList,отсортированный,find,0.1718697999995129 +LinkedList,отсортированный,delete,0.03186750000077154 +HashTable,отсортированный,insert,0.014283700000305544 +HashTable,отсортированный,find,9.820000013860408e-05 +HashTable,отсортированный,delete,5.8200000239594374e-05 +BST,отсортированный,insert,7.79496620000009 +BST,отсортированный,find,0.06252070000027743 +BST,отсортированный,delete,0.04316579999976966 +LinkedList,отсортированный,insert,3.3210246999997253 +LinkedList,отсортированный,find,0.020591699999386037 +LinkedList,отсортированный,delete,0.016228899999987334 +HashTable,отсортированный,insert,0.007315800000469608 +HashTable,отсортированный,find,5.450000026030466e-05 +HashTable,отсортированный,delete,3.370000013092067e-05 +BST,отсортированный,insert,8.219712999999501 +BST,отсортированный,find,0.0645872999994026 +BST,отсортированный,delete,0.04166759999952774 +LinkedList,отсортированный,insert,3.3059798000003866 +LinkedList,отсортированный,find,0.020161800000096264 +LinkedList,отсортированный,delete,0.016405999999733467 +HashTable,отсортированный,insert,0.008103499999378982 +HashTable,отсортированный,find,6.690000009257346e-05 +HashTable,отсортированный,delete,3.999999989900971e-05 +BST,отсортированный,insert,9.020431099999769 +BST,отсортированный,find,0.06939630000033503 +BST,отсортированный,delete,0.04487580000022717 +LinkedList,отсортированный,insert,3.5286267000001317 +LinkedList,отсортированный,find,0.022289700000328594 +LinkedList,отсортированный,delete,0.018663600000763836 +HashTable,отсортированный,insert,0.010729900000114867 +HashTable,отсортированный,find,7.849999929021578e-05 +HashTable,отсортированный,delete,4.8600000809528865e-05 +BST,отсортированный,insert,8.329646700000012 +BST,отсортированный,find,0.06335099999978411 +BST,отсортированный,delete,0.042559800000162795 +LinkedList,случайный,insert (СРЕДНЕЕ),3.4606747199999517 +LinkedList,случайный,find (СРЕДНЕЕ),0.025265180000133114 +LinkedList,случайный,delete (СРЕДНЕЕ),0.018394740000076126 +LinkedList,отсортированный,insert (СРЕДНЕЕ),3.3322449200000848 +LinkedList,отсортированный,find (СРЕДНЕЕ),0.051486199999817475 +LinkedList,отсортированный,delete (СРЕДНЕЕ),0.019919680000202788 +HashTable,случайный,insert (СРЕДНЕЕ),0.00918123999999807 +HashTable,случайный,find (СРЕДНЕЕ),7.325999977183528e-05 +HashTable,случайный,delete (СРЕДНЕЕ),4.215999997541076e-05 +HashTable,отсортированный,insert (СРЕДНЕЕ),0.009705080000094313 +HashTable,отсортированный,find (СРЕДНЕЕ),7.379999988188501e-05 +HashTable,отсортированный,delete (СРЕДНЕЕ),4.4240000170248096e-05 +BST,случайный,insert (СРЕДНЕЕ),0.016399140000248735 +BST,случайный,find (СРЕДНЕЕ),0.0001589199999216362 +BST,случайный,delete (СРЕДНЕЕ),9.326000035798643e-05 +BST,отсортированный,insert (СРЕДНЕЕ),8.301764419999927 +BST,отсортированный,find (СРЕДНЕЕ),0.06626278000003367 +BST,отсортированный,delete (СРЕДНЕЕ),0.04276111999988643 diff --git a/fomichevks/docs/отчет.txt b/fomichevks/docs/отчет.txt new file mode 100644 index 0000000..04bd828 --- /dev/null +++ b/fomichevks/docs/отчет.txt @@ -0,0 +1,137 @@ +1. Цель работы +Реализовать три базовые структуры данных без использования объектно-ориентированных механизмов, применить их для хранения записей телефонного справочника, экспериментально измерить производительность операций вставки, поиска и удаления, а также проанализировать влияние порядка входных данных на время выполнения. + +Связный список (LinkedListPhoneBook) +Узел: {'name': str, 'phone': str, 'next': dict | None} +Операции: +ll_insert: линейный проход до конца, обновление при совпадении имени, вставка нового узла в хвост. Возвращает голову списка. +ll_find: последовательный перебор до первого совпадения. +ll_delete: поиск предшественника удаляемого узла, переназначение ссылки next. +ll_list_all: сбор записей в список, явная сортировка по имени. + +Хеш-таблица с цепочками (HashTable) +Структура: список из BUCKET_COUNT = 1024 элементов, каждый элемент — голова связного списка. +Хеширование: idx = hash(name) % BUCKET_COUNT +Операции: делегируют соответствующим ll_* функциям для конкретного бакета. + +Узел: {'name': str, 'phone': str, 'left': dict | None, 'right': dict | None} +Операции: +bst_insert: рекурсивное сравнение имён, создание листа при достижении None. +bst_find: рекурсивный спуск влево/вправо в зависимости от результата сравнения. +bst_delete: три случая: 0 потомков, 1 потомок, 2 потомка. При двух потомках используется inorder-преемник (минимальный элемент правого поддерева). +bst_list_all: центрированный (in-order) обход, гарантирующий отсортированный вывод без дополнительной сортировки. + +Влияние порядка входных данных на скорость вставки в BST +Двоичное дерево поиска (BST) поддерживает инвариант: left.name < root.name < right.name. При вставке новых узлов алгоритм рекурсивно спускается по дереву, выбирая левую или правую ветвь в зависимости от результата сравнения. + +Случай 1: Случайный порядок данных +Ключи распределяются по дереву хаотично +Левые и правые поддеревья заполняются примерно равномерно +Высота дерева: h ≈ log₂(N) ≈ 14 для N=10 000 +Сложность вставки одного элемента: O(log N) +Общая сложность вставки всех N элементов: O(N log N) + +Случай 2: Отсортированный порядок данных +Каждый следующий ключ больше всех предыдущих +Алгоритм всегда выбирает правую ветвь +Дерево вырождается в линейную цепочку + +Почему хеш-таблица почти не чувствительна к порядку? + +Функция hash() в Python: +Детерминирована: один и тот же ключ → один и тот же хеш +Равномерно распределяет значения по пространству хешей +Не зависит от порядка вызова: hash("User_00001") всегда одинаков + +Распределение по бакетам +При N=10 000 записей и 1024 бакетах: +Ожидаемая загрузка: α = N / BUCKET_COUNT ≈ 9.77 элементов на бакет +Даже если все ключи отсортированы, их хеши «размазываются» по всему диапазону +Внутри каждого бакета хранится короткий связный список (~10 элементов) + +Почему связный список всегда медленен при поиске? + +Связный список хранит элементы последовательно, без индексации +Для поиска элемента с именем X: +Начать с головы списка +Сравнить curr['name'] == X +Если не совпало → перейти к curr['next'] +Повторять до нахождения или конца списка +Связный список не подходит для задач с частым поиском. Его удел очереди, стеки, или вспомогательная роль внутри других структур. + +Как удаление работает в каждой структуре? +Связный список +def ll_delete(head, name): + if head['name'] == name: + return head['next'] + curr = head + while curr['next']: + if curr['next']['name'] == name: + curr['next'] = curr['next']['next'] + return head + curr = curr['next'] + return head + +Поиск узла (или его предшественника) — O(N) +Переназначение ссылки next — O(1) +Сборка мусора (автоматически в Python) + +Хеш-таблица +def ht_delete(buckets, name): + idx = hash(name) % BUCKET_COUNT + buckets[idx] = ll_delete(buckets[idx], name) + +Вычисление индекса бакета — O(1) +Поиск и удаление в связном списке бакета — O(L), где L ≈ 10 +Итого: O(1) в среднем + +Двоичное дерево поиска +def bst_delete(root, name): + # 1. Поиск узла + if name < root['name']: + root['left'] = bst_delete(root['left'], name) + elif name > root['name']: + root['right'] = bst_delete(root['right'], name) + else: + # 2. Три случая удаления + if root['left'] is None: + return root['right'] # 0 или 1 потомок + elif root['right'] is None: + return root['left'] + else: + # 2 потомка: найти inorder-преемника + successor = _bst_find_min(root['right']) + root['name'] = successor['name'] + root['phone'] = successor['phone'] + root['right'] = bst_delete(root['right'], successor['name']) + return root + +Поиск удаляемого узла — O(h) +Обработка случая: +0 потомков: просто удалить узел +1 потомок: «поднять» потомка на место удаляемого +2 потомка: найти минимум в правом поддереве (inorder-преемник), скопировать его данные, рекурсивно удалить преемника +Возврат обновлённого корня поддерева + +Когда какую структуру использовать? + +| Сценарий | Рекомендация | +|---|---| +| **Частый поиск** по имени | HashTable или BST (случайные данные) | +| **Данные приходят отсортированными** | HashTable (BST деградирует!) | +| **Нужен отсортированный список** | BST (in-order обход — бесплатный) | +| **Частые вставки/удаления + поиск** | HashTable | +| **Минимальная память, простота** | LinkedList (для малых N) | +| **Диапазонные запросы** (все имена A–M) | BST | + +### Сложности операций + +| Структура | Insert | Find | Delete | List (sorted) | +|---|---|---|---|---| +| LinkedList | O(n) | O(n) | O(n) | O(n log n) | +| HashTable | O(1) avg | O(1) avg | O(1) avg | O(n log n) | +| BST (сбалансированный) | O(log n) | O(log n) | O(log n) | O(n) | +| BST (вырожденный) | O(n) | O(n) | O(n) | O(n) | + + +HashTable — лучший выбор для телефонного справочника при частых вставках и поисках. BST лучше HashTable только если нужен отсортированный вывод без дополнительной сортировки — но при условии случайного порядка вставки или использования самобалансирующегося дерева (AVL, Red-Black). diff --git a/fomichevks/docs/структуры_данных.py b/fomichevks/docs/структуры_данных.py new file mode 100644 index 0000000..1eaa7dd --- /dev/null +++ b/fomichevks/docs/структуры_данных.py @@ -0,0 +1,257 @@ +import random +import time +import csv +import sys + + +sys.setrecursionlimit(20000) + + +# 1. СВЯЗНЫЙ СПИСОК +def ll_insert(head, name, phone): + curr = head + while curr: + if curr['name'] == name: + curr['phone'] = phone + return head + curr = curr['next'] + + new_node = {'name': name, 'phone': phone, 'next': None} + if head is None: + return new_node + + curr = head + while curr['next']: + curr = curr['next'] + curr['next'] = new_node + return head + + +def ll_find(head, name): + curr = head + while curr: + 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'] + + curr = head + while curr['next']: + if curr['next']['name'] == name: + curr['next'] = curr['next']['next'] + return head + curr = curr['next'] + return head + + +def ll_list_all(head): + res = [] + curr = head + while curr: + res.append((curr['name'], curr['phone'])) + curr = curr['next'] + res.sort(key=lambda x: x[0]) + return res + + + +# 2. ХЕШ-ТАБЛИЦА +BUCKET_COUNT = 1024 + + +def ht_insert(buckets, name, phone): + idx = hash(name) % BUCKET_COUNT + buckets[idx] = ll_insert(buckets[idx], name, phone) + + +def ht_find(buckets, name): + idx = hash(name) % BUCKET_COUNT + return ll_find(buckets[idx], name) + + +def ht_delete(buckets, name): + idx = hash(name) % BUCKET_COUNT + buckets[idx] = ll_delete(buckets[idx], name) + + +def ht_list_all(buckets): + res = [] + for head in buckets: + curr = head + while curr: + res.append((curr['name'], curr['phone'])) + curr = curr['next'] + res.sort(key=lambda x: x[0]) + return res + + + +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'] + elif name < root['name']: + return bst_find(root['left'], name) + else: + return bst_find(root['right'], name) + + +def _bst_find_min(node): + curr = node + while curr['left'] is not None: + curr = curr['left'] + return curr + + +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'] + else: + successor = _bst_find_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 = [] + + def inorder(node): + if node: + inorder(node['left']) + res.append((node['name'], node['phone'])) + inorder(node['right']) + + inorder(root) + return res + + + +# ЭКСПЕРИМЕНТАЛЬНАЯ ЧАСТЬ +def run_experiments(): + N = 10000 + base_records = [(f"User_{i:05d}", f"100{i:05d}") for i in range(N)] + + records_sorted = sorted(base_records, key=lambda x: x[0]) + records_shuffled = base_records[:] + random.shuffle(records_shuffled) + + all_names = [r[0] for r in base_records] + find_existing = random.sample(all_names, 100) + find_non_existing = [f"Missing_{i}" for i in range(10)] + delete_targets = random.sample(all_names, 50) + + all_results = [] + structures = ["LinkedList", "HashTable", "BST"] + data_modes = [("случайный", records_shuffled), ("отсортированный", records_sorted)] + + for mode_name, records in data_modes: + print(f"\n Режим: {mode_name}") + for run in range(1, 6): + print(f" запуск {run}/5") + + + head = None + t = time.perf_counter() + for n, p in records: head = ll_insert(head, n, p) + t_ins = time.perf_counter() - t + + t = time.perf_counter() + for n in find_existing + find_non_existing: ll_find(head, n) + t_find = time.perf_counter() - t + + t = time.perf_counter() + for n in delete_targets: head = ll_delete(head, n) + t_del = time.perf_counter() - t + + all_results.append(["LinkedList", mode_name, "insert", t_ins]) + all_results.append(["LinkedList", mode_name, "find", t_find]) + all_results.append(["LinkedList", mode_name, "delete", t_del]) + + + buckets = [None] * BUCKET_COUNT + t = time.perf_counter() + for n, p in records: ht_insert(buckets, n, p) + t_ins = time.perf_counter() - t + + t = time.perf_counter() + for n in find_existing + find_non_existing: ht_find(buckets, n) + t_find = time.perf_counter() - t + + t = time.perf_counter() + for n in delete_targets: ht_delete(buckets, n) + t_del = time.perf_counter() - t + + all_results.append(["HashTable", mode_name, "insert", t_ins]) + all_results.append(["HashTable", mode_name, "find", t_find]) + all_results.append(["HashTable", mode_name, "delete", t_del]) + + + root = None + t = time.perf_counter() + for n, p in records: root = bst_insert(root, n, p) + t_ins = time.perf_counter() - t + + t = time.perf_counter() + for n in find_existing + find_non_existing: bst_find(root, n) + t_find = time.perf_counter() - t + + t = time.perf_counter() + for n in delete_targets: root = bst_delete(root, n) + t_del = time.perf_counter() - t + + all_results.append(["BST", mode_name, "insert", t_ins]) + all_results.append(["BST", mode_name, "find", t_find]) + all_results.append(["BST", mode_name, "delete", t_del]) + + + averages = [] + for struct in structures: + for mode in ["случайный", "отсортированный"]: + for op in ["insert", "find", "delete"]: + times = [r[3] for r in all_results if r[0] == struct and r[1] == mode and r[2] == op] + avg = sum(times) / len(times) + averages.append([struct, mode, f"{op} (СРЕДНЕЕ)", avg]) + + final_csv_data = [["Структура", "Режим", "Операция", "Время (сек)"]] + all_results + averages + + with open("results.csv", "w", newline="", encoding="utf-8-sig") as f: + writer = csv.writer(f) + writer.writerows(final_csv_data) + + return all_results, averages + + +if __name__ == "__main__": + raw_data, avg_data = run_experiments() +