使用合并排序算法所需的最小比较次数?

时间:2012-04-20 15:37:04

标签: java complexity-theory

对于那些熟悉合并排序的人,我试图找出合并两个大小为n / 2的子数组所需的最小比较次数,其中n是原始未排序数组中的项目数。

我知道算法的平均和最差情况时间复杂度为O(nlogn),但我无法确定所需比较的确切最小数量(就n而言)

3 个答案:

答案 0 :(得分:6)

合并步骤的最小比较次数大约是n/2(顺便说一下,它仍然是O(n)),假设一个列表完全遍历后,它就是一个理智的实现。

例如,如果正在合并两个实际已排序的列表,则将较大列表的第一个成员与较小的列表进行比较n/2次,直到它用完为止;然后可以复制较大的列表而无需进一步比较。

List 1    List 2    Merged List         Last Comparison
[1, 2, 3] [4, 5, 6] []                  N/A
[2, 3]    [4, 5, 6] [1]                 1 < 4
[3]       [4, 5, 6] [1, 2]              2 < 4
[]        [4, 5, 6] [1, 2, 3]           3 < 4
[]        [5, 6]    [1, 2, 3, 4]        N/A
[]        [6]       [1, 2, 3, 4, 5]     N/A
[]        []        [1, 2, 3, 4, 5, 6]  N/A

请注意,进行了3​​次比较,列表中有6名成员。

再次注意,即使在最好的情况下,合并步骤仍然有效地被视为O(n)。合并排序算法具有时间复杂度O(n*lg(n)),因为整个列表中的合并步骤为O(n),并且除法/合并发生在O(lg(n))递归级别。

答案 1 :(得分:3)

这个答案给出了一个确切的结果,而不仅仅是用Landau symbol写的渐近行为。

合并长度 m n 的列表至少需要min( m n )比较。原因是只有在完全处理了其中一个输入列表时才能停止比较元素,即您需要至少迭代两个列表中较小的一个。请注意,此次比较仅对某些输入足够,因此它假定可能的输入数据的最佳情况是最小的。对于最坏情况输入,您会发现更高的数字,即n ⌈lg n⌉ − 2⌈lg n⌉ + 1

n = 2 k 是2的幂。设 i 为合并级别,0≤ i &lt; ķ。在 i 级别执行2 k - i - 1 合并,每个合并需要2 < em> i 比较。将这两个数相乘可得到2 k - 1 比较,等于 n / 2。总结 k 的合并级别,你得到 nk / 2 =( n lg n )/ 2比较。

现在让 n 比2的幂小1。设 k =⌈lg n ⌉仍然表示合并级别的数量。与2 k 情况相比,您现在每个级别的比较少一个。因此合并总数减少了 k ,导致2 k k / 2 - k =(2 k / 2 - 1) k 比较。但是,如果再删除一个元素,导致 n = 2 k - 2,那么您将不会减少最顶层合并的数量,因为其他列表已经是较短的一个。这表明事情可能在这里变得更加困难。

所以让我们有一个小的演示程序,我们可以用它来检查我们之前的结果并计算其他值的比较次数:

mc = [0, 0]                                 # dynamic programming, cache previous results
k = 1                                       # ceil(lg n) in the loop
for n in range(2, 128):
    a = n // 2                              # split list near center
    b = n - a                               # compute length of other half list
    mc.append(mc[a] + mc[b] + min(a, b))    # need to sort these and then merge
    if (n & (n - 1)) == 0:                  # if n is a power of two
        assert mc[-1] == n*k/2              # check previous result
        k += 1                              # increment k = ceil(lg n)
print(', '.join(str(m) for m in mc))        # print sequence of comparison counts, starting at n = 0

这给出了以下顺序:

0, 0, 1, 2, 4, 5, 7, 9, 12, 13, 15, 17, 20, 22, 25, 28, 32, 33, 35,
37, 40, 42, 45, 48, 52, 54, 57, 60, 64, 67, 71, 75, 80, 81, 83, 85,
88, 90, 93, 96, 100, 102, 105, 108, 112, 115, 119, 123, 128, 130, 133,
136, 140, 143, 147, 151, 156, 159, 163, 167, 172, 176, 181, 186, 192,
193, 195, 197, 200, 202, 205, 208, 212, 214, 217, 220, 224, 227, 231,
235, 240, 242, 245, 248, 252, 255, 259, 263, 268, 271, 275, 279, 284,
288, 293, 298, 304, 306, 309, 312, 316, 319, 323, 327, 332, 335, 339,
343, 348, 352, 357, 362, 368, 371, 375, 379, 384, 388, 393, 398, 404,
408, 413, 418, 424, 429, 435, 441

您可以在On-Line Encyclopedia of Integer Sequences中查找该序列来描述total number of 1's in binary expansions of 0, ..., n。那里也有一些公式,但要么它们不精确(涉及一些Landau符号术语),要么它们依赖于其他一些非平凡的序列,或者它们非常复杂。我最喜欢的那个表达了我上面的程序:

  

a(0)= 0,a(2n)= a(n)+ a(n-1)+ n,a(2n + 1)= 2a(n)+ n + 1。 - Ralf Stephan,2003年9月13日

考虑到这些替代方案,我想我会坚持使用上面的脚本来计算这些数字。您可以删除断言以及与此相关的所有内容,依赖a < b这一事实,如果将其包含在更大的程序中,也可以删除输出。结果应如下所示:

mc = [0, 0]
for n in range(2, 1024):
    a = n // 2
    mc.append(mc[a] + mc[n - a] + a)

请注意,例如对于 n = 3,您只能进行两次比较。显然,只有将两个极值元素与中值元素进行比较,这才能起作用,这样您就不必再将极值元素相互比较了。这说明了为什么上述计算仅适用于最佳情况输入。最坏的情况输入会让你在某个时刻相互计算最小和最大元素,导致按n ⌈lg n⌉ − 2⌈lg n⌉ + 1公式计算的三个比较。

答案 2 :(得分:-1)

对于每次比较,您从两个列表中的一个中释放一个元素。因此,比较的次数最多是两个列表的长度之和。如Platinum所示,如果您到达一个数组的末尾并且另一个数组中仍有项目,则可能会更少。

因此,比较次数介于n/2n之间。