算法系列(四)归并排序及其改进(java实现)

前言:算法第四版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++]; //保证稳定性
		}			
	}


总结:可以证明基于比较的排序算法排序长度为N的数组至少需要lg( N! ) ~ NlgN次比较(这里是以2为底的),所以归并排序的时间复杂度是最优,但是空间复杂度不是,它需要O(N)的存储空间。另外,归并排序是稳定的。




  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值