如果我有K个排序的N个元素的数组,例如
[0, 1, 2]
[1, 6, 8]
[10, 11, 12]
我知道我可以通过循环所有列表及其元素并将它们插入堆中来使用堆来合并它们,然后每次在O(KN * log(KN))中返回最小值。
我在互联网上查了一下,另一个流行的解决方案似乎是使用只有K个元素的最小堆并将K列表的所有第一项插入堆中,然后获得最小值并将指针前进到列表中拥有最小元素。
除了更高效的内存要求(第二种情况下为O(K)),第二种方法是否更有效?
可选的奖励积分:是否有比上述算法更好的算法?
答案 0 :(得分:2)
当你有足够的内存来执行所有输入列表的排序时,第一种方法很好,但是只需要在已经排序的列表之间执行k-way合并就更简单了额外空间(K元素列表)用于跟踪您在每个输入列表中的索引。这是一个O(K^2 * N)
解决方案。
哪个更好 - 第一种方法或k-way合并,取决于K与N相比有多大,让我们不要忘记为第一种方法构建堆的O(KN)
成本。提出一个想法:
k=5; n=100
k*n*log(k*n)
=> 3107
k*k*n
=> 2500
k=100; n=100
k*n*log(k*n)
=> 92103
k*k*n
=> 1000000
第二种方法使用较少的内存,这非常重要!当输入列表不适合内存时,它是要走的路 - 因此我们从每个列表中取出一个元素,将其放入堆中,确定下一个进入最终的列表结果,并将其写入输出,相应地更新堆:复杂性O(KN * log(K))
。再一次,提出一个想法:
k=5; n=100
k*n*log(k)
=> 804
k=100; n=100
k*n*log(k)
=> 46051
底线:当输入适合内存并且k很小时,使用k-way合并而不是第一种方法,并且正如@btilly指出的那样,第二种方法理论上是最好的方法,但实际考虑可能使k-way合并得更快。像往常一样:最好的策略是分析一些真实的数据,然后挑选胜利者!
答案 1 :(得分:2)
由于您对每个元素(N * K)执行heapify(log(K))操作,因此第二个版本的运行时间应为O(KN * log(K))。所以是的,它更快。我想不出更有效的方法来解决这个问题。
答案 2 :(得分:1)
第一个答案是O(KN * log(KN))
第二个答案是O(KN * log(K))
,所以更好。通常不可能做得更好。
也就是说,你可以在实践中改进它。不是将最小元素转储到堆中,而是创建合并树之类的合并树。然后添加逻辑,当您似乎从合并的一侧拉出时,尝试向前跳并寻找运行。
如果K
很大,比较费用昂贵,并且您的数据有很多次运行,那么胜利可能很重要。
有关排序算法的示例,请参阅https://en.wikipedia.org/wiki/Timsort,该算法会尝试类似这样的内容,并且针对很多实际用例进行了精细调整。