【问题标题】:Delete in Binary Search tree?在二叉搜索树中删除?
【发布时间】:2010-06-29 20:09:10
【问题描述】:

我正在阅读本书Data Structures and Algorithms: Annotated Reference with Examples中使用的二叉树删除节点算法

在第 34 页,案例 4(删除具有左右子树的节点),书中描述的以下算法看起来不起作用,可能我可能错了,有人可以帮我解决我所缺少的。

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

下面这行如何删除子树FindParent(largestValue).Right &lt;- 0中的最大值

【问题讨论】:

  • 我猜没有明确删除节点largetstValue,可能他们希望在重置指向该节点的唯一指针(来自该节点的父节点的Right链接)时删除(通过垃圾收集器)到0

标签: algorithm data-structures tree binary-search-tree


【解决方案1】:

删除具有两个子节点的节点时,您可以选择其有序后继节点或有序前驱节点。在这种情况下,它在左子树(即其左子树的最右子树)中查找最大值,这意味着它正在查找该节点的有序前驱节点。

一旦找到替换节点,您实际上并没有删除要删除的节点。相反,您从后继节点获取值并将该值存储在要删除的节点中。然后,您删除后继节点。这样做可以保留二叉搜索树属性,因为您可以确定您选择的节点的值将低于原始节点左子树中所有子节点的值,并且大于该值原始节点的右子树中的所有子节点。

编辑

在阅读了你的问题之后,我想我已经找到了问题。

通常,除了 delete 函数之外,您还有一个 replace 函数来替换相关节点。我认为您需要更改这行代码:

FindParent(largestValue).Right <- 0

到:

FindParent(largestValue).Right <- largestValue.Left

如果largestValue 节点没有左子节点,您只需获得null0。如果它确实有一个左孩子,则该孩子将成为largestValue 节点的替代品。所以你是对的;代码没有考虑largestValue节点可能有左孩子的场景。

另一个编辑

由于您只发布了一个 sn-p,我不确定代码的上下文是什么。但是发布的 sn-p 似乎确实存在您建议的问题(替换错误的节点)。通常情况下,有三种情况,但我注意到您的 sn-p 中的评论说//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。您尝试从nodeToRemove.Left 中找到largestValue。这给了你7,因为左子树只有一个孩子。

然后你做:

nodeToRemove.Value <- largestValue.Value

这意味着:

8.value <- 7.Value

8.Value <- 7

所以现在你的树看起来像这样:

  7
 / \
7   9

您需要摆脱替换节点,因此您将用largestValue.Left(即null)替换largestValue。所以首先你要知道7是个什么样的孩子:

if largestValue = largestValue.Parent.Left then

这意味着:

if 7 = 7.Parent.Left then

或:

if 7 = 8.Left then

由于78的左孩子,需要将8.Left替换为7.RightlargestValue.Parent.Left &lt;- largestValue.Left)。由于7 没有孩子,7.Left 为空。所以 largestValue.Parent.Left 被分配给 null (这有效地删除了它的左孩子)。所以这意味着你最终会得到以下树:

  7
   \
    9

【讨论】:

  • 我认为您的解决方案仍然无法处理 maximumValue 与 nodeToRemove.Left 相同的情况
  • @Kathy 是的,我追踪了代码并得出了相同的结论。就目前而言,问题中的代码替换了错误的节点。我已经更新了我的解决方案。问题中的代码可能正在解决特定情况。
【解决方案2】:

这个想法是简单地从左侧最大的节点中取出值并将其移动到正在删除的节点,即根本不删除节点,只需替换它的内容。然后用移入“已删除”节点的值修剪掉节点。这保持了树的顺序,每个节点的值都大于它的所有左子节点并且小于它所有的右子节点。

【讨论】:

  • 如果左子树只有一个节点怎么办? FindParent(largestValue).Right &lt;- 0不会删错节点吗?
  • @Passionate 程序员——您当然必须确保父节点与被删除的节点不同。希望他们在文中提到了这一点——我没有副本所以我不知道。
【解决方案3】:

我认为您可能需要澄清什么不起作用。

如果有帮助,我将尝试解释二叉树中删除的概念。

假设您在树中有一个节点,该节点有两个要删除的子节点。 在下面的树中,假设您要删除节点 b
一个
/     \
b      c
/   \     /  \
d    e   f    g

当我们删除一个节点时,我们需要重新附加它的依赖节点。

即。当我们删除 b 时,我们需要重新连接节点 d 和 e。

我们知道左节点的值小于右节点的值,父节点的值在左右节点之间。在这种情况下,d

稍微不那么明显的是e

如前所述 d

删除现已完成。

(顺便说一句,将节点向上移动并以这种方式重新排列依赖节点的过程称为提升节点。您也可以提升节点而不删除其他节点。)


一个
/     \
d      c
\     /  \
e                                                                                                                       =  

请注意,删除节点 b 还有另一个完全合法的结果。 如果我们选择提升节点 d 而不是节点 e,那么树将如下所示。


一个
/     \
e      c
/        /  \
d       f    g

【讨论】:

    【解决方案4】:

    如果我理解伪代码,它在一般情况下有效,但在“左子树中的一个节点”情况下失败。不错的收获。

    它有效地将 node_to_remove 替换为它左子树中的最大值(也使旧的最大值节点为空)。

    请注意,在 BST 中,node_to_remove 的左子树都将小于 node_to_remove。 node_to_remove 的右子树都将大于 node_to_remove。所以如果你取左子树中最大的节点,它将保留不变量。

    如果这是“子树中的一个节点”,它将破坏右子树。跛脚:(

    正如 Vivin 指出的那样,它也无法重新附加最大节点的左子节点。

    【讨论】:

      【解决方案5】:

      当您查看 Wikipedia's 对算法的那一部分进行处理时,可能会更有意义:

      删除一个有两个孩子的节点: 将要删除的节点称为“N”。做 不删除 N。相反,选择其中一个 它的有序后继节点或其 有序前驱节点“R”。 将 N 的值替换为值 的 R,然后删除 R。(注:R 本身 最多有一个孩子。)

      请注意,给定的算法会选择有序的前驱节点。

      编辑:似乎缺少 R(使用维基百科的术语)有一个孩子的可能性。递归删除可能会更好。

      【讨论】:

      • 如果左子树只有一个节点怎么办? FindParent(largestValue).Right
      • @Vivin 关于似乎缺少什么的讨论指的是原始问题,它确实假设 R 的等价物没有孩子。我指的不是维基百科解决方案中的问题
      • @Passionate 程序员 我建议(隐式)递归删除调用的一个原因是它检查所有可能性,包括左子树只是一个节点(然后将被删除)。递归调用永远不会遇到这种情况,所以它应该很快返回。
      • 我的错误 :) 后来我再次阅读问题后才意识到这一点。删除我原来的评论。
      猜你喜欢
      • 2016-01-08
      • 2017-07-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-23
      • 1970-01-01
      相关资源
      最近更新 更多