(我将其作为面试问题,希望在此方面有所帮助。)
您有k
个排序列表,总共包含n
个不同的数字。
显示如何创建一个包含O(n * log(k))
答案 0 :(得分:4)
想法是使用大小为 k 的min heap。
将所有 k 列表推入堆中(每个列表一个堆条目),并以其最小值(即第一个值)作为键
然后重复执行此操作:
重复此操作,直到将所有值都推入结果列表为止。
初始步骤的时间复杂度为 O(klogk)。
上面的3个步骤将重复 n 次。在每次迭代中,每个的成本为:
因此得出的复杂度为 O(nlogk)(因为 k < n ,初始步骤并不重要)。
答案 1 :(得分:1)
正如问题所指出的,不需要k路合并(或堆)。重复使用的标准2方式合并将以任意顺序合并成对的列表,直到生成单个排序的列表也将具有时间复杂度O(n log(k))。如果问题是询问如何单次合并k个列表,则需要进行k次合并。
考虑k == 32的情况,并且为了简化数学,假设所有列表按顺序合并,以便每个合并遍历合并所有n个元素。第一次通过后,有k / 2个列表,第二次通过后,有k / 4个列表,在log2(k)= 5个通过之后,所有k(32)个列表都合并为一个排序列表。除了简化数学之外,列表的合并顺序也没有关系,时间复杂度保持为O(n log2(k))。
仅当使用外部设备(例如一个或多个磁盘驱动器(或经典用法的磁带驱动器))合并数据时,使用k向合并通常是有利的,其中外部设备的I / O时间足够长,因此堆开销可能很大。忽略了。对于基于ram的合并/合并排序,对于2向合并/合并排序或k向合并/合并排序,操作总数大致相同。在具有16个寄存器的处理器上,其中大多数都用作索引或指针,优化的(无堆)4路合并(使用8个寄存器作为指向每次运行的当前位置和结束位置的索引或指针)可以快一点比2路合并的原因在于对缓存更友好。
答案 2 :(得分:0)
在N=2
时,通过迭代弹出最小的列表前面来合并两个列表。在某种程度上,您将创建一个虚拟列表,该列表支持通过以下方式实现的pop_front
操作:
pop_front(a, b): return if front(a) <= front(b) then pop_front(a) else pop_front(b)
您可以很好地安排类似树的合并方案,其中将这些虚拟列表成对合并:
pop_front(a, b, c, d): return if front(a, b) <= front(c, d) then pop_front(a, b) else pop_front(c, d)
每个流行音乐都会涉及到树中的每个级别一次,导致每次流行音乐花费O(Log k)
。
上面的推理是错误的,因为它不考虑front
操作,它涉及两个元素之间的比较,这将级联并最终每个输出元素总共需要k-1
比较。
可以通过“记住”前元素来避开此问题,即在进行比较后将其保留在两个列表的旁边。然后,当弹出一个元素时,将更新此前端元素。
这直接导致了二进制最小堆设备,如@trincot所建议。
5 7 32 21
5
6 4 8 23 40
2
7 7 20 53
2
2 4 6 8 10