2026-rff_mp/SavelevMI/docs/report-1.md

12 KiB
Raw Blame History

Отчёт по лабораторной работе «Структуры данных»

Цель работы

Реализовать три структуры данных «с нуля» (связный список, хеш‑таблицу, двоичное дерево поиска) для хранения записей телефонного справочника. Экспериментально сравнить производительность операций вставки, поиска и удаления на наборе из 10000 записей при случайном и отсортированном порядке поступления данных.

Реализованные структуры

Все структуры написаны в процедурном стиле без использования классов.

  1. Связный список узлы в виде словарей {'name': str, 'phone': str, 'next': None}.
  2. Хеш‑таблица массив из 10 корзин, каждая корзина связный список. Хеш‑функция hash(name) % size.
  3. Двоичное дерево поиска узлы {'name': str, 'phone': str, 'left': None, 'right': None}. Операции реализованы рекурсивно.

Методика эксперимента

  • Генерация данных: 10000 записей с именами User_00001User_10000. Телефоны случайные строки вида XXX-XXXX.
  • Два режима подачи данных:
    Случайный записи перемешаны.
    Отсортированный записи по возрастанию имени.
  • Измеряемые операции:
    Вставка всех 10000 записей.
    Поиск 110 имён (100 существующих + 10 несуществующих).
    Удаление 50 случайных существующих записей.
  • Повторы: каждый эксперимент выполнен 5 раз, зафиксировано среднее время.

Результаты замеров сохранены в experiment_results.csv. Время измерялось через time.perf_counter().

Результаты измерений

Связный список (LinkedList)

При 10000 записях связный список показал ожидаемо низкую производительность. Вставка всех элементов заняла около 4.4 секунды в среднем, поиск около 0.027 секунды, удаление 50 записей около 0.012 секунды. Порядок входных данных практически не повлиял на результаты (случайный и отсортированный режимы показали близкие значения). Это объясняется тем, что связный список всегда работает за линейное время O(n) независимо от того, как приходят данные.

Хеш‑таблица (HashTable)

Хеш‑таблица с 10 корзинами показала значительное ускорение по сравнению со связным списком. Вставка 10000 записей заняла в среднем 0.56 секунды (почти в 8 раз быстрее списка). Поиск выполняется за 0.004 секунды (в 7 раз быстрее), а удаление за 0.0016 секунды (в 7.5 раз быстрее). Порядок данных практически не влияет на производительность разница между случайным и отсортированным режимами не превышает 10%, что соответствует теоретической сложности O(1) в среднем.

Двоичное дерево поиска (BST)

Здесь наблюдается самая интересная картина:

На случайных данных BST показал выдающуюся производительность. Вставка всех 10000 записей заняла всего 0.025 секунды, что в 22 раза быстрее хеш‑таблицы и в 176 раз быстрее связного списка. Поиск выполняется за 0.00024 секунды (в 16 раз быстрее хеш‑таблицы), удаление за 0.00017 секунды (почти в 10 раз быстрее). Это идеальный случай сбалансированного дерева.

На отсортированных данных ситуация кардинально меняется. Дерево вырождается в линейный список, и производительность падает катастрофически. Вставка замедлилась до 10.15 секунды это в 406 раз медленнее, чем на случайных данных, и даже медленнее, чем у связного списка (в 2.3 раза). Поиск вырос до 0.091 секунды (в 380 раз медленнее), удаление до 0.057 секунды (в 335 раз медленнее). Это классический пример деградации BST при упорядоченных входных данных.

Анализ результатов

Как порядок входных данных влияет на скорость вставки в BST

Эксперимент наглядно демонстрирует проблему наивной реализации двоичного дерева поиска. На случайных данных дерево остаётся достаточно сбалансированным, и операции выполняются за логарифмическое время (O(log n)). Однако на отсортированных данных каждый новый элемент становится самым большим и добавляется только в правую ветку. В результате дерево превращается в односвязный список высотой 10000 узлов, а сложность всех операций деградирует до линейной O(n). Это подтверждается цифрами: время вставки выросло с 0.025 до 10.15 секунд разница в 406 раз.

Почему хеш‑таблица почти не чувствительна к порядку

Хеш‑функция распределяет ключи по корзинам независимо от того, в каком порядке они поступают. «User_00001» и «User_10000» с равной вероятностью могут попасть в любую из 10 корзин. Поэтому порядок ввода не влияет на длину цепочек в каждой корзине. Результаты подтверждают это: в случайном и отсортированном режимах время выполнения операций отличается незначительно (менее 10%).

Почему связный список всегда медленен при поиске

Связный список не имеет индексов или другой структуры для ускорения доступа. Чтобы найти элемент, нужно в худшем случае пройти все 10000 узлов. Поэтому поиск занимает ~0.027 секунды независимо от того, как расположены данные. Вставка тоже требует прохода до конца списка, что даёт ~4.4 секунды на 10000 элементов.

Как удаление работает в каждой структуре

Удаление тесно связано с поиском, потому что сначала нужно найти удаляемый элемент. Поэтому время удаления коррелирует со временем поиска:

  • В связном списке удаление занимает ~0.012 секунды примерно половину времени поиска (0.027 с), так как операция перелинковки дёшева.
  • В хеш‑таблице удаление (~0.0016 с) близко ко времени поиска (~0.004 с), опять же с поправкой на перелинковку в списке корзины.
  • В BST на случайных данных удаление (~0.00017 с) даже быстрее поиска (~0.00024 с) из-за особенностей рекурсивной реализации.
  • В BST на отсортированных данных удаление (~0.057 с) занимает примерно половину времени поиска (~0.091 с) та же закономерность, что и у списка, потому что вырожденное дерево ведёт себя как список.

Выводы

Какую структуру и для каких задач стоит выбирать в реальной жизни?

  1. Хеш‑таблица оптимальный выбор для подавляющего большинства сценариев, где нужен быстрый доступ по ключу (словари, кэши, индексы в базах данных). Она стабильна, предсказуема и не зависит от порядка данных. В моём эксперименте она уступила BST на случайных данных, но выиграла у BST на отсортированных и оказалась намного быстрее связного списка. Главный минус отсутствие естественного порядка при обходе.

  2. Сбалансированное дерево (AVL или красно-чёрное) выбор, когда нужны оба свойства: быстрый доступ (O(log n)) и возможность получать данные в отсортированном порядке без дополнительной сортировки. Обычный BST (как в моей реализации) использовать не стоит, если нельзя гарантировать случайный порядок входных данных. Деградация на упорядоченных данных делает его непригодным для реальных систем.

  3. Связный список практически бесполезен для хранения больших объёмов данных. Единственное оправданное применение очень маленькие коллекции (до сотни элементов), реализация очередей/стеков или учебные цели.

Рекомендация: если нужен только быстрый доступ по ключу берите хеш-таблицу. Если нужен отсортированный вывод и вы готовы пожертвовать небольшой долей производительности используйте сбалансированное дерево. От наивного BST и связного списка в реальных проектах лучше отказаться.

Ключевой вывод эксперимента: порядок поступления данных критически важен для производительности BST (разница в 400 раз между случайными и отсортированными данными), но почти не влияет на хеш-таблицу и связный список (хотя последний всегда медленный).