【问题标题】:Why is merge sort worst case run time O (n log n)?为什么归并排序最坏情况运行时间为 O (n log n)?
【发布时间】:2011-12-09 18:04:59
【问题描述】:

谁能用简单的英语或简单的方式向我解释一下?

【问题讨论】:

标签: algorithm mergesort


【解决方案1】:

合并排序使用分而治之的方法来解决排序问题。首先,它使用递归将输入分成两半。划分后,它对一半进行排序并将它们合并为一个排序输出。见图

这意味着最好先对一半的问题进行排序,然后执行一个简单的合并子程序。因此,了解合并子程序的复杂性以及在递归中会被调用多少次是很重要的。

归并排序的伪代码非常简单。

# C = output [length = N]
# A 1st sorted half [N/2]
# B 2nd sorted half [N/2]
i = j = 1
for k = 1 to n
    if A[i] < B[j]
        C[k] = A[i]
        i++
    else
        C[k] = B[j]
        j++

很容易看出,在每个循环中都会有 4 个操作:k++、i++ 或 j++、if声明和属性 C = A|B。因此,您将进行少于或等于 4N + 2 次操作,从而产生 O(N) 复杂性。为了证明,4N + 2 将被视为 6N,因为对于 N = 1 (4N +2 ) 是正确的。

因此,假设您有一个包含 N 元素的输入,并假设 N 是 2 的幂。在每个级别,您有两倍于半个元素的输入的子问题从之前的输入。这意味着在级别 j = 0, 1, 2, ..., lgN 将有 2^j 个输入长度为 N 的子问题/2^j。每个级别j的操作数将小于或等于

2^j * 6(N / 2^j) = 6N

请注意,您将始终拥有少于或等于 6N 次操作的级别并不重要。

由于有lgN+1层,复杂度会是

O(6N * (lgN + 1)) = O(6N*lgN + 6N) = O(n lgN)

参考资料:

【讨论】:

  • 为什么第一个n是小写,第二个N是大写?这有什么意义吗?
  • 也许我很糟糕,但2^j * 6(N / 2^j) = 6N 还有 2 个操作。好吧,它们并不重要,但在这种情况下,它应该看起来像:2^j * 6(N / 2^j) + 2 = 6N,正如你所说,将有更少或等于 6N 次操作
【解决方案2】:

在“传统”合并排序中,每次通过数据都会使已排序子节的大小加倍。第一遍之后,文件将被分类为长度为 2 的部分。第二遍后,长度为四。然后是八、十六等,直到文件的大小。

有必要将已排序部分的大小不断增加一倍,直到有一个部分包含整个文件。需要 lg(N) 倍的节大小才能达到文件大小,并且每次传递数据所需的时间与记录数成正比。

【讨论】:

  • 这是一个很好的答案,但在我得到它之前我必须阅读它几次。一个例子可能会有所帮助......例如假设您有一个包含 8 个数字的列表。您将它们拆分为长度为 1 的子列表。将需要 3 次加倍或 lg(8) 才能获得 8 个成员长的列表。在 Word 情况下,您必须检查每个子列表的每个成员一次,这意味着对于所有三个加倍,您必须检查所有 8 个值。
  • 我刚刚加了这样一个例子;你更喜欢那个吗?
  • 在这种情况下不应该是 log2N。比如回答 2 乘以 2 得到 N。所以我们得到 2、4、8、16。宾果游戏是 3 次
  • @xpioneer:如果一个操作需要时间 O(f(N)) 来完成,这意味着有一对常数 n' 和 k 使得对于所有 N >= n',时间将小于 kf(n)。由于 N 的以 2 为底的对数是自然对数的常数倍,因此 O(Nlg(N)) 和 O(Nln(N)) 是等价的。
  • @supercat。知道了 。在其他任何地方都找不到更好的解释。
【解决方案3】:

将数组拆分到具有单个元素的阶段后,即称它们为子列表,

  • 在每个阶段,我们将每个子列表的元素与其相邻的子列表进行比较。例如,[重用@Davi 的图像 ]

    1. 在第 1 阶段,每个元素都会与其相邻元素进行比较,因此进行 n/2 次比较。
    2. 在第 2 阶段,将子列表的每个元素与其相邻的子列表进行比较,因为每个子列表都已排序,这意味着两个子列表之间的最大比较次数是 n/2
    3. 如您所见,总阶段数为log(n) base 2 所以总复杂度将是 == (每个阶段的最大比较数 * 阶段数) == O((n/2)*log(n)) ==> O(nlog(n))

【讨论】:

    【解决方案4】:

    这是因为无论是最坏情况还是平均情况,合并排序只是在每个阶段将数组分成两半,这给它 lg(n) 分量,而另一个 N 分量来自它在每个阶段进行的比较.因此,将其组合起来几乎是 O(nlg n)。无论是平均情况还是最坏情况,lg(n) 因子始终存在。剩余 N 因子取决于在两种情况下进行的比较所进行的比较。现在最坏的情况是在每个阶段对 N 个输入进行 N 次比较。所以它变成了 O(nlg n)。

    【讨论】:

      【解决方案5】:

      算法合并排序在 O(n log n) 中对大小为 n 的序列 S 进行排序 时间,假设 S 的两个元素可以在 O(1) 时间内进行比较。

      【讨论】:

        【解决方案6】:

        许多其他答案都很棒,但我没有看到与“合并排序树”示例相关的 height 和 depth 的任何提及。这是解决问题的另一种方法,重点放在树上。这是另一个帮助解释的图像:

        只是一个回顾:正如其他答案所指出的那样,我们知道合并序列的两个排序切片的工作在线性时间内运行(我们从主排序函数调用的合并辅助函数)。
        现在看这棵树,我们可以将根(除了根)的每个后代视为对排序函数的递归调用,让我们尝试评估我们在每个节点上花费了多少时间......因为切片序列和合并(都在一起)需要线性时间,任何节点的运行时间相对于该节点的序列长度是线性的。

        这里是树深度的用武之地。如果n是原始序列的总大小,那么序列在任何节点的大小都是n/2i,其中i是深度。如上图所示。将其与每个切片的线性工作量放在一起,树中每个节点的运行时间为 O(n/2i)。现在我们只需对 n 个节点进行总结。一种方法是识别树中每个深度级别有 2i 个节点。所以对于任何级别,我们都有 O(2i * n/2i),这是 O(n),因为我们可以抵消 2is!如果每个深度为 O(n),我们只需将其乘以二叉树的 高度,即 logn。答案:O(nlogn)

        参考:Data Structures and Algorithms in Python

        【讨论】:

        • 很好的解释!谢谢。
        • 这绝对是我在这个话题上看到的最好的答案。简短,简单,逻辑一致。谢谢!
        • 为了清楚起见和额外的上下文,当他说:“这棵二叉树的高度,即 logn” 时(正如他指出的那样),给定深度的节点总数为 2^i - 其中 i 从 0 开始。本质上:2^i = nodes(N) ``` / / 取两边的 log(base 2) log(2^i) = logN; i*log2 = logN; // log 2 = 1 因为 2^1 = 2;因此: i = 树的高度 = logN ```
        【解决方案7】:

        递归树的深度为log(N),并且在该树的每一层,您将合并N 工作以合并两个排序数组。

        合并排序数组

        要合并两个排序数组A[1,5] 和B[3,4],您只需从头开始迭代,选择两个数组之间的最低元素并递增该数组的指针。当两个指针都到达各自数组的末尾时,您就完成了。

        [1,5] [3,4]  --> []
         ^     ^
        
        [1,5] [3,4]  --> [1]
           ^   ^
        
        [1,5] [3,4]  --> [1,3]
           ^     ^
        
        [1,5] [3,4]  --> [1,3,4]
           ^      x
        
        [1,5] [3,4]  --> [1,3,4,5]
            x     x
        
        Runtime = O(A + B)
        

        合并排序图解

        您的递归调用堆栈将如下所示。工作从底部的叶子节点开始,然后冒泡。

        beginning with [1,5,3,4], N = 4, depth k = log(4) = 2
        
          [1,5]    [3,4]     depth = k-1 (2^1 nodes) * (N/2^1 values to merge per node) == N
        [1]  [5]  [3]  [4]   depth = k   (2^2 nodes) * (N/2^2 values to merge per node) == N
        

        因此,您在树中的每个 k 级别上执行 N 工作,其中 k = log(N)

        N * k = N * log(N)

        【讨论】:

        • 这样的排序算法有具体的名称吗?我的意思是您解释的排序算法恰好需要 O(A + B) 时间。
        • @Poream3387 我不知道官方名称,但该算法用于“合并排序列表”
        【解决方案8】:

        MergeSort 算法需要三个步骤:

        1. 除法步骤计算子数组的中间位置,它需要恒定的时间 O(1)。
        2. 征服步骤递归地对两个大约 n/2 个元素的子数组进行排序。
        3. Combine 步骤在每次通过时合并总共 n 个元素,最多需要 n 次比较,因此需要 O(n)。

        该算法需要大约 logn 次来对 n 个元素的数组进行排序,因此总时间复杂度为 nlogn。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-05-04
          • 1970-01-01
          • 2019-11-19
          • 1970-01-01
          • 2014-08-02
          • 2021-06-12
          • 2016-01-20
          • 1970-01-01
          相关资源
          最近更新 更多