归并排序在平均、最好、最差的时间复杂度都是O(nlogn),所以是非常稳定的算法,然而在归并的过程中需要额外的空间开销,空间复杂度为O(n)。但目前的计算机内存、硬盘的空间都很大,相较于空间开销,更加注重于时间的开销,所以归并排序还是值得我们学习的。
一、算法思想
归并排序主要体现的是分治的思想,每次都将数列一分为二,当分到最底层时再层层回溯归并,最终将每层的有序子数列归并为完全有序数列。
1)如图,每一层都不断将数组一分为二,直到只有一个元素;
2)分别将[8,6],[2,3],[1,5],[7,4]各自为一组进行归并,得到有序子数列[6,8],[2,3],[1,5],[4,7];
3)将有序子数列[6,8],[2,3]为一组进行归并,得到新的有序子数列[2,3,6,8];将有序子数列[1,5],[4,7] 为一组进行归并,得到新的有序子数列[1,4,5,7] ;
4)将有序子数列[2,3,6,8]和[1,4,5,7]进行归并,得到最终有序数列[1,2,3,4,5,6,7,8]。
二、归并过程
1)首先开辟一个和原数组同样大小的空间,指针 k(即蓝色箭头)指向新的空间,指针 i 指向左有序子序列,指针 j 指向右有序子序列;
2)比较 arr[i] 和 arr[j] 的大小,在升序的情况下,若左子序列当前值大于右子序列当前值,即 arr[i] > arr[j],则把右子序列当前值赋给辅助空间当前值,即 aux[k] = arr[j],然后右子序列、辅助空间指针后移;
3)继续比较 arr[i] 和 arr[j],若左子序列当前值小于右子序列当前值,即 arr[i] < arr[j],则把左子序列当前值赋给辅助空间当前值,即 aux[k] = arr[i],然后左子序列、辅助空间指针后移,以此类推,至到排序结束。
三、代码实现
public class Merge{
public int[] sort(int[] arr, int n) {
mergeSort(arr, 0, n - 1);
return arr;
}
//对arr[l,r]进行归并
static void mergeSort(int[] arr, int l, int r){
//当个数小于11时,采用插入排序
if(r - l <= 10){
insertion(arr, l, r);
return;
}
int mid = (l + r) / 2;
mergeSort(arr, l, mid);
mergeSort(arr, mid + 1, r);
//当左边最大的元素大于右边最小的元素时才需要进行归并
if(arr[mid] > arr[mid + 1]){
merge(arr, l, mid, r);
}
}
//将arr[l, mid]和arr[mid+1, r]进行合并
static void merge(int[] arr, int l, int mid, int r){
//开辟辅助空间
int[] aux = new int[r - l + 1];
for (int i = l; i <= r; i++){
aux[i - l] = arr[i];
}
int t = l;
int p = mid + 1;
for (int i = l; i <=r; i++){
if(t > mid){ //左边的已经遍历完成
arr[i] = aux[p - l];
p++;
} else if(p > r){ //右边的已经遍历完成
arr[i] = aux[t - l];
t++;
} else if(aux[t - l] > aux[p - l]){
arr[i] = aux[p - l];
p++;
} else {
arr[i] = aux[t - l];
t++;
}
}
}
//对arr[l,r]进行插入排序
static void insertion(int[] arr, int l, int r){
for (int i = l + 1; i <= r; i++){
int copy = arr[i];
int j;
for (j = i; j - l > 0 && copy < arr[j - 1]; j--){
arr[j] = arr[j - 1];
}
arr[j] = copy;
}
}
}