ВНИМАНИЕ Для заказа программы на двоичное поисковое дерево пишите на мой электронный адрес proglabs@mail.ru

Операция удаления вершины из двоичного дерева по сравнению с операциями вставки, поиска и обхода наиболее сложная. Правда есть еще такая сложная операция, как балансировка дерева, но она не является стандартной для двоичного дерева поиска. Балансировку проводят в AVL-деревьях.

Хочу показать анонимное поисковое дерево ( без привязки к ключам ), состоящее из $10$ вершин.анонимное поисковое двоичное дерево

💡 При удалении очень важно следить за статусом удаляемой вершины. Любая вершина в любом бинарном дереве имеет один из трех статусов:

# Статус Количество детей Примечание
1 Пустая ( лист ) $0$ Левый и правый указатель указывают в NULL
2 Половинчатая $1$ Левый или правый указатель указывает в NULL, но не оба одновременно
3 Полная $2$ Левый и правый указатель не указывают в NULL

Посмотрим на листовые узлы анонимного поискового двоичного дерева ( они выделены красным цветом ):

листовые узлы анонимного бинарного дерева поиска

Рисунок — листовые узлы бинарного дерева поиска

Также рассмотрим половинчатые узлы дерева ( они выделены синим цветом ):

половинчатые узлы бинарного дерева

Рисунок — половинчатые узлы бинарного дерева поиска

И осталось рассмотреть полные узлы дерева ( они выделены цветом морской волны ). Каждый полный узел имеет двух сыновей.

полные узлы бинарного дерева поиска

Рисунок — полные узлы бинарного дерева поиска

А сейчас я хочу посмотреть статусы всех узлов на одном двоичном дереве поиска ( напомню: красные — листья; синие — неполные; морской волны — полные узлы ):

статусы узлов двоичного дерева поиска

Рисунок — статусы узлов бинарного дерева поиска

Очевидно, что нужна специальная программная функция, которая определяет статус заданного узла двоичного дерева поиска. У меня получилась следующая функция:

Дополнительно мне пришлось описать перечисление следующего вида:

Показывать удаление вершин буду на этом двоичном дереве поиска:

двоичное дерево поиска для демонстрации удаления

Рисунок — бинарное дерево для демонстрации операции удаления

Например, требуется удалить вершину с ключом $35$.

удаление листовой вершины из бинарного дерева

Рисунок — удаление вершины с ключом $35$

В первую очередь надо определить статус удаляемой вершины. Напомню, что вершина может быть пустой ( листовой ), половинчатой или полной. Очевидно, что удаляемая вершина является листовой, так как она не имеет сыновей.

💡 Удаление листовой вершины в двоичном дереве реализуется наиболее просто и понятно!

Для этого я удаляю эту вершину из дерева ( командой free, если речь про язык программирования «чистый» Си ) и в качестве ответа возвращаю NULL-указатель. В результате удаления узла с ключом $35$ бинарное дерево поиска примет вид:

двоичное дерево после удаления листовой вершины

Рисунок — вид двоичного дерева после удаления вершины с ключом $35$

Сейчас я хочу удалить вершину с ключом $40$ из следующего двоичного поискового дерева:

удаление неполной вершины из бинарного дерева

Рисунок — удаление вершины с ключом $40$

Удаляемая вершина является половинчатой, так как она имеет только одного сына ( левого ) с ключом $30$. Удалять неполные вершины из дерева чуть сложнее, чем листовые, но в целом также несложно.

Алгоритм удаления неполных узлов такой:

рекурсивно «спуститься» и найти удаляемый узел в бинарном дереве поиска;
определить статус удаляемого узла ( очевидно, что удаляемая вершина должна иметь статус «половинчатой» );
удалить память из-под найденной вершины ( например, командой free, если реализация проходит на языке Си );
в качестве результата вернуть указатель на левое поддерево ( если удаляемый узел имел левого сына ), иначе указатель на правое поддерево.

В моем примере удаляемая вершина с ключом $40$ имеет левого сына, значит, в качестве результата функция должна вернуть указатель на левое поддерево. У этого левого поддерева корнем выступает узел с ключом $30$.

В результате удаления элемента с ключом $40$ двоичное дерево поиска примет вид:

двоичное дерево после удаления неполной вершины

Рисунок — вид двоичного дерева после удаления вершины с ключом $40$

А сейчас хочу посмотреть на самый тяжелый вариант удаления элемента из двоичного поискового дерева. Например, надо удалить узел с ключом $60$:

удаление полного узла из двоичного дерева поиска

Рисунок — удаление вершины с ключом $60$

💡 Мало того, что удаляемый узел является корнем заданного дерева, так при этом он является и полным узлом, то есть имеет и левого, и правого сына.

Очевидно, что просто взять и удалить этот узел нельзя, так как после этого оба сына не будут иметь родителя. Дерево будет «порвано» и перестанет быть бинарным деревом поиска, а превратится в какую-то другую динамическую структуру данных.

Поэтому на практике поступают следующим образом:

рекурсивно «спуститься» и найти удаляемый узел в бинарном дереве поиска;
определить статус удаляемого узла ( очевидно, что удаляемая вершина должна иметь статус «полной» );
в левом поддереве найти вершину с максимальным ключом или в правом поддереве найти вершину с минимальным ключом;
договоримся, что на предыдущем этапе нашли в правом поддереве вершину с минимальным ключом и сейчас записываем это минимальное значение на место ключа удаляемой полной вершины;

удаляем из правого поддерева вершину с минимальным ключом.

Звучит данный алгоритм непросто, да и реализуется также непросто!

Итак, моя цель удалить полный узел с ключом $60$, который по совместительству также является и корнем рассматриваемого дерева. Следуя алгоритму, нужно найти в правом поддереве вершину с минимальным значением ключа. Получаю следующую картинку:

поиск наименьшего узла в правом поддереве

Рисунок — поиск узла с минимальным ключом в правом поддереве

Далее я следую алгоритму и записываю значение минимального ключа, то есть число $68$ вместо ключа удаляемой полной вершины, то есть вместо числа $60$. Получаю такое промежуточное состояние бинарного дерева поиска:

копируем ключ минимального элемента в полный удаляемый узел

Рисунок — копируем ключ минимального элемента в полный удаляемый узел

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

  • листовая ( не имеет детей );
  • неполной ( имеет правого сына ).

Никаких других статусов вершина с минимальным ключом иметь не может. Если бы она имела левого сына, это бы означало, что она сама не является минимальной.

запускаем удаление вершины с минимальным ключом из правого поддерева

Рисунок — запускаем удаление вершины с минимальным ключом из правого поддерева

В результате удаления узла с ключом $60$ бинарное дерево поиска примет вид:

вид двоичного дерева после удаления вершины с ключом 60

Рисунок — вид двоичного дерева после удаления вершины с ключом $60$

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

А это моя программная реализация на языке «чистый» Си рекурсивной функции удаления элемента из бинарного дерева поиска:

Из функции Remove_node вызывается вспомогательная функция Find_min_node. Ее реализацию я оставляю для вашей работы 🙂 

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

ВНИМАНИЕ Для заказа программы на двоичное дерево поиска пишите на мой электронный адрес proglabs@mail.ru