Деревья поиска. Организация: свойство упорядоченности и поиск за O(log N)

Модуль "Деревья поиска" • урок 1 из 8 • ~6 минут

Суть урока. Дерево поиска (BST) - двоичное дерево, где для каждого узла все ключи левого поддерева меньше, а правого - больше его ключа. Поиск спускается от корня, на каждом шаге отбрасывая половину вариантов, что дает время O(log N) в сбалансированном случае (иначе O(N)). Симметричный обход (ЛКП) такого дерева дает отсортированную последовательность элементов.

Деревья поиска. Организация: правило размещения ключей

Двоичные деревья часто употребляются для представления множеств данных, в которых необходимо осуществлять быстрый поиск элементов по ключу. Если дерево организовано таким образом, что для любой его вершины выполняется следующее жесткое правило: все ключи в ее левом поддереве строго меньше ее ключа, а все ключи в правом поддереве строго больше ее ключа, то такое дерево называется деревом поиска (Binary Search Tree, BST).

Главным преимуществом такой организации является существенное сокращение времени поиска по сравнению с линейными структурами (списками). На каждом шаге поиска, сравнивая искомый ключ с ключом текущего узла, алгоритм переходит либо в левое, либо в правое поддерево, автоматически отбрасывая половину оставшихся вариантов. В идеально сбалансированном дереве максимальное количество сравнений не превышает его высоты, которая пропорциональна log2(N), где N - количество элементов. Таким образом, время поиска значительно сокращается: O(log N) вместо O(N).

Поиск в дереве (алгоритм)

Процедура поиска в таком дереве чрезвычайно проста и не требует применения рекурсии (хотя рекурсивная реализация также возможна). Поиск начинается от корня и продвигается вниз по однозначно выбираемому пути, реализуясь одним циклом while.

struct node {
    int key;
    struct node* l;
    struct node* r;
};

/* Итеративный поиск в дереве */
struct node* locate(int x, struct node* t) {
    while (t != NULL && t->key != x) {
        if (x < t->key) {
            t = t->l; /* Идем в левое поддерево */
        } else {
            t = t->r; /* Идем в правое поддерево */
        }
    }
    return t; /* Вернет NULL, если элемент не найден */
}
Поиск ключа 35 в дереве поиска: спуск от корня 50 влево к вершине 25, затем вправо к вершине 35
Три сравнения вместо семи: каждый шаг отбрасывает целую ветвь

Визуализация организации дерева поиска

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

Деревья поиска. Организация: в левом поддереве корня 50 лежат только меньшие ключи 25, 10 и 35, а в правом только большие 75, 60 и 90
Главное правило дерева поиска: слева от узла только меньшие ключи, справа только большие
А как в Python? Деревья поиска заложены в основу многих структур в других языках (например, std::set и std::map в C++). В стандартной библиотеке Python нет прямой реализации двоичного дерева поиска. Ассоциативные массивы (словари dict) и множества (set) реализованы на основе хеш-таблиц, что обеспечивает константное время поиска O(1), но не сохраняет сортировку элементов. Если требуется именно упорядоченная древовидная структура, используют сторонние модули (такие как bintrees) или пишут свой класс дерева.

Коротко: главное

  • Дерево поиска - это двоичное дерево, в котором ключи левого поддерева всегда меньше ключа узла, а ключи правого - больше.
  • Такое свойство обеспечивает логарифмическое время поиска O(log N) в сбалансированном случае, так как на каждом шаге отбрасывается половина дерева.
  • Поиск осуществляется простым итеративным спуском от корня, без необходимости использовать рекурсию.

Проверьте себя

За счет чего достигается алгоритмическая сложность O(log N) при поиске элемента в сбалансированном дереве поиска?

Как вам урок? Спасибо за отзыв!