使用静态容器找到序列中k个最大元素的最快算法是什么?

what is the fastest algorithm for finding the k-maximal elements of the sequence using stl-containers

本文关键字:元素 是什么 算法 静态      更新时间:2023-10-16

我需要使用c++任意stl容器查找序列中k个最大元素的最快算法。我的想法是:使用列表或向量,对它们排序,得到前k个元素。在这种情况下,操作次数等于n*log(n)。N -元素的数目。但我认为这不是最好的。

使用std::partial_sort的方法可能是最好的答案。

还要注意 std::nth_element 刚好获得第n个位置的元素(并将序列划分为第n个元素之前的"小"和之后的"大")

所以,如果你真的对感兴趣,只是前k个元素 (没有特定的内部排序),那么 nth_element绝对是biscuit

我认为最好的方法是使用一个向量来保存结果,并在你遍历输入时在其中建立一个堆。一旦堆大小达到k,你就不再增加它了(只是保持从位置k-1开始冒泡)。

当输入完成时,堆已经是一个答案(假设您没有被要求按顺序返回它们)。

如果是k > n/2,那么最好存储那些从大小为n - k的堆中冒出来的元素(但这假设您知道n的元素数量,而不仅仅是k)。

假设随机未排序的数据,我认为最快的是创建一个排序的链表,在原始容器上循环,对于每个元素,如果它大于结果向量中的最低值,将其钩入(在正确的排序位置)。如果列表现在包含超过k个元素,则删除最小的值。

最坏情况(排序的原始容器)是指O(k*n),最佳情况O(n)

使用QuickSelect,您可以使用wiki页面中描述的"智能"pivot选择在O(n)最坏情况下找到它们(未排序:它们是在算法引起的最终顺序中第k个元素之前的元素)。

你不能超过O(n)(因为你必须"触摸"所有元素以确保你选择的是第k个元素),所以这是你能达到的最好结果。

编辑:如果你不关心最大项的顺序,你可以使用nth_element来划分一个向量,正如@sehe所指出的。这是O(n) .

否则,如果你关心排序:

在数据向量上使用std::partial_sort对第一个k项进行排序。这将运行在O(n log k)

或者堆积您的数据并拉出k项。这仍然是O(n log k),但我相信有更高的常数。

如果性能是一个问题,配置文件两种方法和使用更快的数据集。

遗憾的是,我找不到我为此编写的源代码,但是看看这个:

http://en.wikipedia.org/wiki/Radix_sort

我会使用std::make_heap从数组或值向量构建堆,这将导致O(n)时间。然后,您可以重复检查堆的顶部元素并将其弹出k次(使用std::pop_heap),这将导致O(k * log n)时间。

总运行时复杂度将是O(k * log n),这比O (n * log k)好,因为n大于k。正如你所问的,所有这些都已经在<algorithm>中可用,所以实现起来非常容易。

可以在线性时间内通过使用选择算法来做到这一点,该算法在最坏情况下取O(n),然后遍历向量一次并精确地取至少与(n-k)阶统计量一样大的元素(并记录你取了多少元素,这样你就可以取k而不是更多)。然而,Cppreference表示std::nth_element平均花费线性时间,而不是最坏情况。我将用一种稍微慢一些但可能更简单的方法来解释如何使用堆。该解决方案在最坏情况下需要O(max(n,k*log(k)))时间来提取大小为n的向量的前k个元素。

首先为所有n元素创建一个max-heap,对于std::make_heap,这需要O(n)时间。

我们现在想要从堆中提取k的顶部元素,但是我们在这样做的时候必须聪明。如果我们提取最大元素k次,那么每次将花费我们O(log(n)),因此总共花费O(k*log(n)),这没有达到我们的目标。

相反,我们不会碰这个n大小的堆,而是创建一个单独的最大堆,我称之为"等待堆"。这个等待堆只从原始堆的最大元素开始,为了获得顶部的k元素,您需要重复以下过程k次:从等待堆中提取顶部元素并将其两个后代添加到它中。等待堆的大小每一步增加1,因此它以k为界。由于我们正在执行k提取和2k插入(假设您使用的是二进制堆),因此这将不会比3*k*log(k)花费更多的时间。