【发布时间】:2011-12-09 18:04:59
【问题描述】:
谁能用简单的英语或简单的方式向我解释一下?
【问题讨论】:
-
观看排序算法总是很有趣:sorting-algorithms.com
谁能用简单的英语或简单的方式向我解释一下?
【问题讨论】:
合并排序使用分而治之的方法来解决排序问题。首先,它使用递归将输入分成两半。划分后,它对一半进行排序并将它们合并为一个排序输出。见图
这意味着最好先对一半的问题进行排序,然后执行一个简单的合并子程序。因此,了解合并子程序的复杂性以及在递归中会被调用多少次是很重要的。
归并排序的伪代码非常简单。
# 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 的部分。第二遍后,长度为四。然后是八、十六等,直到文件的大小。
有必要将已排序部分的大小不断增加一倍,直到有一个部分包含整个文件。需要 lg(N) 倍的节大小才能达到文件大小,并且每次传递数据所需的时间与记录数成正比。
【讨论】:
这是因为无论是最坏情况还是平均情况,合并排序只是在每个阶段将数组分成两半,这给它 lg(n) 分量,而另一个 N 分量来自它在每个阶段进行的比较.因此,将其组合起来几乎是 O(nlg n)。无论是平均情况还是最坏情况,lg(n) 因子始终存在。剩余 N 因子取决于在两种情况下进行的比较所进行的比较。现在最坏的情况是在每个阶段对 N 个输入进行 N 次比较。所以它变成了 O(nlg n)。
【讨论】:
许多其他答案都很棒,但我没有看到与“合并排序树”示例相关的 height 和 depth 的任何提及。这是解决问题的另一种方法,重点放在树上。这是另一个帮助解释的图像:
只是一个回顾:正如其他答案所指出的那样,我们知道合并序列的两个排序切片的工作在线性时间内运行(我们从主排序函数调用的合并辅助函数)。
现在看这棵树,我们可以将根(除了根)的每个后代视为对排序函数的递归调用,让我们尝试评估我们在每个节点上花费了多少时间......因为切片序列和合并(都在一起)需要线性时间,任何节点的运行时间相对于该节点的序列长度是线性的。
这里是树深度的用武之地。如果n是原始序列的总大小,那么序列在任何节点的大小都是n/2i,其中i是深度。如上图所示。将其与每个切片的线性工作量放在一起,树中每个节点的运行时间为 O(n/2i)。现在我们只需对 n 个节点进行总结。一种方法是识别树中每个深度级别有 2i 个节点。所以对于任何级别,我们都有 O(2i * n/2i),这是 O(n),因为我们可以抵消 2is!如果每个深度为 O(n),我们只需将其乘以二叉树的 高度,即 logn。答案:O(nlogn)
【讨论】:
递归树的深度为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)
【讨论】:
MergeSort 算法需要三个步骤:
该算法需要大约 logn 次来对 n 个元素的数组进行排序,因此总时间复杂度为 nlogn。
【讨论】: