此博客为连载博客需要参考我前面几篇博客代码才可以实现
/*归并排序
* 思想: divide and conquer (分治法)
* 将一个数组等分成两个更小的数组,将这两个数组排序后,再将它们归并起来将得到一个有序数组
* 具体归并方法就是从两个数组中从小到大放入大数组中
* 时间复杂度: 对于长度为N的任意数组,自顶向下的归并排序需要1/2N㏒N至N㏒N O(NlogN)
* 空间复杂度:需要额外为N的空间 O(N)
* 特点:运行时间比插入排序等比较大大减少,但辅助数组所使用的额外空间和N的大小成正比
* */
package paixu;
//自顶向下的归并排序
public class Merge extends Example{
private static Comparable[] aux; //归并排序所用的辅助数组
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){
//将a[lo..hi]排序
if(hi <= lo) return;
int mid = lo + (hi-lo)/2;
sort(a,lo,mid); //将左半边排序
sort(a, mid+1, hi); //将右半边排序
merge(a,lo,mid,hi); //归并结果
}
public static void Merge(Comparable[] a,int lo,int mid,int hi) {
//归并:将a[lo..mid]和a[mid+1..hi]排序
int i = lo, j = mid+1;
for(int k=lo; k<=hi; k++){
aux[k] = a[k]; //将a[lo..hi]复制到aux[lo..hi]中
}
for(int k=lo; k<=hi; k++){ //归并回到a[lo..hi]
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 main(String[] args) {
String alg1 = "Shell";
String alg2 = "Merge";
int N = 100000;
int T = 10;
long t1 = SortCompare.timeRandomInput(alg1, N, T);
long t2 = SortCompare.timeRandomInput(alg2, N, T);
System.out.println("希尔排序运行时间:"+t1);
System.out.println("归并排序运行时间:"+t2);
}
}
/*归并排序的改进:
* 1.对小规模数组使用插入排序
* 2.测试数组是否有序:可以加一个判断条件,如果a[mid]小于a[mid+1],我们就认为数组已经有序并跳过merge方法
* 3.不要每次都将元素复制到辅助数组,一次排序将输入数组排序到辅助数组,一次将辅助数组排序到输入数组*/
运行结果如下所示:
可以看到,将10个大小为十万的数组排序时,归并排序比希尔排序要快,更不用说选择排序、插入排序了。但归并排序的一个不足是需要O(N)的额外空间。
下面代码我仅仅实现了改进1和2
而且仅仅修改了上述public static void sort(Comparable[] a,int lo,int hi)函数
public static void sort(Comparable[] a,int lo,int hi){
//将a[lo..hi]排序
if(hi-lo<15){//对小规模数组使用插入排序
for(int i=1; i<hi-lo+1; i++){
Comparable tmp = a[i];
int j;
for( j=i-1; j>=0; j--){
if( less(tmp, a[j]) ){
a[j+1] = a[j];
}else{
break;
}
}
a[j+1] = tmp;
}
return;
}
int mid = lo + (hi-lo)/2;
sort(a,lo,mid); //将左半边排序
sort(a, mid+1, hi); //将右半边排序
if(less(a[mid],a[mid+1])) return; //如果a[mid]小于a[mid+1],我们就认为数组已经有序并跳过merge方法
merge(a,lo,mid,hi); //归并结果
}
再次运行:
说明这两个改进作用还是挺大的。
/*自底向上的递归排序
* 思想:任何一种分治法算法,都有自顶向下(递归)和自底向上(循环、迭代)的两种实现方法。
* 下面代码是先归并最小的数组,然后归并得到的子数组,如此这般,直到将整个数组归并在一起
* */
package paixu;
public class MergeBU extends Example{
private static Comparable[] aux; //归并排序所需的辅助数组
public static void sort(Comparable[] a){
int N = a.length;
aux = new Comparable[N];
for(int sz=1; sz<N; sz*=2){ //子数组大小sz
for(int lo=0; lo<N-sz; lo+=2*sz){ //lo子数组索引
merge(a, lo, lo+sz-1, Math.min(N-1, lo+sz+sz-1));
}
}
}
public static void merge(Comparable[] a,int lo,int mid,int hi){
//将a[lo..mid]和a[mid+1..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 main(String[] args) {
/*Integer[] a = {4,5,6,1,2,3,9,8,7,123,-1,22};
MergeBU.sort(a);
MergeBU.show(a);*/
String alg1 = "Merge";
String alg2 = "MergeBU";
int N = 100000;
int T = 10;
long t1 = SortCompare.timeRandomInput(alg1, N, T);
long t2 = SortCompare.timeRandomInput(alg2, N, T);
System.out.println("自顶向下归并排序运行时间:"+t1);
System.out.println("自底向上归并排序运行时间:"+t2);
}
}
运行结果如下:
两种方法运行时间基本相同。