归并:将两个有序的数组归并成一个更大的有序数组。java
归并排序(分治思想):要将一个数组排序,能够先(递归地)将它分别分红两半分别排序,而后将结果归并起来。算法
优势:它可以保证将任意长度为N的数组排序所需时间和NlogN成正比,相对于初级排序,时间大大缩短;数组
缺点:它所须要的额外空间和N成正比。less
原地归并的实现:先将全部元素复制到另外一额数组(aux[])中,而后在归并回原数组(a[])中,具体归并是循环比较两个有序数组的最左侧元素即最小值,哪一个更小,即将这个元素复制回原数组(a[])。性能
实现代码以下:测试
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复制到数组aux aux[k]=a[k]; } for(int k=lo;k<=hi;k++)//遍历整个aux,归并回数组a 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++]; }
自顶向下的归并排序:spa
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);//将右半边排序 merge(a,lo,mid,hi); } }
性能:对于长度为N的任意数组,自顶向下的归并须要1/2NlgN至NlgN次比较;最多须要访问数组6NlgN。code
改进:①对小规模子数组使用插入排序——是用插入排序处理小规模的子数组(好比长度小于15)通常能够将归并排序的巡行时间缩短10%~15%;排序
②测试数组是否已经有序——添加判断条件,若是a[mid]小于等于a[mid+1],咱们就认为数组已经有序而不须要在进行归并即不须要在调用merge()方法,所以能够将任意有序的子数组算法运行时间变为线性;递归
③不将元素复制到辅助数组——能够节省复制到用于归并的辅助数组所用的时间(但空间不能够)。一是经过将数据从输入数组排序到辅助数组;二是将数据从辅助数组排序到输入数组。
自底向上的归并方法:
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)); } }
自底向上的归并排序会屡次遍历整个数组,根据子数组大小进行两两归并。子数组的大小sz的初始值为1,每次加倍。最后一个子数组的大小只有在数组大小是sz的偶数倍的时候才会等于sz(不然它会比sz小)。
性能:对于长度为N的任意数组,自底向上的归并排序须要1/2NlgN至NlgN次比较,最多访问数组6NlgN次。
当数组长度为2的幂时,自顶向下和自底向上的归并排序所用的比较次数和数组访问次数相同,只是顺序不一样。
自底向上的归并排序比较适合用链表组织的数据。
没有任何基于比较的算法可以保证使用少于lg(N!)~NlgN次比较将长度为N的数组排序。
归并排序是一种渐进最优的基于比较排序的算法。