最大堆中的第 K 个最大元素

Posted

技术标签:

【中文标题】最大堆中的第 K 个最大元素【英文标题】:Kth largest element in a max-heap 【发布时间】:2015-10-06 00:14:22 【问题描述】:

我正在尝试解决以下问题:

给定一个表示为数组的最大堆,在不修改堆的情况下返回第 k 个最大的元素。我被要求在线性时间内完成,但被告知可以在对数时间内完成。

我想到了一个解决办法:

使用第二个最大堆并在其中填充 k 或 k+1 个值(广度优先遍历原始值),然后弹出 k 个元素并获得所需的元素。我想这应该是 O(N+logN) = O(N)

是否有更好的解决方案,可能在 O(logN) 时间内?

【问题讨论】:

知道了,谢谢,但在这种情况下,我仍然认为您的算法不正确,因为树的广度优先搜索无法正常工作? 我想它应该可以工作。我错误地使用了“搜索”一词,基本上我只是在搜索存储一个级别的节点然后继续下一个级别的遍历。我将编辑该术语以消除任何潜在的歧义 我认为斐波那契堆是一种摊销 O(log n) 解决方案的方法,但我喜欢这个问题。我会考虑的…… @Alstor 我认为您的解决方案不正确,因为第 k 个最大元素不必位于树的第 k 级。 @Alstor 如果您只是要遍历树并使用堆栈,那么为什么要使用第二个最大堆,因为遍历不会修改堆? 【参考方案1】:

据我所知,没有简单的算法可以解决这个问题。我所知道的最好的算法归功于 Frederickson,这并不容易。您可以查看论文here, but it might be behind a paywall. 它运行时间为 O(k),据称这是最好的时间,所以我怀疑不存在对数时间的解决方案。

如果我找到比这更好的算法,我一定会告诉你的。

希望这会有所帮助!

【讨论】:

【参考方案2】:

不,没有 O(log n) 时间算法,通过简单的细胞探测下限。假设 k 是 2 的幂(不失一般性)并且堆看起来像(min-heap 传入,因为它更容易标记,但没有真正的区别)

      1
   2     3
  4 5   6 7
.............
permutation of [k, 2k).

在最坏的情况下,我们必须读取整个排列,因为堆没有强加的顺序关系,只要没有找到 k,它可能在任何尚未检查的位置。这需要时间 Omega(k),与 templatetypedef 发布的(复杂的!)算法相匹配。

【讨论】:

标题表示最大堆中第 K 个最大的(不是任意数组)。所以没有最小堆的问题。您概述的最坏情况不适用,因为它是最大堆,因此父节点始终高于其子节点。 @Kishore 我的回答的重点是堆结构在最坏的情况下不会强加太多结构。【参考方案3】:

最大堆可以有多种方式,更好的情况是完全排序的数组,在其他极端情况下,堆可以有完全不对称的结构。

这里可以看到:

在第一种情况下,第 k 个最大元素在第 k 个位置,您可以用堆的数组表示在 O(1) 中计算。 但是,通常,您需要在 (k, 2k) 个元素之间进行检查,并对它们进行排序(或使用另一个堆进行部分排序)。据我所知,是 O(K·log(k))

还有算法:

Input:
    Integer kth <- 8
    Heap heap <- 19,18,10,17,14,9,4,16,15,13,12

BEGIN
    Heap positionHeap <- Heap with comparation: ((n0,n1)->compare(heap[n1], heap[n0]))

    Integer childPosition
    Integer candidatePosition <- 0
    Integer count <- 0
    positionHeap.push(candidate)
    WHILE (count < kth) DO
        candidatePosition <- positionHeap.pop();
        childPosition <- candidatePosition * 2 + 1
        IF (childPosition < size(heap)) THEN
            positionHeap.push(childPosition)
            childPosition <- childPosition + 1
            IF (childPosition < size(heap)) THEN
                positionHeap.push(childPosition)
            END-IF
        END-IF
        count <- count + 1
    END-WHILE
    print heap[candidate]
END-BEGIN

已编辑

我在这里找到了 Frederickson 的“最小堆中的最优选择算法”: ftp://paranoidbits.com/ebooks/An%20Optimal%20Algorithm%20for%20Selection%20in%20a%20Min-Heap.pdf

【讨论】:

您能否在站点参考声明“通常,您需要检查 (k, 2k) 元素之间”?例如,在this 图像中,第 7 大元素 9 位于索引 1,而在 this 图像中,第 4 大元素 12 位于索引 14。所以我猜这个说法是错误的。 @anir 在您的第一个示例中,您如何知道第 7 大元素是什么?要确定哪个是第七大元素,您必须评估 9 个元素。所以 7 我认为,通过“检查 (k, 2k) 个元素之间并对其进行排序”,您的意思是在堆数​​组索引 k 和 2k 之间对元素进行排序。现在我觉得,这不是你想说的对吧?我说这是错误的,因为在第一个示例中,9 是第 7 大元素,但出现在索引 1 处。因此我们无法通过对索引 7 和 14 之间的元素进行排序来确定 9 为第 7 大元素(然后从他们)。同样,在第二个示例中,第四大元素 12 出现在索引 14 处。因此,我们无法通过在索引 4 和 8 之间对元素进行排序来确定 12 为第四大元素。(续...) (续...)可能是一个可以避免混淆的例子。现在我觉得你在谈论与这些答案中解释的相同过程:1、2 和3,而不是直接在索引 k 和 2k 之间对堆数组元素进行排序(然后从排序输出中选择最小的),对吗?【参考方案4】:

数组中的最大堆:element at i is larger than elements at 2*i+1 and 2*i+2i 从 0 开始)

您将需要另一个最大堆(insertpopempty),其中元素对(value, index)value 排序。伪代码(无边界检查):

input: k
1. insert (at(0), 0)
2. (v, i) <- pop and k <- k - 1
3. if k == 0 return v
4. insert (at(2*i+1), 2*i+1) and insert (at(2*+2), 2*+2)
5. goto 2

运行时评估

数组访问 at(i): O(1) 插入堆:O(log n) 在 4 处插入。最多需要 log(k),因为堆对的大小最多为 k + 1 语句 3. 最多达到 k 次 总运行时间:O(k log k)

【讨论】:

以上是关于最大堆中的第 K 个最大元素的主要内容,如果未能解决你的问题,请参考以下文章

优先队列(堆)经典例题——poj1442 black fox

java - 如何使用PriorityQueue集合从Java中的给定数组在O(n)时间内构建最大堆?

最小堆_最大堆

关于leetcode第K个最大元素的几种解法

二叉堆

算法试题 - 找出最小 k 个数