归并排序
public class Merge {
private static Comparable[] aux;
private Merge(){}
private static boolean isSorted(Comparable[] a){
for(int i=1,len=a.length;i<len;i++)
if(less(a[i],a[i-1])) return false;
return true;
}
private static boolean less(Comparable v,Comparable w){
return v.compareTo(w)<0;
}
private static void show(Comparable[] a){
for(int i=0,len=a.length;i<len;i++)
StdOut.print(a[i]+" ");
StdOut.println();
}
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])) a[k] = aux[j++];
else a[k] = aux[i++];
}
public static void sort(Comparable[] a){
int N = a.length;
aux = new Comparable[N];
sort(a,0,N-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);
}
public static void main(String[] args){
String[] a = new In(args[0]).readAllStrings();
sort(a);
assert isSorted(a);
show(a);
}
}
实现策略
实现归并的方法是将两个不同的有序数组归并到第三个数组中通过创建一个额外的数组,将两个数组中较小的值一个一个的放到新数组中,对大数组进行递归变为一个一个小的数组进行归并排序。归并使用原地归并的方式,不需要每次Merge操作都创建额外的驻足,只需要在排序初始创建一个数组大小的空间,一次性分配aux = new Comparable[N]
。
分析
假设对16个元素进行排序,会先对左边a[0…7]排序(sort(lo,mid)),然后对a[0…3]进行排序,然后对a[0…1]排序,然后对a[0]和a[1]进行归并操作(merge(a,lo,mid,hi)),然后对a[2]和a[3]进行归并,在对a[0…1]和a[2…3]进行归并,以此类推。
以图中树状图按照成本模型:
已知条件:
- 每次
merge
操作会进行数组长度次比较操作 - N个数会形成2n层树->n = lgN
按照成本模型比较次数进行分析,如代码可知。假设具有n层,第k层对应的数组个数有2k个,每个数组的长度是2n-k,固会进行2n-k次比较,每层会进行2n次比较,具有n层,则会进行总共n2n=NlgN次比较
改进
在merge中添加if(less(a[mid],a[mid+1]))
当已经有序的情况不在进行归并操作。
自底向上的归并排序
前文列出的排序是自顶向下的归并排序,将大数组变为一个一个小数组进行排序,是算法中分治思想的使用,而自底向上的归并排序更加的自然、循序渐进。按照循环的方法处理。
// 自底向上的归并排序
public static void sortFromBottmToTop(Comparable[] a){
int N = a.length;
aux = new Comparable[N];
for(int sz=1;sz<N;sz*=2)// 1个和一个归并 然后两个和两个归并....
for(int lo=0;lo<N-sz;lo=lo+sz+sz)
merge(a,lo,lo+sz-1,Math.min(lo+sz+sz-1,N-1));
}