深入理解归并排序

声明:图片来自于《算法》第四版。

何为归并?

即将两个有序的数组归并成一个更大的有序数组。

归并排序:

要将一个数组排序,可以先(递归地)将它分成两半分别排序,然后将结果归并起来。

《深入理解归并排序》
归并排序示意图

优点:

它能够保证将任意长度为N的数组排序所需要的时间和NlogN成正比

缺点:

所需要的额外空间和N成正比。

原地归并的抽象方法:

实现归并的一种直截了当的办法是将两个不同的有序数组归并到第三个数组中,两个数组中的元素都实现了Comparable接口,实现的方法很简单,创建一个适当大小的数组然后将两个输入数组中的元素一个个从小到大放入到这个数组中。

当用归并将一个大数组排序时,我们需要进行很多次的归并操作。因此每次归并时都创建一个新的数组来存储排序结果会带来问题。

原地归并方法:

先将前半部分排序,再将后半部分排序,然后在数组中移动元素而不需要额外的空间。

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++)			//将a[lo..hi]复制到aux[lo..hi] 		auk[k] = a[k];
	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++];
}

注释:

此方法会将子数组a[lo..mid]和a[mid+1..hi]归并成一个有序的数组并将结果存放在a[lo..hi]中。

该方法先将所有元素复制到aux[]中,然后再归并回a[]中,方法在归并时(第二个for循环)进行了四个条件判断:

a、 左边用尽(取右边的元素)

b、 右边用尽(取左边的元素)

c、 右半边的当前元素小于等于左半边的当前元素(取右边的元素)

d、 右半边的当前元素大于等于左半边的当前元素(取左边的元素)

《深入理解归并排序》
《深入理解归并排序》 原地归并的抽象方法的轨迹

自顶向下的归并排序:

如果它能将两个子数组排序,它就能通过归并两个子数组来讲整个数组排序。

//自顶向下的归并排序
public class Merge{
	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){	//将数组a[lo..hi]排序
		if  (hi < lo)  return ;
		int mid  = lo + (hi - lo)/2;
		sort(a, lo, mid);		//将左半部分排序
		sort(a, mid+1,hi);		//将右半部分排序
		merger(a, lo, mid, hi)	;	//归并结果
	}
}

《深入理解归并排序》
《深入理解归并排序》 自顶向下的归并排序

过程:

如要将a[0..15]排序,sort会先调用自己将a[0..7]排序,再在其中调用自己将a[0..3]和a[0..1]排序。再将a[0]和a[1]分别排序后,终于才会将a[0]和a[1]归并。第二次归并是a[2]和a[3],然后是a[0..1]和a[2..3],以此类推。

优化方式:

a、 对小规模子数组使用插入排序

《深入理解归并排序》
对小规模子数组使用插入排序示意图

b、 测试数组是否已经有序

c、 不将元素复制到辅助数组

自底向上的归并排序:

先归并那些微型数组,然后再成对归并得到的子数组,如此这般,直到我们将整个数组归并在一起。

//自底向上的归并排序
public class MergeBU{
	private static Comparable[] aux;			        //归并所需的辅助数组

	public static void sort(Comparable[] a){			//进行lgN次两两归并
		int N = a.length;
		aux = new Comparable[N];
		for(int sz = 1; sz < N; sz = sz+sz)		        //sz子数组的大小
			for(int lo = 0; lo < N-sz; lo += sz+sz)	        //lo:子数组的索引
				merge(a,lo,lo+sz-1,Math.min(lo+sz+sz-1. N-1));
	}
}

《深入理解归并排序》
《深入理解归并排序》

总结:

  • 对于长度为N的任意数组,自顶向下的归并排序需要1/2NlgN至NlgN次比较。
  • 对于长度为N的任意数组,自顶向下的归并排序最多需要访问数组6NlgN次。
  • 对于长度为N的任意数组,自底向上的归并排序需要1/2NlgN至NlgN次比较,最多需要访问数组6NlgN次。
  • 没有任何基于比较的算法能够保证使用少于lg(N!)~NlgN次比较将长度为N的数组排序。
  • 归并排序是一种基于渐进最优的基于比较排序的算法。
    原文作者:SeYuFac
    原文地址: https://zhuanlan.zhihu.com/p/61232392
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞