前言:算法第四版2.2节 归并排序学习总结
归并排序:将两个有序的数组归并成一个更大的有序数组。采用分治(divide and conquer)策略,利用递归每次将数组分成两半,直到子数组个数为1(1个元素的数组自然就有序的),将结果归并再返回。
性能:所需时间与NlogN成正比(N为数组大小);所需额外空间与N成正比。当所有元素都相同时,归并排序的运行时间是线性的。
归并两个有序数组的merge()方法:
private static void merge(Comparable[] a, int lo, int mid, int hi){
int i = lo, j = mid+1;
for(int k = lo; k <= hi; k++){
aux[k] = a[k];
}
for(int k = lo; k<= hi; k++){
if(i>mid)
a[k] = aux[j++];
else if(j>hi)
a[k] = aux[i++];
else if(aux[j] < aux[i]))
a[k] = aux[j++];
else
a[k] = aux[i++];
}
}
先将元素复制到辅助数组aux[ ]中,四个判断:左半边耗尽时,将右版边剩余的直接复制到a中;右半边耗尽时,将左半边剩余的直接复制到a中;右半边元素小于左半边时,复制右半边的元素;右半边的元素大于等于左半边的元素时,复制左半边的元素(保证了稳定性)。示例如下:
分治所需时间复杂度的证明:
D (N) = 2 D (N / 2) + N,其中 D( 1 ) = 0,证明D (N) = N lg N
1.图解法
2.逐步扩展法
3.归纳法
自顶向下的归并排序(标准递归):
利用递归:一分为二,再一分为二,直到分到1,再return
public class MergeSort {
private static Comparable[] aux; //辅助数组
public static void sort(Comparable[] a){
aux = new Comparable[a.length];
sort(a,0,a.length-1);
}
private static void sort(Comparable[] a,int lo, int hi){
if(lo>=hi)
return;
int mid = lo + (hi -lo) / 2;
sort(a,lo,mid); //左半排序
sort(a,mid+1,hi); //右半排序
merge(a,lo,mid,hi); //归并
}
private static void merge(Comparable[] a, int lo, int mid, int hi){
int i = lo, j = mid+1;
for(int k = lo; k <= hi; k++){
aux[k] = a[k];
}
for(int k = lo; k<= hi; k++){
if(i>mid)
a[k] = aux[j++];
else if(j>hi)
a[k] = aux[i++];
else if(less(aux[j],aux[i])) //将aux的元素归并到a中
a[k] = aux[j++];
else
a[k] = aux[i++];
}
}
private static boolean less(Comparable v, Comparable w) {
return v.compareTo(w) < 0;
}
private static void exch(Comparable[] a, int i, int j) {
Comparable t = a[i];
a[i] = a[j];
a[j] = t;
}
public static void show(Comparable[] a){
for (int i = 0; i < a.length; i++) {
System.out.print(a[i] + " ");
}
System.out.println();
}
public static boolean isSorted(Comparable[] a){
for (int i = 1; i < a.length; i++) {
if(less(a[i], a[i-1]))
return false;
}
return true;
}
}
自底向上的归并排序:
先归并小数组,再成对归并得到的子数组,依次往上。size=1,2,4,8,16...,其代码量比标准递归小。
public static void sort(Comparable[] a){
int N = a.length;
aux = new Comparable[a.length];
for (int size = 1; size < N; size = size+size) {
for(int lo = 0; lo < N-size; lo += size+size){
merge(a, lo, lo+size-1, Math.min(lo+size+size-1, N-1));
}
}
}
不使用静态数组来辅助:
由于静态变量是对象共享的,如果有多个程序同时在用这个类,就会出错,所以将辅助数组作为参数传递。
public static void sort(Comparable[] a){
Comparable[] aux = new Comparable[a.length];
sort(a,aux,0,a.length-1);
}
private static void sort(Comparable[] a, Comparable[] aux,int lo, int hi){
if(lo>=hi)
return;
int mid = lo + (hi -lo) / 2;
sort(a,aux,lo,mid);
sort(a,aux,mid+1,hi);
merge(a,aux,lo,mid,hi);
}
private static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi){
int i = lo, j = mid+1;
for(int k = lo; k <= hi; k++){
aux[k] = a[k];
}
for(int k = lo; k<= hi; k++){
if(i>mid)
a[k] = aux[j++];
else if(j>hi)
a[k] = aux[i++];
else if(less(aux[j],aux[i]))
a[k] = aux[j++];
else
a[k] = aux[i++];
}
}
三项改进:
1. 如果子数组较小(7左右),则采用速度更快的插入排序;
2. 检测待归并的两个子数组是否已经有序,如果已经有序则直接复制进a[ ];
3. 通过再递归中交换参数来避免每次归并时都要复制数组到辅助数组。
private static final int CUTOFF = 7;
public static void sort(Comparable[] a){
// Comparable[] aux = new Comparable[a.length];
Comparable[] aux = a.clone(); //初始化aux,为了后面把它当原始数组a[]用
sort(aux,a,0,a.length-1);
}
private static void sort(Comparable[] a, Comparable[] aux,int lo, int hi){
int mid = lo + (hi -lo) / 2;
// if(hi <= lo)
// return;
if(hi <= lo + CUTOFF - 1){ //小数组使用插入排序
insertionSort(a, lo, hi);
return;
}
sort(aux,a,lo,mid);
sort(aux,a,mid+1,hi);
if(!less(a[mid+1],a[mid])){ //如果数组已经有序则直接复制,不再merge
System.arraycopy(aux, lo, a, lo, hi-lo+1);
return;
}
merge(a,aux,lo,mid,hi);
}
private static void insertionSort(Comparable[] a, int lo, int hi) {
for (int i = lo; i <= hi; i++) {
for(int j = i; j > lo && less(a[j],a[j-1]); j--)
exch(a,j,j-1);
}
}
private static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi){
int i = lo, j = mid+1;
/*for(int k = lo; k <= hi; k++){
aux[k] = a[k];
}
for(int k = lo; k<= hi; k++){
if(i>mid)
a[k] = aux[j++];
else if(j>hi)
a[k] = aux[i++];
else if(less(aux[j],aux[i]))
a[k] = aux[j++];
else
a[k] = aux[i++];
} */
//消除数组复制
for(int k = lo; k<= hi; k++){
if (i > mid) aux[k] = a[j++];
else if(j > hi) aux[k] = a[i++];
else if(less(a[j],a[i])) aux[k] = a[j++];
else aux[k] = a[i++];
}
}
快速归并:
这里修改merge()方法,目的是省掉内循环中总是判断某半边是否耗尽,做法是按索引值降序将a[ ]的后半部分复制到aux[ ]中,再去归并。
private static void merge(Comparable[] a,Comparable[] aux, int lo, int mid, int hi){
for(int k = lo; k <= mid; k++)
aux[k] = a[k];
for(int k = mid+1; k <= hi; k++)
aux[k] = a[hi+mid+1-k];
int i = lo, j = hi; //由两边向中间比较
for(int k=lo;k<=hi; k++ ){
if(less(aux[j],aux[i]))
a[k] = aux[j--];
else
a[k] = aux[i++]; //保证稳定性
}
}