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

Бинарное дерево поиска – структура данных, позволяющая производить поиск элемента без обхода всего множества элементов, хранимых в дереве. Среднее время добавления и среднее время поиска при этом пропорциональны логарифму количества элементов в дереве.

Алгебраически мы можем построить дерево следующим образом:

Дерево = Пустое_дерево
или
Дерево = Узел(Значение, Дерево, Дерево)

То есть, либо дерево является пустым, либо, дерево создаётся из значения хранимого в узле, и двух поддеревьев, которые мы будем называть левым и правым.

Пример различных деревьев на рисунке.

_images/bintree1.png

Определить дерево мы можем следующим образом:

struct node {
    int value;
    struct node *left;
    struct node *right;
};

Хорошо, мы построили некоторую структуру данных, пусть это даже будет сложный тип данных под названием “бинарное дерево”. Однако, нам всё ещё рано гордиться собой, так как абсолютно непонятно, какие задачи мы будем с её помощью решать.

А решать мы будет задачу быстрого поиска в некотором множестве элементов, которые можно сравнивать с помощью операции “больше”. Мы уже на самом деле это делали раньше. Ведь если у нас есть некоторый массив элементов, и эти элементы мы можем сравнивать между собой с помощью оператора “больше”, то мы можем отсортировать такой массив. А в отсортированном массиве мы уже можем искать элементы со скоростью логарифм от числа элементов в массиве. Однако, вставка в нового элемента в случайное место массива в среднем всё равно будет занимать время, пропорциональное длине массива. Разрешить эту проблему нам поможет бинарное дерева. За счёт того, что в каждом узле дерева мы будет хранить некоторый элемента, в левом поддереве все элементы меньше него, а в правом – большие его, то при условии, что элементы распределены по поддеревьям более или менее равномерно, скорость и поиска, и добавления будет логарифмической. Если хотите, можете доказать это.

Покажем это на примере дерева, в котором мы будем хранить, сколько раз какое слово встречилось в обрабатываемом тексте. Текст для обработки будет следующим:

bee dog cat ant cat bird

Состояние дерева после добавления новых элементов показано на рисунке:

_images/bintree2.png

На следующем рисунке показано добавление слова “bird”. Обратите внимание, что несмотря на то, что “bird” и “bee” стоят в дереве достаточно близко, “bird” всё равно будет располагаться от него достаточно далеко. Порядок расположения слов в бинарном дереве зависит от того, в каком порядке они добавлялись.

_images/bintree3.png

Если ещё раз вернуться глазами к алгебраическому определению типов и картинке с изображением бинарного дерева, можно явно увидеть, что работа с таким типом данных предполагает рекурсию. Ну действительно, мы обрабатываем дерево вызовом функции, а поддерево – рекурсивным вызовом этой же функции. Терминальные ветви функции у нас будут отвечать за то, чтобы ничего не делать с NULL, то есть отсутствием поддеревьев.

  • Создание дерева Здесь всё просто. Пустое дерево – это просто NULL.
  • Добавление элемента. Если добавляемый элемент больше элемента в узле, то возможны два варианта: пустое правое поддерево и непустое правое поддерево. Если правое поддерево пусто, добавляем вместо него узел с двумя пустыми поддеревьями. Если правое поддерево непусто, вызываем функцию добавления на нём рекурсивно. Если добавляемый элемент меньше элемента в узле, то для левого поддерева аналогично.
  • Поиск элемента. Если искомый элемент равен элементу в узле, то узел найден. Если меньше, продолжаем поиск в правом поддереве. Если больше, то в правом.