我有n个数组。这些阵列中的每一个都可以具有无限长度。 (长度可以变化)。所有这些n个数组都已排序。
现在我想从这些n个排序数组中取出前k个最小元素。
例如n = 5且k = 10
2 4 6 7 9
23 45 67 78 99
1 2 6 9 1000 4567 6567 67876
45 56 67 78 89 102 103 104
91 991 9991 99991
现在回答应为1 2 4 6 7 9 23 45 56 67
在最坏的情况下,它是O(n * k),即O(n ^ 2),在最好的情况下是O(k)吗?
答案 0 :(得分:8)
我认为这是O(n + k.log(n))。
首先在每个数组中构建一个最小元素的堆(也存储数组的索引)。构建大小为n的堆是O(n)。然后,重复k次:从堆中取一个元素(即O(log n)),然后插入所用元素来自数组的下一个最小元素(也是O(log n))。总的来说,这是O(n + k.log(n))。
答案 1 :(得分:3)
Anonymous提供的答案在这种情况下是更好的解决方案,因为我们知道各个数组已经排序。
你可以在O(n log k)时间内使用堆来完成,最坏的情况。这将需要O(k)额外空间。
initialize a MAX heap
for each array
for each item in the array
if (heap.count < k)
heap.insert(item)
else if (item < heap.peek())
{
// item is smaller than the largest item on the heap
// remove the smallest item and replace with this one
heap.remove_root()
heap.insert(item)
}
else
{
break; // go to next array
// see remarks below
}
因为您知道数组最初是排序的,所以您可以包含我展示的最终优化。如果您正在查看的项目不小于堆上已有的最大项目,那么您就知道当前数组中的其他项目不会更小。所以你可以跳过当前数组的其余部分。
这是为您提供最小k
项的算法。如果您想要最大的k
项,请构建MIN堆并将if (item < heap.peek())
更改为if (item > heap.peek())
。在这种情况下,通过向后移动数组可以获得更好的性能。这将减少堆插入和删除的数量。如果你不向后走数组,你将无法使用我展示的优化。
另一种方法是将所有项目连接成一个数组并使用Quickselect。 QuickSelect是一种O(n)算法。 Empirical evidence表明在k < .01*n
时使用堆更快。否则,Quickselect更快。当然,您的里程可能会有所不同,并且必须从多个阵列创建单个阵列将为Quickselect增加处理和内存开销。