从长度为N的数组返回前k个值的最优算法

时间:2011-02-10 11:37:44

标签: algorithm sorting

我有一个n个浮点数组,我希望返回前k个 (在我的情况下,n~100,k~10)

这个问题是否有已知的最佳解决方案?

有人可以提供C算法吗?

编辑:实际上这里有两个问题:排序和未排序。我对未分类感兴趣,应该更快!

4 个答案:

答案 0 :(得分:32)

您可以使用selection algorithmO(n)中执行此操作。使用分区算法找到k最大元素,然后它后面的所有元素都会大于它,那些元素就是你的k

如果您需要按排序顺序排列前k,则可以在O(k log k)中对其进行排序。

答案 1 :(得分:28)

方法1

由于k较小,您可以使用锦标赛方法找到第k个最大值。 Knuth的编程艺术,第3卷,第212页描述了这种方法。

首先在n-k + 2个元素上创建一个锦标赛。像淘汰赛网球锦标赛。首先你分成几对并比较成对的成员(就好像那两个玩了一个匹配而一个丢了)。然后是获胜者,你再次分成两对,等等,直到你有一个胜利者。您可以将其视为一棵树,获胜者位于顶部。

这完全比较n-k + 1。

现在这些n-k + 2的获胜者不能成为你的第k大元素。考虑它在比赛中的路径P.

剩下的k-2现在选择一个,并沿着路径P向上,这将给你一个新的最大。基本上你有点重做锦标赛,前一个冠军被一个k-2元素取代。让P成为新赢家的道路。现在从k-3中选择另一个并按照新路径等等。

在你耗尽k-2之后的最后,用-infinity替换最大的,并且锦标赛中最大的将是第k个最大的。你扔掉的元素是顶级的k-1元素。

这需要最多n - k + (k-1) [log (n-k+2)]次比较才能找到前k个。它虽然使用O(n)内存。

就比较数而言,这可能会超过任何选择算法。

方法2

作为替代方案,您可以维护k个元素的最小堆。

首先插入k个元素。然后对于数组的每个元素,如果它小于堆的min元素,则抛弃它。否则,删除堆的min并从数组中插入元素。

最后,堆将包含前k个元素。这将进行O(n log k)比较。

当然,如果n很小,只需对数组进行排序就足够了。代码也会更简单。

答案 2 :(得分:10)

简答:不。

更长的答案:是的,已知几种互不兼容的最佳解决方案。它取决于n,k以及您可以保证的数组属性。

如果您对数组一无所知,复杂性的下限显然是O(n),因为必须检查源数组的所有元素以查看它们是否适合前10位。如果您对源数组有所了解它允许安全地跳过元素,你应该使用这些知识。

类似地,上层复杂性界限是O(n.log(n)),因为您总是可以通过对数组进行排序(O(n.log(n))并返回前10个项目(O(O)来选择找到答案。 1))。

线性搜索将每个项目与目前发现的第十个最高项目进行比较,并将其插入到最高找到项目列表中的适当位置(如果需要),对于平均和最佳案例场景具有相似的复杂性最坏情况下的O(kn)明显优于O(n平方)。对于您估计的尺寸,我希望这种方法表现良好。

如果n大得多(~10000)并且k以相同的比率增加,那么实施快速选择算法可能是值得的。 Quickselect可以更好地执行您想要的更多元素。但是,如果k没有按比例增加n,你应该坚持使用线性搜索。快速选择&朋友修改原始数组,所以如果你不能这样做就不太合适了,因为你需要更多的存储空间和大量的复制算法复杂性不包括在内。

如果n很大(~1e20),你会想要从输入数组的多个分区中找到最大的k,然后从这些结果的聚合中找到k-最大,这样你就不会尝试分析比一次适合内存的数据更多的数据,并允许有效地并行操作。

答案 3 :(得分:3)

以下是Java中基于堆的优雅解决方案,复杂度为O(nlogK)。它不是最有效的,但我认为它很容易理解。如果您想要基于浮动的解决方案,可以将Integer更改为Float

import java.util.Arrays;
import java.util.PriorityQueue;

public class FindKLargest {

public static void find(int[] A, int k) {

    PriorityQueue<Integer> pq = new PriorityQueue<>(k);// Min heap because the element has to be greater
                                                        // than the smallest element in the heap in order
                                                        // to be qualified to be a member of top k elements.
    for (int i = 0; i < A.length; i++) {
        if (i < k) // add until heap is filled with k elements.
            pq.add(A[i]);
        else if (pq.peek() < A[i]) { // check if it's bigger than the
                                        // smallest element in the heap.
            pq.poll();
            pq.add(A[i]);
        }
    }
    int[] topK = new int[pq.size()];
    int index = 0;
    while (index != k)
        topK[index++] = pq.poll();
    System.out.println(Arrays.toString(topK));
}

public static void main(String[] args) {
    int[] arr = { 1, -2, -3, -4, -5 };
    find(arr, 4);
}

}