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

## Цель работы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

**На случайных данных** BST показал выдающуюся производительность. Вставка всех 10 000 записей заняла всего **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)). Однако на отсортированных данных каждый новый элемент становится самым большим и добавляется только в правую ветку. В результате дерево превращается в односвязный список высотой 10 000 узлов, а сложность всех операций деградирует до линейной O(n). Это подтверждается цифрами: время вставки выросло с 0.025 до 10.15 секунд – разница в 406 раз.

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

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

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

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

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

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

- В связном списке удаление занимает ~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 раз между случайными и отсортированными данными), но почти не влияет на хеш-таблицу и связный список (хотя последний всегда медленный).
