"归并"的含义是将两个或两个以上的有序列表组合成一个新的有序列表。归并排序也称合并排序,注意这里指的归并排序默认指二路归并排序。
基本思想
假设初始序列含有n个元素,则可看成n个有序的子序列,每个子序列的长度是1。然后两两归并,得到 ⌈ n / 2 ⌉ \lceil n/2 \rceil ⌈n/2⌉个长度为2或1的有序子序列;再两两归并,…,如此重复,直到得到一个长度为n的有序序列为止。(也即二路归并排序的实现)
伪代码实现
二路归并排序的核心操作是将列表中前后相邻的两个有序序列归并为一个有序序列。假设n个元素使用数组存储,二路归并排序的伪代码实现如下:
假设数组A[0...n-1]表示待排序数组
MergeSort(A[0...n-1])
// 回归条件:传入数组的长度为1
if(n<=1)
return
copy A[0...(n/2取下整)-1] to B[0...(n/2取下整)-1]
copy A[(n/2取下整)...n-1] to C[0...(n/2取下整)-1]
// 递推
MergeSort(B[0...(n/2取下整)-1])
MergeSort(C[0...(n/2取下整)-1])
Merge(B,C,A)
数组B[0...p-1]和C[0...q-1]表示已排序数组
A[0...p+q-1]表示待排序数组
Mege(B[0...p-1],C[0...q-1],A[0...p+q-1])
while i<p-1 && j<q-1 then
if(B[i]≤C[j]) then
A[k] = B[i]
i++
else
A[k] = C[j]
j++
k++
if(i == p) then
copy C[j...q-1] to A[k...p+q-1]
else
copy B[i...p-1] to A[k...p+q-1]
接下来考虑该算法的执行效率。根据伪代码实现,基于比较次数
C
(
n
)
C(n)
C(n)的递推关系式如下:
当
n
>
1
时
,
C
(
n
)
=
2
C
(
n
/
2
)
+
C
m
e
r
g
e
(
n
)
当n>1时,C(n) = 2C(n/2) + C_{merge}(n)
当n>1时,C(n)=2C(n/2)+Cmerge(n)
C
(
1
)
=
n
C(1) = n
C(1)=n
这里
C
m
e
r
g
e
(
n
)
C_{merge}(n)
Cmerge(n)表示合并阶段进行比较的次数。显然,最坏的情况下,
C
m
e
r
g
e
(
n
)
=
n
−
1
C_{merge}(n)=n-1
Cmerge(n)=n−1,所以其递推式为:
当
n
>
1
时
,
C
w
o
r
s
t
(
n
)
=
2
C
w
o
r
s
t
(
n
/
2
)
+
n
−
1
当n>1时,C_{worst}(n) = 2C_{worst}(n/2) + n-1
当n>1时,Cworst(n)=2Cworst(n/2)+n−1
根据分治法的效率求解,这里选择主方法求解。代入公式,可知a=2,b=2,
f
(
n
)
=
n
−
1
f(n)=n-1
f(n)=n−1,经计算
n
l
o
g
b
a
=
n
n{log_b^a} = n
nlogba=n。因为
n
−
1
n-1
n−1和
n
n
n多项式相等,所以根据主定理
T
(
n
)
=
θ
(
n
l
o
g
b
a
l
o
g
k
+
1
n
)
=
θ
(
n
l
o
g
n
)
T(n)= θ(n{log_b^alog^{k+1}n})=θ(nlogn)
T(n)=θ(nlogbalogk+1n)=θ(nlogn)。
这里给出java版本实现:
public class MergeSort {
public void sort(int[] array) {
if (array.length <= 1) {
return;
}
int halfLength = array.length/2;
halfLength += array.length % 2 == 0 ? 0 : 1;
int[] leftHalfArray = new int[halfLength];
copyArray(array, 0, leftHalfArray);
int[] rightHalfArray = new int[array.length - halfLength];
copyArray(array, halfLength, rightHalfArray);
sort(leftHalfArray);
sort(rightHalfArray);
merge(leftHalfArray, rightHalfArray, array);
}
private void merge(int[] leftHalfArray, int[] rightHalfArray, int[] dstArray) {
int i = 0, j = 0 ,k = 0;
while (i < leftHalfArray.length && j < rightHalfArray.length) {
if (leftHalfArray[i]<rightHalfArray[j]) {
dstArray[k] =leftHalfArray[i++];
} else {
dstArray[k] = rightHalfArray[j++];
}
k++;
}
if(i == leftHalfArray.length) {
while (j<rightHalfArray.length) {
dstArray[k++] = rightHalfArray[j++];
}
} else {
while (i<leftHalfArray.length) {
dstArray[k++] = leftHalfArray[i++];
}
}
}
private void copyArray(int[] srcArray, int beginSubscript, int[] dstArray) {
for(int i = 0; i < dstArray.length; i++) {
dstArray[i] = srcArray[i + beginSubscript];
}
}
}
更多归并排序的排序实现可以参考力扣。
需要说明的是,尽管归并排序的算法时间复杂度是
n
l
o
g
(
n
)
nlog(n)
nlog(n)(与快速排序时间复杂度相当),但是因为该算法需要线性的额外空间,且算法过于复杂,从而只具有理论上的意义。
参考
《算法设计与分析基础》 第三版 Anany Levitin 著 潘彦 译
《数据结构 严蔚敏 吴伟民 著
https://leetcode-cn.com/problems/sort-an-array/ 排序数组