在二叉搜索树中搜索数字。如果它不存在,找到比它小的最大数字

时间:2016-02-22 11:42:14

标签: algorithm find binary-search-tree

假设您有一个平衡的二叉搜索树(比如AVL树),其中包含n个数字。

您需要一种能够查找给定数字x的算法,但如果它找不到, 返回小于x的最大数字。

为了问题,不插入x然后删除它,解决方案应该是O(log(n))。

谢谢

1 个答案:

答案 0 :(得分:2)

这个问题是关于找到(虚拟)插入节点的Inorder Predecessor。我将首先解释一下Inorder Predecessor是什么,然后解释我们如何在BST中找到Inorder Predecessor。

在我们讨论Inorder Predecessor之前,我们必须首先熟悉二进制搜索树的Inorder Traversal。 Inorder遍历是一种以特定顺序访问二进制搜索树的所有节点的算法,如下面的递归算法所述:

Inorder(Node S)
  Inorder(left child of S)
  print S
  Inorder(right child of S)

简单来说,我们递归打印左侧树,然后是中心节点,然后是右侧子树。

该算法非常简单而优雅,因为当按照这个顺序打印键时,由于BST结构中键的排序,我们将得到一个特殊的键序列。这个特殊的序列是什么?让我们跟踪BST的算法并检查它。

让我们考虑下面显示的BST:

enter image description here

这里,让我们跟踪我们的算法,首先我们在根(值为20的节点)启动算法,然后算法说我们递归调用左子树的相同函数,这是根的子树在节点8,我们再次调用左子树8,即节点4.此时4没有左子树,所以我们打印键4.由于4没有正确的子树,我们完成了子树以节点4为根,我们回溯到节点8.现在我们打印节点8.然后我们调用节点8的右子树的递归函数,并按照算法一步一步,你会发现打印的序列是:4,8,10,12,14,20,22

这个序列是什么?它只是键的排序顺序。

这是怎么发生的?嗯,它只是BST节点结构的方式:BST有一个特殊属性,即对于每个节点S,节点S左边的所有键都小于S,S右边的所有节点都是大于S.

换句话说,Inorder遍历只是整齐地按排序顺序打印出二叉搜索树的键。

好的,现在我们了解了打印BST的Inorder Traversal的递归算法,我们重新关注我们找到节点的任务,该节点将是比我们的目标节点小的最大节点。这个节点是什么?它只是在BST的Inorder遍历中我们虚拟插入节点之前打印的节点。这被称为Inorder Predecessor。

让我们回顾一下我们的BST,了解Inorder Predecessor是什么。

enter image description here

这里的12个Inorder Predecessor是什么?我们做什么?我们知道节点的Inorder Predecessor是在Inorder遍历中打印此节点之前打印的节点。这可以是哪个节点?为了回答这个问题,我们必须想象出我们的节点12将被打印的确切时间点。显然,对于通过我们的Inorder遍历算法在特定节点之前打印的内容,它应该是已经完成打印步骤的某个节点。在这种情况下,对于节点12,我们看到12的打印步骤之前立即通过调用递归地打印12的左孩子。什么是12岁的孩子?它是10.显然,10是在Inorder遍历中在12之前打印的节点。因此,10是12的Inorder Predecessor。

通常,很容易看出,如果一个节点有一个左子树,那么节点的Inorder Precedessor就是它左边子树中的最大节点。

但是如果节点没有左子树怎么办?比如说,节点10的Inorder Predecessor是什么?我们怎么找到这个?

嗯,这有点棘手,因为10没有左子树。让我们想一想,哪个节点可以在节点10之前打印?我们正在寻找已经打印过的节点。首先,我们如何在遍历的算法序列中到达节点10?我们从节点12离开,Inorder Predecessor可能是12吗?不,因为根据算法,节点12将仅在10之后打印,因为通过对节点12的左子树的递归调用打印10。好的,什么叫12? 8称为递归调用12,作为其递归调用它的右子树的一部分。好吧,8可以成为Inorder Predecessor吗?首先我们已经打印了8个?是的,我们已经打印了8,因为我们现在已经在8的正确子树中了。图8确实是在Inorder Traversal中恰好在节点10之前打印的节点。为什么?我们刚刚做了什么?我们所做的只是回顾我们如何在节点10结束,回溯步骤并查看已经打印的第一个节点。显然,这就是Inorder Predecessor。因此,8是Inorder Predecessor of 10。

一般来说,很容易看出,如果一个节点没有左子树,你需要做的只是从你的节点到树的树上,然后找到一个正确的子节点它的父母,那么父母将是Inorder Predecessor。

因此,要找到节点的Inorder Predecessor,您需要做的就是追溯到遍历中到达节点的递归调用。

汇总:  如果节点具有左子树,则Inorder Predecessor是节点左子树中的最大键。  其他   上去,直到你发现一个节点是它的父母的正确孩子,那么父母将是Inorder Predecessor。

现在,我们了解Inorder Predecessor是什么以及如何在BST中找到它,让我们回到我们的任务。由于我们的节点首先不存在,所以我们将插入它(几乎至少),这意味着它将是插入的叶子,这意味着它将没有左子树,这意味着它的前身是实际上只有树。因此,在一次遍历中,您应该能够执行此任务,而无需实际插入。