Insertion Sort & Merge sort
---
Insertion sort
Attempt#1
每次将元素A[j]插入它之前的有序序列A[1 .. j-1]中,插入方式是与前一个相邻元素两两比较并交换直到到达正确位置
Complexity
显然复杂度主要来自于 compare 和 swap :
平均情况下,比较和交换均需要 Θ(n2),虽然可以通过binary search将比较部分改进为 Θ(nlogn),但由于交换采用相邻元素两两交换的方式,整个算法复杂度仍然为 Θ(n2 + nlogn)= Θ(n2)
下面考虑另一种排序方式:归并排序
Merge sort
Algo
=== devide&conquer ===
将数组A一分为二得到数组L和R
将L和R排序得到L‘和R’
合并L‘和R’得到排序后的数组A‘
递归执行...
方法merge(Elem[] L', Elem[] R')应接收两个已排序的数组 L’ 和 R‘
“合并”的过程需要设置两个工作指针,分别指向L’和R‘中待处理的元素,每确定一个元素位置则将指针向后移一位
merge的复杂度取决于工作指针的移动次数,假如某个数组(不妨以R’为例)的所有元素都比左边的最小值小,则左边数组工作指针不会移动,直到右侧工作指针扫描完整个R’。此时只需将左侧数组直接”连接“到R‘后即可得到结果,总共耗费 Θ(n/2)。
最坏的情况可见是左右工作指针交替移动直到遍历完所有元素,这时的时间复杂度为Θ(n)。
Complexity
设对长度为n的数组进行归并排序的时间复杂度为T(n),不难得出下列递推关系:
$$
T(n) = C_1 + 2.T(n/2) + C.n
$$
C1表示devide需要常数时间,C.n表示merge需要Θ(n),T(n/2)为对子数组归并排序(recursion)的耗时
现在来求解上面的递归式(recurrence solving):
因为有C.n在可以忽略常数项C1,即有
$$
T(n) = 2.T(n/2) + C.n
$$
从确定项C.n开始展开,T(n) = C.n + 2.T(n/2) = C.n + ( 2.( C.n/2 + 2(C.n/4 + T(n/4)) ) ) = ...
recursive tree如下:
可见全部展开后T(n)就等于所有结点值的和:
$$
T(n) = (1+log_2n).C.n = Θ(nlgn)
$$
Auxiliary space
现在考虑 merge sort 的空间复杂度,由于 merge() 需要接收两个有序数组 L’ 和 R‘ ,因此需要2*n/2 = n的辅助空间存放排序后的子数组 L’ 和 R‘,空间复杂度为 Θ(n)
可以看出 insertion sort 虽然在时间复杂度上表现不如 merge sort,但却在空间复杂度上却略胜一筹,其仅需要一个变量 temp 来辅助元素的两两交换,其空间复杂度为 Θ(1),我们称其为 原地工作
Tree for different recurrence
使用递归树可以更容易地看出算法所需时间 T(n),假设如下两个递归式,求算法的时间复杂度:
#1
计算过程:
$$
T(n) = C(1+2+4+\ ...\ +n/2+n ) = Cn(1+1/2+1/4+...)=Θ(n)
$$
#2
计算过程:
$$
T(n) = Cn2(1+1/2+1/4+...)=Θ(n2)
$$