如何在O(n * log(k))中将排序列表合并为单个列表

时间:2019-02-14 12:59:28

标签: algorithm sorting

(我将其作为面试问题,希望在此方面有所帮助。)

您有k个排序列表,总共包含n个不同的数字。
显示如何创建一个包含O(n * log(k))

中的k个列表中所有元素的单个排序列表

3 个答案:

答案 0 :(得分:4)

想法是使用大小为 k min heap

将所有 k 列表推入堆中(每个列表一个堆条目),并以其最小值(即第一个值)作为键

然后重复执行此操作:

  1. 从堆中提取顶部列表(具有最小键)
  2. 从该列表中提取最小值并将其推入结果列表
  3. 将缩短的列表(如果不为空)推回到堆上,现在以其新的最小值为键

重复此操作,直到将所有值都推入结果列表为止。

初始步骤的时间复杂度为 O(klogk)

上面的3个步骤将重复 n 次。在每次迭代中,每个的成本为:

  1. O(1)
  2. O(1)(如果提取是使用指针/索引(不移动列表中的所有值)实现的)
  3. O(log k),因为堆大小永远不会大于k

因此得出的复杂度为 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