这是上周讲座中提出的一个挑战性问题,自那以后我一直在考虑它。我们被要求创建一种算法,在两个AVL树之间搜索第k个最大元素。两棵树中的每个节点都包含两条信息:它的整数值和它在子树中包含的子项的数量(因此一个叶子将有一个子元素)。算法的复杂性不能低于O((logn)^ 2)。
我想过将一棵树中的每个节点与另一棵树中的每个节点进行比较,但这样的O(n)复杂度太慢了。
答案 0 :(得分:1)
作为第一步,设计一个函数,对于给定的上限 b ,确定当 b 时,AVL树中的元素数量会减少:
count(node, b)
if node.key < b:
return node.left.size + 1 + count(node.right, b)
else
return count(node.left, b)
每个分支中只有一个递归调用,并且每次调用时节点的深度都会增加。所以这个函数的复杂性是O(log(n))。
现在我们可以使用count
:
kth(k, node1, tree2)
left = node1.left.size + count(tree2, node1.key)
if k < left:
return kth(k, node.left, node2.right
else if k == left:
return max(upperBound(tree2, node1.key), upperBound(node1.left, node1.key))
else if k == left + 1:
return node1.key
else
return kth(k - node1.left.size - 1, tree2)
对于这个函数node1
在每次递归调用中也增加了,所以我们有 O(log n)调用,每个调用需要 O(log n)时间由于调用count
函数。根据需要,总运行时间为 O(log²n)。
要完成此解决方案,需要设计应返回子树中最大键的函数upperBound
。这可以在 o(log n)时间内轻松完成。
答案 1 :(得分:0)
假设k-th
元素位于第一个树中。我们可以二元搜索它在树中的位置。对于固定位置p
,我们可以在p-th
时间内找到树中的O(log n)
元素(从根开始,向右移动,具体取决于左子树的大小) 。
现在我们需要找到第二个树中小于此元素的元素数。我们可以在O(log n)
时间从根开始,向左或向右移动,具体取决于当前节点中的值(当我们向右移动时,将左子树的大小添加到答案中) 。
当我们知道第二棵树中小于给定元素的元素数量时(让我们称这个数字为s
),我们知道它在树的联合中的位置正好是{{ 1}}。
此算法在s + p
时间内运行。
如果第O(log^2 n)
个元素不在第一个树中,则它必须位于第二个树中。因此,我们在另一棵树上重复这个过程来得到一般情况下的答案。