O(klogk) 时间算法从二进制堆中找到第 k 个最小元素

Posted

技术标签:

【中文标题】O(klogk) 时间算法从二进制堆中找到第 k 个最小元素【英文标题】:O(klogk) time algorithm to find kth smallest element from a binary heap 【发布时间】:2011-11-30 20:14:45 【问题描述】:

我们有一个 n 节点二进制堆,其中包含 n 不同的项(根中的最小项)。对于k<=n,找到O(klogk)时间算法从堆中选择kth最小元素。

O(klogn) 很明显,但无法找出 O(klogk) 一个。也许我们可以使用第二个堆,不确定。

【问题讨论】:

所以如果我要求你找到最小的元素 (k = 1),它应该在 O(0) 中做到这一点,也就是说,立即? :) @BlackBear:复习Big-O的定义;-p 相关:***.com/questions/4922648/… 【参考方案1】:

假设我们使用的是 minheap,因此根节点总是小于其子节点。

Create a sorted list toVisit, which contains the nodes which we will traverse next. This is initially just the root node.
Create an array smallestNodes. Initially this is empty.
While length of smallestNodes < k:
    Remove the smallest Node from toVisit
    add that node to smallestNodes
    add that node's children to toVisit

完成后,第 k 个最小的节点在 minimumNodes[k-1] 中。

根据 toVisit 的实现,您可以在 log(k) 时间内插入并在恒定时间内删除(因为您只删除最顶层的节点)。这使得 O(k*log(k)) 总计。

【讨论】:

插入不是log(k),而是log(n),其中n是堆中已经存在的节点数。插入k 节点将是k*log(n) @JimMischel: 不,在toVisit 中,任何时候都没有更多的2k 节点[因为我们为删除的每个元素添加2 个元素,我们这样做k 次] ,所以toVisit的插入和删除是O(log2k) = O(logk)。对于原始列表上的每个操作,我们只提取特定节点的直接子节点,即O(1)。我们总体上做kO(logk) ops,确实是O(klogk) 虽然sorted list 对于toVisit 来说不是一个好的数据结构,因为在这个列表中插入是O(k)。您将需要一个堆来实际获取O(klogk) [跳过列表/平衡的 BST/B+ 树也是有效的选项,虽然更难实现,但堆在这里就足够了]。 @amit:谢谢。我误解了算法的描述。 不是排序列表,你不能只使用队列并添加到队列中要访问的最小-最大子节点吗?【参考方案2】:

嗯,你的直觉是正确的,我们需要额外的数据结构来实现 O(klogk),因为如果我们只是在原始堆上执行操作,术语 logn 将保留在产生的复杂性中。

从目标复杂度 O(klogk) 猜测,我想创建和维护一个大小为 k 的堆来帮助我实现目标。您可能知道,以自上而下的方式构建大小为 k 的堆需要 O(klogk),这让我想起了我们的目标。

以下是我试图达到 O(klogk) 的尝试(不一定优雅或高效):

    我们创建一个新的最小堆,将其根初始化为原始堆的根。

    我们通过删除当前根并将当前根的两个子代插入原始堆来更新新的最小堆。我们重复这个过程 k 次。

    生成的堆将由 k 个节点组成,其根是原始堆中第 k 个最小的元素。

注意:新堆中的节点应该存储它们在原始堆中对应节点的索引,而不是节点值本身。在第 2 步的每次迭代中,我们实际上将一个由一个节点组成的网络添加到新堆中(一个删除,两个插入),其中 k 次迭代将产生大小为 k 的新堆。在第 i 次迭代中,要删除的节点是原堆中第 i 个最小的元素。

时间复杂度:在每次迭代中,从新堆中删除一个元素并将两个元素插入新堆需要 O(3logk) 时间。 k次迭代后,为O(3klogk) = O(klogk)。

希望这个解决方案能给你一些启发。

【讨论】:

这基本上是@Kevin 的解决方案 @Terry Li:如果我们创建一个大小为 k 的新最大堆并继续将原始最小堆中的元素插入新的最大堆,而不是创建一个新的最小堆。当最大堆已满时,其根将具有第 k 个最小的元素,并且该堆将具有最小的 k 个元素。我的想法对吗? @VikasMangal 不,这在 klogk 中不起作用。这又是一个 klogn 算法,因为您必须将原始的最小堆堆 k 次。 @jiangok 原来的堆不用修改了。您只需从原始堆中读取元素,然后修改新创建的堆。新堆的最大大小为 k,因此需要 O(logk) 来堆化它。 @TerryLi 那么,新堆将由指向原始堆节点而不是实际节点的指针组成?那么,新堆的堆码代码会有所不同吗?

以上是关于O(klogk) 时间算法从二进制堆中找到第 k 个最小元素的主要内容,如果未能解决你的问题,请参考以下文章

在 MFC 中从二进制文件加载图像

在 O(K*log(K)) 中打印给定堆中最大的 K 个元素?

试图理解这个从两个排序数组中找到第 K 分钟的算法

将任何文件转换为二进制字符串并从二进制转换为文件[关闭]

从二进制矩阵中划掉坏线

快速选择算法