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

Algorithm 从长度为N的数组返回前k值的优化算法,algorithm,sorting,Algorithm,Sorting,我有一个n个浮点数组,我希望返回最上面的k (在我的例子中,n~100,k~10) 这个问题有一个已知的最优解决路径吗 有人能提供一个C算法吗 编辑:实际上这里有两个问题:排序和未排序。我对unsorted感兴趣,它应该更快 您可以在O(n)中使用。用分区算法找到kth最大的元素,然后它后面的所有元素都会比它大,这些就是你的顶部k 如果您需要按排序顺序排列前k,则可以按O(k log k)对它们进行排序 较长的回答:是的,已知几个互不兼容的最优解。这取决于n,k以及您可以保证的数组属性 如果您对

我有一个n个浮点数组,我希望返回最上面的k (在我的例子中,n~100,k~10)

这个问题有一个已知的最优解决路径吗

有人能提供一个C算法吗

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

您可以在
O(n)
中使用。用分区算法找到
k
th最大的元素,然后它后面的所有元素都会比它大,这些就是你的顶部
k

如果您需要按排序顺序排列前
k
,则可以按
O(k log k)
对它们进行排序

较长的回答:是的,已知几个互不兼容的最优解。这取决于n,k以及您可以保证的数组属性

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

类似地,复杂度上限是O(n.log(n)),因为您总是可以选择通过排序数组(O(n.log(n))并返回前10项(O(1))来查找答案

线性搜索将每个项目与迄今为止发现的第十个最高项目进行比较,并在需要时将其插入到迄今为止发现的最高项目列表中的适当位置,对于平均和最佳情况场景具有类似的复杂性,最坏情况O(kn)明显优于O(n平方).对于您估计的尺寸,我希望这种方法能够很好地执行

如果n大得多(~10000)和k以相同的比例增加可能值得实施quickselect算法。quickselect的性能更好,您需要的元素越多。但是,如果k不是以n的比例增加,您应该坚持线性搜索。quickselect和friends会修改原始数组,因此如果您不能这样做,则不太适合这是因为您需要更多的存储和大量的复制,而这是算法复杂性所不包括的


如果n很大(~1e20)您可能希望从输入数组的多个分区中找到k个最大值,然后从这些结果的集合中找到k个最大值,这样您就不会试图一次分析超过内存容量的数据,并允许有效地并行操作。

方法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后的最后,将最大的替换为-无穷大,锦标赛中最大的将是第k个最大的元素。你丢弃的元素是最上面的k-1元素

这最多需要
n-k+(k-1)[log(n-k+2)]
比较才能找到最上面的k。不过它使用了O(n)内存

就比较次数而言,这很可能胜过任何选择算法

方法2

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

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

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


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

如果你有一个漂亮的gpu,我可以告诉你如何同时计算最大的n个实例中的k个实例,所以将它们按每个实例分布在纹理上,并使用它们的“高度”在纹理上添加混合作为沿纹理的位置

但请注意,你必须猜测一个可接受的范围或知道它,否则你不会传播到你本可以拥有的最大细节

你可以在所有实例中克隆位置。(如果上面有2个位置,你应该得到一个2,如果上面有10个位置,你应该得到10。)(只需在8192x8192纹理,64x64这些“高度”框上说全部。)并且你还可以跳过计数为0的插槽

然后做一个mipped add层次结构,除了你像二叉树一样做,你只把它当作它的1维,所以把前面的2个数字加在一起,继续为每个二叉mip做

然后,我们使用这些MIP(收集计数)来发现k的大致位置,使用过程中的所有MIP,在最后一个线程上执行此操作,您将从中取出大量块,然后慢慢使用更详细的MIP来查找k所在的每像素值

这样做更有意义,如果它再次被实例化,那么它是一个线程/阈值发现(只需假设您一次运行一个ANN 128x128次,(平移不变性,有人吗?),那么它就非常有意义

并达到该计数的阈值高度,但它是近似值…因此得到近似值
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);
}