如何有效地找到列表中的n个最小元素?

时间:2017-05-02 03:02:38

标签: f# heap priority-queue

查找list<float>中的n个最小元素可以通过对列表进行排序并选择n个最小元素来完成。但是使用堆也可以更有效地完成它。我发现了几个用于F#的堆的实现,但是没有关于如何将它们用于此目的的示例。我的两个绊脚石是:

1)我找不到从列表中创建堆的方法。我见过的所有实现都提供了一种创建空堆并具有insert方法的方法。我应该创建一个空堆并逐个插入项目吗?这看起来很慢,这会破坏目的。

2)没有实现有nLargest或nSmallest方法,例如,这个Python代码:

from heapq import nlargest
lst = [9,1,6,4,2,8,3,7,5]
nlargest(3, lst) # Gives [9,8,7]

有没有一种简单的方法可以解决这个问题?

2 个答案:

答案 0 :(得分:2)

请注意,您并不需要为此使用堆。例如,请参阅Jon Skeet's old post,了解如何实现快速排序以生成排序序列,而无需预先执行所有工作。 (不幸的是,帖子中有一些不相关的说明,因为它只是重新实现LINQ到对象的长篇系列的一部分)

答案 1 :(得分:1)

如何将数组重新排列为二进制堆:

从数组的中间开始向后移动,在堆中向下筛选每个元素。例如,假设您有一个长度为a的数组n。以下是:

for i = n/2 downto 0
    siftDown(i);

siftDown方法:

siftDown(int index)
{
    while (index < n/2)
    {
        // find the smallest child
        int ixChild = (ix * 2) + 1;
        if (ixChild < n-1 && a[ixChild] > n[ixChild + 1])
        {
            ixChild = ixChild + 1;
        }
        // if the item is <= the smallest child, we're done
        if (a[i] < a[ixChild]) break;

        // otherwise, swap with the smallest child
        swap(i, ixChild);

        // and do it again
        i = ixChild;
    }
}            

选择列表中k个最小项的另一种方法是使用Quickselect,这基本上是Quicksort分区方法。这具有O(n)的优点,其通常比使用堆更快。算法完成后,k个最小的项目位于数组的前面,但它们没有排序。

最后,如果要使用堆选择方法,可以考虑使用Pairing heap而不是二进制堆。配对堆有O(1)插入和O(log n)删除。另外,从维基百科页面上的示例中可以很容易地在F#中实现。