【问题标题】:what is the fastest algorithm for finding the k-maximal elements of the sequence using stl-containers使用 stl-containers 查找序列的 k 最大元素的最快算法是什么
【发布时间】:2026-02-15 03:00:01
【问题描述】:

我需要使用 c++ 任何 stl-containers 找到序列的 k 最大元素的最快算法。 我的想法:使用列表或向量,对它们进行排序,获取第一个 k 元素。在这种情况下,操作数等于 n*log(n)。 n - 元素的数量。 但我认为这不是最好的。

【问题讨论】:

  • 你可以在 O(N) 时间内得到一个列表的第 i 个值,但我不知道算法是否扩展到第 i 个值。
  • k 可以有多大?如果 k 很小,使用堆可能会更快。此外,这可能是一个骗局,所以您可能会发现它已经在网站的其他地方得到了回答。

标签: c++ algorithm stl


【解决方案1】:

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

还要注意 std::nth_element 哪个 获得了右边第 n 个位置的元素(并将序列划分为之前的“更小”和之后的“更大”第n个元素

因此,如果您真的对只对前 k 个元素感兴趣(没有特定的内部排序),那么 nth_element 肯定会拿下饼干

【讨论】:

  • 这是最简单的选择,但partial_sort 仍然通常使用@6502 描述的次优算法实现。渐近更快的解决方案是 quickselsort 算法。在使用 partial_sort 之前检查您的库(或尝试一下,看看是否足够)。
  • 您能否在回答中也包括partial_sort 的 BigO 是什么?
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

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

    Worst-case(排序的原始容器)表示O(k*n),最好的情况是O(n)

    【讨论】:

      【解决方案4】:

      使用QuickSelect,您可以使用 wiki 页面中描述的“智能”枢轴选择在 O(n) 最坏情况下找到它们(未排序:它们是在最后诱导的第 k 个元素之前的元素由算法)。

      您无法击败 O(n)(因为您必须“触摸”所有元素以确保您选择的元素是第 k 个),所以这是您可以达到的最佳效果。

      【讨论】:

      • 在一般情况下这很好,但应该注意的是,该算法确实会降级为多项式最坏情况,即使是三的中位数也可以通过精心选择的输入被恶意破解。
      • median-of-three 可以通过精心挑选的输入来击败。中位数的中位数不能。 Mark 的评论是正确的,但与足够好的 Quickselect 版本无关——他似乎不同意你的“这个算法”是什么!
      【解决方案5】:

      编辑:如果您不关心最大项目的顺序,您可以使用nth_element 来划分向量,如@sehe 所述。这是O(n)

      否则,如果您确实关心排序:

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

      交替堆积您的数据并提取k 项。这仍然是O(n log k),但我相信更高的常数。

      如果性能是一个关注点,两种方法都可以使用,并为您的数据集使用更快的方法。

      【讨论】:

      • 删除了赞成票,因为也许 nth_element 实际上更适合提问者问题(请参阅here
      • 可能会更快,因为它可以在分区完成时停止,不需要继续直到所有前 k 个元素都正确排序。不过,我还没有查看标准保证
      • 最好对 nth_element 的结果应用完全排序(支付 O(n) + O(k log k)),而不是对整个数组应用部分排序(支付 O(n log k ))。
      • @akappa 在这种情况下,假设标准库已经为我们做了优化,这不是公平的吗?还是依赖于知道kn 的相对值?
      【解决方案6】:

      很遗憾,我找不到为此编写的源代码,但请查看:

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

      【讨论】:

        【解决方案7】:

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

        总运行时复杂度为O(k * log n),优于O (n * log k),因为n 大于k。正如您所问的,所有这些都已在<algorithm> 中提供,因此实施非常简单。

        【讨论】:

          【解决方案8】:

          可以通过使用selection algorithm 在最坏的情况下采用O(n) 来在线性时间内完成此操作,然后遍历向量一次并精确获取至少与 (n-k) 一样大的元素-th order statistic(并记录您已采用的元素数量,以便您准确地采用k 而不是更多)。然而,Cppreference 表示std::nth_element 平均需要线性时间,而不是最坏的情况。我将解释如何使用堆以稍慢但可能更简单的方式执行此操作。此解决方案在最坏的情况下需要时间 O(max(n,k*log(k))) 来提取大小为 n 的向量的顶部 k 元素。

          首先创建一个包含所有 n 元素的最大堆,这需要 O(n) 时间 std::make_heap

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

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

          【讨论】:

            最近更新 更多