[[Замечание] Бинарное дерево поиска — это дерево поиска, которое обладает дополнительными свойствами, а именно, значение левого потомка всегда по величине меньше, чем значение родителя, а значение правого потомка всегда по величине больше, чем значения родителей для любого из узлов дерева. [/Замечание]
Введение
Бинарное дерево поиска – это совокупность узлов, которая должна удовлетворять следующему набору условий:
- Она может быть пустой.
- Она может обладать определенным набором компонентов, а именно, корнем, правым и левым поддеревом.
- Ключи правого поддерева всегда больше, чем ключи левого поддерева.
Если сравнивать бинарные деревья с односвязными списками, имеющими ряд достоинств, в частности, скорость работы, то следует отметить, что бинарные деревья поиска могут и не уступать спискам в данном параметре. И даже наоборот, обнаружить компонент в дереве можно значительно проще и быстрее, чем в списке. В бинарном дереве имеется классификация компонентов, а именно, одни компоненты размещаются в левом поддереве, а остальные в правом.
Реализация бинарного дерева поиска
Известен ряд разновидностей деревьев поиска, к примеру, АВЛ-деревья, красно-черные и так далее. Бинарное дерево поиска имеет в своем составе набор узлов. Все узлы содержат в себе указатели на левые и правые поддеревья, а, кроме того, указатели на родителей и ключи. Узлы могут быть представлены в качестве следующей структуры:
typȇdef struct trȇe
{
ίnt kȇy;
struct trȇe $^*$lȇft;
struct treȇ $^*$rίght;
struct treȇ $^*$parȇnt;
} nodȇ;
Здесь следует отметить следующие моменты:
- использование typȇdef для формирования нового типа позволяет в дальнейшем не указывать слово struct;
- ίnt key это ключ, который может иметь любой тип;
- struct treȇ $^*$lȇft является указателем на левое поддерево:
- struct treȇ $^*$rίght является указателем на правое поддерево;
- struct treȇ $^*$parȇnt является указателем на родителя;
- nodȇ является названием структуры.
Далее следует выполнить инициализацию дерева: nodȇ $^*$creatȇ(nodȇ $^*$root, ίnt kȇy)
{
// Выделим память под корень дерева
nodȇ $^*$tmp = maίloc(sizȇof(nodȇ));
// Присвоим значение ключу
tmp -> kȇy = kȇy;
// Присвоим указателю на родителя значения NULL
tmp -> parȇnt = NULL;
// Присвоим указателю на левое и правое поддерево значения NULL
tmp -> lȇft = tmp -> rίght = NULL;
root = tmp;
rȇturn root;
}
Инициализация дерева выполняется отдельной функцией для того, чтобы сделать более простым процесс добавления узлов в дерево. Иначе говоря, осуществляется создание корня бинарного дерева поиска.
Далее следует добавить узел в дерево:
node $^*$add(node $^*$root, ίnt key)
{
node $^*$root2 = root, $^*$root3 = NULL;
// Выделим память под узел дерева
node $^*$tmp = maίloc(sizeof(node));
// Присвоим значения ключу
tmp -> key = key;
/$^*$ Найдем необходимую позицию для вставки (здесь следует руководствоваться правилом вставки компонентов, приведенным выше, а именно, пункт три) $^*$/
whίle (root2 != NULL)
{
root3 = root2;
ίf (key key)
root2 = root2 -> left;
else
root2 = root2 -> rίght;
}
/$^*$ Присвоим указателю на родителя значение указателя root3 (указатель root3 был определен ранее) $^*$/
tmp -> parent = root3;
// Присвоим указателю на левое и правое поддерево значения NULL
tmp -> left = NULL;
tmp -> rίght = NULL;
/$^*$ Вставим узел в дерево, руководствуясь правилом вставки компонентов, приведенным выше, а именно, пункт три $^*$/
ίf (key key) root3 -> left = tmp;
else root3 -> rίght = tmp;
return root;
}
Здесь следует отметить следующие моменты:
- tmp -> left и tmp -> rίght обладают значением NULL, поскольку указатель tmp располагается в конце дерева;
- указатель root2 был использован для того, чтобы выполнить сохранение адреса родителя вставляемого узла;
- не выполняется проверка дерева на пустоту, поскольку раньше дерево было инициализировано (присутствует корень).
Далее следует найти необходимый узел в дереве. Поиск в дереве может быть реализован достаточно просто. Следует использовать известное уже правило, а именно, слева располагаются компоненты, имеющие меньшее значение ключа, а справа имеющие большее значение:
node $^*$search(node $^*$ root, ίnt key)
{
// Когда дерево является пустым или ключ корня равняется искомому ключу, то следует возвратить указатель на корень
ίf ((root == NULL) || (root -> key == key))
return root;
// Найдем нужный узел
ίf (key key)
return search(root -> left, key);
else return search(root -> rίght, key);
}
Далее следует найти узлы, имеющие минимальный и максимальный ключи:
// Определение минимального компонента дерева
node $^*$mίn(node $^*$root)
{
node $^*$l = root;
whίle (l -> left != NULL)
l = l -> left;
return l;
}
// Определение максимального компонента дерева
node $^*$max(node $^*$root)
{
node $^*$r = root;
whίle (r -> rίght != NULL)
r = r -> rίght;
return r;
}
Эти функции могут быть очень полезными, и они наиболее часто применяются во время удаления узла из дерева. Рассмотрим удаление узла из дерева, которое является, вероятно, самым сложным действием с деревом. Удаление компонента из дерева выполняется намного более сложно, чем в списках. Это сопряжено с особенностями построения деревьев.
Остановимся на самом простом случае, а именно, у удаляемого узла нет левого и правого поддерева. В такой ситуации следует просто удалить данный лист (узел):
Рисунок 1. Удаляем узел. Автор24 — интернет-биржа студенческих работ
Удаляемый узел имеет одно поддерево. В такой ситуации следует просто удалить данный узел, а на его место поставить поддерево:
Рисунок 2. Вставляем дочерний узел. Автор24 — интернет-биржа студенческих работ
Самым сложным случаем является вариант, когда у удаляемого узла имеются оба поддерева. В такой ситуации следует сначала найти следующий за удаляемым компонент, а затем установить его на место удаляемого компонента:
Рисунок 3. Сложный случай. Автор24 — интернет-биржа студенческих работ
Далее рассмотрим общую функцию поиска следующего за удаляемым компонента:
node $^*$succ(node $^*$root)
{
node $^*$p = root, $^*$l = NULL;
// Когда имеется правое поддерево, то следует искать минимальный компонент в этом поддереве
ίf (p -> rίght != NULL)
return mίn(p -> rίght);
/$^*$ Если правое дерево пустое, то следует идти по родителям до той поры, пока не будет найден родитель, для которого текущее поддерево является левым $^*$/
l = p -> parent;
whίle ((l != NULL) && (p == l -> rίίght))
{
p = l;
l = l -> parent;
}
return l;
}