假设您有一个平衡的二叉搜索树(比如AVL树),其中包含n个数字。
您需要一种能够查找给定数字x的算法,但如果它找不到, 返回小于x的最大数字。
为了问题,不插入x然后删除它,解决方案应该是O(log(n))。
谢谢
答案 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:
这里,让我们跟踪我们的算法,首先我们在根(值为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是什么。
这里的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中找到它,让我们回到我们的任务。由于我们的节点首先不存在,所以我们将插入它(几乎至少),这意味着它将是插入的叶子,这意味着它将没有左子树,这意味着它的前身是实际上只有树。因此,在一次遍历中,您应该能够执行此任务,而无需实际插入。