归并排序(内部)总结
归并排序建立在归并操作(即将两个有序数组归并为一个更大的有序数组)上的一种有效的排序算法,该算法是分治法的一个非常典型的应用
我们常用的自顶向下归并排序分为两步:
I. 无序数组逐层递归分解为子数组->mergeSort()
II. 有序子数组逐层归并为有序数组->merge()
1.借助一个辅助数组的归并排序
一般的归并操作需要用到三个数组,以下这种方法虽然只用了两个数组,但也只是少了一次数组声明而已。
public class MergeSort {
private static int[] temp;//归并所需的辅助数组
//辅助数组也可以在方法的参数里声明
//原地归并的抽象化(升序)
public static void merge(int[] array, int lo, int mid, int hi){
int i = lo, j = mid + 1; //i,j分别代表左半边和右半边的开始
for(int k = lo; k <= hi; k++) {
temp[k] = array[k];//将array[lo...hi]复制到将temp[lo...hi]
}
for(int k = lo; k <= hi; k++) {//归并回到array[lo...hi]
//左半边用尽,取右半边元素
if (i > mid) array[k] = temp[j++];
//右半边用尽取左半边元素
else if(j > hi) array[k] = temp[i++];
//右半边的当前元素小于左半边的当前元素,取右半边元素
else if(temp[j] < temp[i]) array[k] = temp[j++];
//右半边的当前元素大于或等于左半边的当前元素,取左半边元素
else array[k] = temp[i++];
}
}
二分归并(递归实现)
//1.自顶向下的归并排序基于原地归并
public static void mergeSort(int[] array) {
temp = new int[array.length];//一次性分配空间
mergeSort(array, 0, array.length - 1);
}
public static void mergeSort(int[] array, int lo, int hi){
if(hi < lo) return;
int mid = lo + (hi - lo) / 2;//或(lo + hi) / 2
mergeSort(array, lo, mid);
mergeSort(array, mid + 1, hi);
merge(array, lo, mid, hi);
}
自顶向下归并:
特点:
- 是稳定的排序算法, 空间复杂度:O(
n
), 时间复杂度:O(
nlogn ) - 归并排序需要的比较次数是 12nlogn 到 nlogn 次
- 归并排序最多需要访问数组 6nlogn 次:每次归并最多访问数组 6n 次( 2n 次复制, 2n 次用来将排好序的元素移回,另外最多比较 2n 次)
归并排序的优化:
a.对小规模的数组可以使用插入排序减少递归的空间花费
b.可以在排序时,判断a[mid]是否小于a[mid+1],跳过merge()方法。因为子数组已经有序,所以a[mid]是前半部分的最大值,a[mid+1]是后半部分的最小值,就不用进行多余的归并操作
c.为了节省将元素复制到辅助数组作用的时间,可以在递归调用的每个层次交换原始数组与辅助数组的角色
另:没有任何的基于比较的算法保证使用少于 log(n!) ~ nlogn 次比较将长度为n的数组排序
2.自底向上的方法
一般我们习惯使用自顶向下的归并排序,但下面这种先逐步归并小数组的非递归方法也是可以的
//2.自底向上的归并
public static void mergeSortBU(int[] array) {
//进行logn次两两归并
int n = array.length;
temp = new int[n];
for(int sz = 1; sz < n; sz += sz) {//sz子数组大小
for( int lo = 0; lo < n - sz; lo += sz + sz) {//lo子数组下标
merge(array, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, n-1));
}
}
}
}
自底向上归并: