Удалить в бинарном дереве поиска? - PullRequest
0 голосов
/ 30 июня 2010

Я читаю алгоритм удаления узла двоичного дерева, используемый в книге Структуры данных и алгоритмы: аннотированная ссылка с примерами

на стр. 34, случай 4 (Удалить узел, который имеет как правое, так и левое поддеревья), следующий алгоритм, описанный в книге, выглядит не сработавшим, возможно, я ошибаюсь, если кто-то поможет мне в том, что мне не хватает.

//Case 4
get largestValue from nodeToRemove.Left
FindParent(largestValue).Right <- 0
nodeToRemove.Value<-largestValue.Value

Как следующая строка удаляет наибольшее значение из поддерева FindParent(largestValue).Right <- 0

Ответы [ 5 ]

6 голосов
/ 30 июня 2010

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

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

EDIT

Прочитав ваш вопрос немного больше, я думаю, что нашел проблему.

Обычно, помимо функции delete, у вас есть функция replace, которая заменяет рассматриваемый узел. Я думаю, вам нужно изменить эту строку кода:

FindParent(largestValue).Right <- 0

до:

FindParent(largestValue).Right <- largestValue.Left

Если у узла largestValue нет левого потомка, вы просто получаете null или 0. Если у него есть левый дочерний элемент, этот дочерний элемент становится заменой для узла largestValue. Так ты прав; код не учитывает сценарий, что у узла largestValue может быть левый потомок.

Другая редакция

Поскольку вы только опубликовали фрагмент, я не уверен, каков контекст кода. Но приведенный фрагмент, похоже, имеет проблему, которую вы предлагаете (замена неправильного узла). Обычно есть три случая, но я замечаю, что комментарий в вашем фрагменте говорит //Case 4 (так что, возможно, есть какой-то другой контекст).

Ранее я упоминал о том, что delete обычно идет с replace. Поэтому, если вы найдете узел largestValue, вы удалите его в соответствии с двумя простыми случаями (узел без дочерних элементов и узел с одним дочерним элементом). Поэтому, если вы смотрите на псевдокод для удаления узла с двумя дочерними элементами, вы будете делать следующее:

get largestValue from nodeToRemove.Left
nodeToRemove.Value <- largestValue.Value

//now replace largestValue with largestValue.Left    

if largestValue = largestValue.Parent.Left then   
   largestValue.Parent.Left <- largestValue.Left //is largestValue a left child?
else //largestValue must be a right child
   largestValue.Parent.Right <- largestValue.Left

if largestValue.Left is not null then
   largestValue.Left.Parent <- largestValue.Parent

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

Чтобы доказать, что приведенный выше код работает, рассмотрим следующее дерево:

  8
 / \
7   9

Допустим, вы хотите удалить 8. Вы пытаетесь найти largestValue из nodeToRemove.Left. Это дает вам 7, поскольку у левого поддерева есть только один дочерний элемент.

Тогда вы делаете:

nodeToRemove.Value <- largestValue.Value

Что означает:

8.value <- 7.Value

или

8.Value <- 7

Итак, теперь ваше дерево выглядит так:

  7
 / \
7   9

Вам нужно избавиться от узла замены, и вы собираетесь заменить largestValue на largestValue.Left (что составляет null). Итак, сначала вы узнаете, что за ребенок 7:

if largestValue = largestValue.Parent.Left then

Что означает:

if 7 = 7.Parent.Left then

или

if 7 = 8.Left then

Поскольку 7 является 8 левым ребенком, необходимо заменить 8.Left на 7.Right (largestValue.Parent.Left <- largestValue.Left). Так как 7 не имеет детей, 7.Left является нулевым. Таким образом, largestValue.Parent.Left получает значение null (что эффективно удаляет его левого потомка). Таким образом, это означает, что вы получите следующее дерево:

  7
   \
    9
1 голос
/ 30 июня 2010

Я думаю, вам может понадобиться уточнить, что не работает.

Я попытаюсь объяснить концепцию удаления в двоичном дереве, если это поможет.

Предположим, что у вас есть узел в дереве, который имеет два дочерних узла, которые вы хотите удалить. в дереве ниже скажем, что вы хотите удалить узел b
а
/ \
б с
/ \ / \
д е ф г

Когда мы удаляем узел, нам нужно заново присоединить его зависимые узлы.

т. Когда мы удаляем b, нам нужно присоединить узлы d и e.

Мы знаем, что левые узлы меньше, чем правые узлы по значению, и что родительские узлы находятся между левым и правым узлами s по значению. В этом случае d Что немного менее очевидно, так это то, что e

Как уже говорилось ранее, d

Удаление завершено.

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


а
/ \
д с
\ / \
е е г

Обратите внимание, что существует другой вполне законный результат удаления узла b. Если бы мы решили продвигать узел d вместо узла e, дерево выглядело бы так:


а
/ \
е с
/ / \
д ф г

1 голос
/ 30 июня 2010

Если я понимаю псевдокод, он работает в общем случае, но не работает в случае «один узел в левом поддереве». Хороший улов.

Он эффективно заменяет node_to_remove на самое большое_значение из своего левого поддерева (также обнуляет старый узел large_value).

Обратите внимание, что в BST левое поддерево node_to_remove будет все меньше, чем node_to_remove. Правое поддерево node_to_remove будет больше, чем node_to_remove. Поэтому, если вы возьмете самый большой узел в левом поддереве, он сохранит инвариант.

Если это «один узел в случае поддерева», он уничтожит правильное поддерево. Хромой: (* ​​1007 *

Как указывает Вивин, он также не может присоединить оставленные дочерние элементы самого большого узла.

1 голос
/ 30 июня 2010

Идея состоит в том, чтобы просто взять значение из наибольшего узла с левой стороны и переместить его в удаляемый узел, то есть вообще не удалять узел, просто заменить его содержимое. Затем вы удаляете узел со значением, которое вы переместили в «удаленный» узел. Это поддерживает порядок дерева со значением каждого узла, большим, чем все его левые дочерние элементы, и меньшим, чем все его правые дочерние элементы.

0 голосов
/ 30 июня 2010

Это может иметь больше смысла, когда вы смотрите на Википедию , взявшую эту часть алгоритма:

Удаление узла с двумя дочерними элементами : Назовите удаляемый узел "N". Делать не удаляйте N. Вместо этого выберите либо его преемник узел в порядке или его по порядку узел-предшественник, "R". Заменить значение N на значение R, затем удалите R. (Примечание: сам R имеет до одного ребенка.)

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

Редактировать: кажется, что отсутствует возможность того, что у R (если использовать терминологию Википедии) есть один ребенок. Рекурсивное удаление может работать лучше.

...