从大列表中获取最大的n个元素时应使用哪种算法?

时间:2018-08-21 08:48:24

标签: algorithm sorting

在我的项目中,有一个很大的清单。 此列表上最常见的操作是获取最大的n元素。 n在整个生命周期中都是固定的或很少更改的。我应该使用哪种算法才能有效地做到这一点?

这意味着在插入,更新或删除列表中的元素时应该做什么,以及从列表中获取前n个元素时应该做什么。

有一个解决方案(也许不是很好):

  1. 在插入,更新或删除元素之后,使用quicksort或其他排序算法对列表进行排序。 由于列表太大,因此此步骤可能太慢。
  2. 获取前n个元素时,请从列表中获取前n个元素。

有更好的解决方案吗?

4 个答案:

答案 0 :(得分:3)

因此,您有一个n个项目的列表,并且想选择最大的k个。一种方法是使用大小为k的最小堆。生成的算法为O(n log k)。

首先创建前k个项目的空白最小堆。然后,对于列表中的每个后续项,如果它大于堆中的最小项,请删除堆中的最小项,然后将其替换为新项。完成后,最大的k项将在堆上。伪代码如下:

// assume an array a[], with length n.
// k is the number of largest items you want.
heap = new min-heap

// add first k items to the heap
for (i = 0; i < k; ++i)
    heap.add(a[i])
for (i = k; i < n; ++i)
    if (a[i] > heap.peek())
        heap.removeMin()
        heap.add(a[i])
// at this point, the largest k items are on the min-heap

当k占n的一小部分时,此技术效果很好。在这种情况下,它只需要很少的内存。该算法的最坏情况下运行时间为O(n log k),但是它高度依赖于列表中项目的顺序。最坏的情况是数组按升序排序。最好的情况是数组按降序排序。在一般情况下,从堆中添加和删除项的数量要少于50%。

另一种算法Quickselect的复杂度为O(n),但是当k占n的一小部分(1或2%)时,它比堆选择方法慢。快速选择还会修改现有列表,而这可能不是您想要的。

有关更多详细信息,请参见我的博客文章https://blog.mischel.com/2011/10/25/when-theory-meets-practice/

您可以在此处做一些事情,通过维护堆而不是为每个查询重建它来加快平均时间。

  1. 如果所需的项目数始终少于30,则始终保持30个项目的堆。如果用户只想要前十名,那么您可以从堆中挑选那些。
  2. 将一个项目添加到列表中后,检查它是否大于堆中最小的项目。如果是这样,请更换最小的物品。
  3. 删除项目后,将堆标记为脏。
  4. 当询问前k个项目时,如果堆很脏,则必须重新构建它。否则,您可以将堆中的内容复制到临时数组中,对其进行排序,然后返回所要求的k个项目。当然,一旦重建堆,请清除脏标志。

那么,结果是您可以以很少的成本维护堆:只要添加新项目,就可以对其进行更新,但前提是该堆必须大于前30个(或最大)项目之一。唯一需要重建的是在删除后要求输入前k个项目。

考虑一下,如果删除的项目大于或等于堆中最小的项目,则只需将堆标记为脏。另外,如果堆被标记为脏堆,那么您可以放弃对插入或删除操作的任何进一步更新,因为无论如何下次您要查询时都必须重新构建堆。

答案 1 :(得分:1)

(平衡的)二进制搜索树是您最好的朋友。插入,删除,始终搜索第k个O(Log N)。


如果数据驻留在外部存储器中,则为B树或类似树。

答案 2 :(得分:0)

如果n <<<< size(list),则对主要元素使用哈希表,并使用伴随数据结构存储最大的n个元素。伴随数据结构在插入和删除期间进行更新,并用于查询最大的元素。 如果n为30,则排序数组就足够了。

免责声明:如果经常删除大多数元素,则此方法效果不佳。删除最大元素将需要对整个哈希表进行顺序扫描。

答案 3 :(得分:0)

在C ++ STL中为

。 最好的选择是使用std :: set。

每次添加元素时都会对其进行排序。 然后您可以提取std :: set

的最后n个元素