归并排序
所讨论的算法都基于归并这个简单的操作,即将两个有序的数组归并称一个更大的有序数组,很快人们就根据这个操作发明了一种简单的递归排序算法,归并排序,要将一个数组排序,可以先递归地将它分成两半分别排序,然后将结果归并起来;
归并排序的性质:
- 它能够保证任意长度为N的数组排序所需时间和NlogN成正比;
- 缺点:所需额外空间和N成正比;
归并示意图
M E R G E S O R T E X A M P L E
E E G M O R R S
A E E L M P T X
A E E E E G L M M O P R R S T X
原地归并的抽象方法
实现归并的一种直截了当的办法就是将两个有序的数组存储至第三个数组中,连个数组的元素都应该实现了Comparable;
但是,当用归并排序将一个很大的数组进行排序时,我们可能需要多次的归并了,因此在每次归并时,都创建一个新数组来存储哦排序结果时,会带来问题,我们更希望一种能够在原地(不创建新数组)归并的方法,这样就可以先将前半部分排序,再将后半部分排序,然后在数组中移动元素,不需要耗费额外的内存空间;
下述代码中,
a分为两个有序数组组成,从lo到hi,
lo~mid为有序数组1
mid+1~hi为有序数组2
public static void merge(Comparable[] a,int lo , int mid , int hi){
int i = lo,j=mid+1;
Comparable[] aux = new Comparable[a.length];
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++];
}
}
自上而下的归并排序
代码:
private static Comparable[] aux;
public static void merge(Comparable[] a,int lo , int mid , int hi){
int i = lo,j=mid+1;
aux = new Comparable[a.length];
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){
aux=new Comparable[a.length];
sort(a,0,a.length-1);
}
public static void sort(Comparable[] a,int lo , int hi){
if (lo>=hi) return;
int mid = (lo+hi)/2;
sort(a,lo,mid);
sort(a,mid+1,hi);
merge(a,lo,mid,hi);
}
基于原地归并的抽象实现了另一种的递归归并,应用了高效算法设计中分治思想;
方法不断进栈,如数组所示:
M E R G E S O R T E X A M P L E
最先弹栈merge(a,0,0,1 )对应数组[M , E]:因仅有2个字符,故可认为左右两边为有序数组;
进行merge操作后 a变为
E M R G E S O R T E X A M P L E
随后弹栈merge(a,2,2,3);对应数组[R,G]同理排序后为[G,R];
如此递归,不断弹栈后,完成排序;
时间复杂度
O(NlogN)
自底向上的归并排序
思想:从1|1 的数组分布开始,整个数组merge以后
开始2|2 —4|4 直到所有结束;
public static void sort(Comparable[] a){
int n = a.length;
aux = new Comparable[n];
for(int sz = 1 ; sz < n ;sz = sz+sz){
for(int lo =0 ; lo < n-sz ; lo += sz+sz){
merge(a,lo,lo+sz-1,Math.min(lo+sz+sz-1,n-1));
}
}
}