目录
一、 原始的归并排序
归并排序的主算法:
public void mergeSort(int[] T, int low, int high) {
if (high - low < 2) {
return;
}
int mid = (low + high) >> 1;
mergeSort(T, low, mid);
mergeSort(T, mid, high);
merge(T, low, mid, high);
}
下面是merge过程的一种简单实现:
private void merge(int[] T, int low, int mid, int high) {
int[] tempArray = new int[mid-low];
// 先把左侧的部分拷贝到临时数组中
for (int i=low; i<mid; i++) {
tempArray[i-low] = T[i];
}
// merge T[mid, high) & tempArray[0, mid-low)
int i = low, j = mid, k = 0;
while (j < high && k < mid-low) {
if (T[j] < tempArray[k]) {
T[i++] = T[j++];
} else {
T[i++] = tempArray[k++];
}
}
// 拷贝临时数组中剩余的元素
while (k<mid-low) {
T[i++] = tempArray[k++];
}
}
虽然上面的merge过程还能继续优化,但总的来说,这类方法都必须有一个额外的数组来辅助merge过程,该数组的长度至少为n/2。
因此归并排序的空间复杂度为O(n),时间复杂度为O(n*logn)。
二、 一种新的merge方法
下面提出一种只需O(1)空间的merge算法。
2.1 不变性
2.1.1 区域划分
在merge过程中,始终将整个数组划分为4个区域,如下图所示:
其中,
- S区的范围是 [low, a),在图中是白色 。S区的元素均为已经就位的元素,即已经摆放到正确位置上的元素。
- A区的范围是[a, b),在图中是红色。
- B区的范围是[b, c),在图中是绿色。
- C区的范围是[c, high),在图中是蓝色。
2.1.2 有序性
在每一个区域中,元素都是从小到大排列的。
2.2 初始化
从merge函数的定义来看:
merge(int[] T, int low, int mid, int high)
是将 T[low, mid) 和 T[mid, high) 这两个有序的部分合并成一个有序的数组
因此,我们可以初始化成下图中的形式:
此时,只有A和C两个区域,而S区和B区都是空的。
2.3 迭代
不断的比较T[a], T[b], T[c] 三个元素的大小,选出其中的最小值。
这里先不讨论元素相等的情况(在代码实现部分讨论等号),因此只可能有如下3种情况:
2.3.1 T[c]最小
此时,先将a和c位置上的元素交换,然后再执行 a++, c++。操作完成后,S区中的元素增加一个,A区元素减少一个,
B区元素增加一个,C区元素减少一个。
这是唯一能让B区元素增加的情况。
2.3.2 T[b]最小
此时,我们先递归调用merge(b, c, high) 来将B区和C区两个有序数组合并成一个有序数组。
再执行 c=b。操作完成后,B区元素全部清零。
2.3.3 T[a]最小
T[a]最小时,B区一定不存在($2.3.5),即 b==c。
此时,执行操作 a++ 。操作完成后,S区中的元素就增加了一个,A区中的元素减少一个。
2.3.4 A区消失和C区消失
因为迭代过程中执行了a++和c++操作,会导致A区元素和C区元素减少,当区域中元素数量减少到0时,该区域就消失了。
此时可以做如下处理:
操作完成后,又回到了初始状态:未就位的部分只剩下A区和C区。
2.3.5 为什么T[a]最小时,B区一定不存在 ?
由2.3.1节可知,B区元素增加的唯一途径是——从A区的开头交换元素到B区的末尾。
由于A区原本就是有序的,因此B区中的所有元素都会小于或等于A区中的第一个元素,
在不考虑等号的情况下,"T[a]为T[a]、T[b]、T[c]中的最小值"这一现象说明T[a] < T[b],
这与上面的结论矛盾,因此B区一定不存在。
2.4 终止
当 A、B、C三个区域其中两个为空时,数组整体有序,算法终止。
三、代码实现
/**
* Description: 归并排序
*
* @author zouxiang
* @date 2020/10/26
*/
public class MergeSort {
public void mergeSort(int[] T) {
mergeSort(T, 0, T.length);
}
public void mergeSort(int[] T, int low, int high) {
if (high - low < 2) {
return;
}
int mid = (low + high) >> 1;
mergeSort(T, low, mid);
mergeSort(T, mid, high);
merge(T, low, mid, high);
}
protected void merge(int[] T, int low, int mid, int high) {
int a = low, b = mid, c = mid;
while (a != c) { // A、B、C中至少存在两个区域
if (a == b) { // A区不存在
b = c;
} else if (T[b] < T[c]) { // T[b]最小
merge(T, b, c, high);
c = b;
} else if (T[a] <= T[c]) { // T[a] 最小
a++;
} else { // T[c]最小
swap(T, a++, c++);
if (c == high) { // C区不存在
c = b;
}
}
}
}
private void swap(int[] T, int x, int y) {
int temp = T[x];
T[x] = T[y];
T[y] = temp;
}
}
四、性能
下面的复杂度是根据实验结果来估计的。
从实验结果来看,这个merge过程的时间复杂度应该是 O(N*logN),因此整个归并排序的时间复杂度不会超过 O(N*logN*logN)
空间上,新的merge过程已经不需要在堆上的额外数组空间的分配。但是在栈上的调用深度为O(logN),与归并排序的主算法递归深度一样;且当mergeSort深度约大时,merge的深度就越小。