Открыть сервис

Бинарное дерево поиска

Бинарное дерево поиска (англ. binary search tree, BST) — это структура данных, представляющая собой корневое двоичное дерево, в котором для каждого узла выполняется свойство упорядоченности: все ключи в левом поддереве узла меньше ключа этого узла, а все ключи в правом поддереве — больше (или равны в зависимости от реализации). Данная структура обеспечивает эффективный поиск, вставку и удаление элементов, в среднем за время O(log n), где n — количество узлов.

Основные свойства

Ключевой особенностью бинарного дерева поиска является инвариант, накладываемый на значения узлов. Для любого узла с ключом \( K \):

Допущение относительно равенства ключей варьируется: в большинстве реализаций дубликаты не допускаются, что делает дерево «строгим». В некоторых вариантах допускается хранение повторяющихся ключей (обычно в одном узле или в одном из поддеревьев). Отсутствие дубликатов упрощает алгоритмы и гарантирует уникальность ключей.

Каждый узел дерева, как правило, содержит три поля: ключ (или пару «ключ — значение» для ассоциативных массивов), указатель на левого потомка, указатель на правого потомка. В расширенных реализациях также может храниться ссылка на родительский узел для упрощения итераций.

История

Идея упорядоченных бинарных деревьев восходит к работам конца 1950-х — начала 1960-х годов. В 1960 году британский учёный Тони Хоар опубликовал алгоритм быстрой сортировки, который использует принцип разбиения массива на две части относительно опорного элемента — аналогично тому, как устроено BST. Однако само понятие структуры «бинарное дерево поиска» и её алгоритмы были формализованы в 1962 году американскими учёными Робертом Седжвиком и Дональдом Кнутом в рамках анализа алгоритмов сортировки и поиска. В 1970-е годы появилась концепция сбалансированных BST (АВЛ-деревья, красно-чёрные деревья), что устранило проблему вырождения в линейный список.

Основные операции

Поиск

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

Рекурсия заканчивается, если достигается пустой узел (null или nil) — тогда элемента в дереве нет.

Временная сложность: в среднем \( O(\log n) \), в худшем случае (дерево вырождено в линейный список) — \( O(n) \).

Вставка

Операция вставки также начинается от корня. Сравнивая новый ключ с ключом текущего узла, алгоритм выбирает направление (левое или правое поддерево). Если в выбранном направлении есть потомок — рекурсивно переходит к нему. Если потомка нет — новый узел создаётся и подвешивается в соответствующую ветвь. При строгом BST с уникальными ключами при обнаружении дубликата обычно выбрасывается исключение или возвращается ошибка.

Временная сложность: аналогична поиску — \( O(\log n) \) в среднем, \( O(n) \) в худшем случае.

Удаление

Удаление узла — наиболее сложная операция, так как необходимо сохранить свойство упорядоченности. Рассматриваются три случая:

  1. Узел — лист (нет потомков). Просто удаляется — родительский указатель (или корень) устанавливается в null.
  2. Узел имеет только одного потомка. Удаляемый узел заменяется его потомком (левым или правым). Если удаляется корень — корнем становится единственный потомок.
  3. Узел имеет обоих потомков. В этом случае на место удаляемого узла помещается его преемник (наименьший ключ в правом поддереве) или предшественник (наибольший ключ в левом поддереве). После этого необходимо рекурсивно удалить преемника или предшественника, который гарантированно находится в одном из двух простых случаев (лист или узел с одним потомком).

Все операции сравнения выполняются над ключами, поэтому критически важно, чтобы ключи поддерживали операцию сравнения (например, целые числа, строки в лексикографическом порядке).

Обходы (Traversals)

Для работы с BST используются три основных способа обхода узлов:

Центрированный обход часто используется для сортировки данных, хранящихся в BST — достаточно построить дерево из входных данных и выполнить in-order обход.

Сложность и вырождение

Идеальное бинарное дерево поиска — сбалансированное, когда высота дерева минимально возможна (\( \approx \log_2 n \)). Однако при последовательной вставке уже отсортированных данных дерево вырождается в линейный односвязный список (все новые узлы добавляются только вправо или только влево). В этом случае все операции становятся \( O(n) \), что неприемлемо для больших наборов данных.

Для решения проблемы вырождения были разработаны самобалансирующиеся структуры на основе BST, например АВЛ-деревья и красно-чёрные деревья. Они автоматически перестраивают дерево после вставок и удалений, поддерживая высоту порядка \( O(\log n) \).

Сбалансированные варианты

АВЛ-дерево

Изобретено в 1962 году советскими математиками Георгием Адельсоном-Вельским и Евгением Ландисом. Для каждого узла хранится разность высот левого и правого поддеревьев (коэффициент сбалансированности), которая не должна превышать 1 по модулю. При нарушении баланса выполняются повороты — перестановки узлов, восстанавливающие свойство. АВЛ-деревья обеспечивают строгую сбалансированность, но требуют дополнительных затрат памяти на хранение высоты и более сложных операций восстановления.

Красно-чёрное дерево

Разработано в 1972 году Рудольфом Байером (первоначально как «симметричные бинарные B-деревья»). В каждом узле хранится дополнительный атрибут — цвет (красный или чёрный). Правила дерева гарантируют, что ни один путь от корня до листа не более чем вдвое длиннее любого другого, что обеспечивает логарифмическую высоту. Красно-чёрные деревья используются в стандартной библиотеке C++ (std::map, std::set), в Java (TreeMap, TreeSet) и в ядре Linux для управления памятью.

Декартово дерево (Treap)

Гибрид BST и кучи. Каждому узлу присваивается случайный приоритет; дерево строится так, что по ключам оно является BST, а по приоритетам — кучи (max-heap или min-heap). Это обеспечивает ожидаемую логарифмическую высоту при любой последовательности вставок. Treap прост в реализации и широко применяется в задачах олимпиадного программирования.

Применение

Бинарные деревья поиска (в том числе сбалансированные) используются во многих областях:

Недостатки

Альтернативы

В случаях, когда критична скорость поиска (O(1)) и не нужны операции с упорядоченными данными, чаще применяются хеш-таблицы. Для работы с большими объёмами данных на диске используются B-деревья и их производные. Для поддержки эффективной вставки, удаления и поиска в условиях жёстких требований к времени используют сбалансированные BST. Выбор структуры данных зависит от конкретной задачи: если часто требуются операции поиска по диапазону или упорядоченный вывод — BST предпочтительнее хеш-таблиц.

Интересные факты

Источники

BFOmetr — база данных и аналитика по компаниям России.

На главную BFOmetr →