为什么引出归并算法呢,我们知道堆排序是利用完全二叉树的深度log2 n 向下取整再加一的特性,所以效率比较高。但 

是堆结构的设计相对比较复杂,但是又因为利用二叉树来进行排序的算法效率一般都比较高,所以我们引出利用二叉树特 

性的归并排序来进行数据的排序。 



什么是归并排序:归并排序就是利用归并的思想把一组数据,首先分解成n个长度为一的序列,其每个序列都是有序的 

,然后两两归并,得到n/2向上取整个长度为2或者是1的有序子序列,,再两两归并,最后得到一个长度为n的有序序 

列为止,这种方法又叫做2路归并排序 



归并排序算法的递归实现: 

首先来介绍一下递归:递归利用的系统栈来完成的,先进后出 设计递归的时候先写base case(就是递归终止的条件,什么时候 

不用划分子问题了) 

递归函数其实就是一个函数公用一个过程,只不过它每次的范围和作用域是不同的 

下面来通过一个小demo来充分理解一下递归 

就是给定一个数组,求一个数组的最大值: 

public static int getMax(int[] arr,int L,int R){ 

 if(L == R){//base case 

 return arr[L]; 

 } 

 int mid = (L + R)/2; 

 int maxLeft = getMax(arr,L,mid);//第六行 

 int maxRight = getMax(arr,mid+1,R);//第七行 

 return Math(maxLeft,maxRight);  

} 

例如举个简单的例子:例如输入一组数据[1,3,9,6]; 

直接调用getMax函数,运行到第六行时再次调用getMax函数,此时对父函数进行压栈操作,即把父类函数的参数,变量 

以及调用子类函数的行数等等信息全部压进栈里: 

流程: 

栈: 

g3(0,0)此时直接返回 

g2(0,1)、mid=0、第六行 

g1(0,3)、mid=1、第六行 

当g3返回时,g3出栈,其出栈时的返回值给栈顶也就是g2,g2先出来:运行到第七行时,g2压栈 

此时栈内的数据应该为 

g2(0,1)、mid=0、maxLeft、第七行 

g1(0,3)、mid=1、第六行 

然后调用g4(1,1)此时g4直接出栈,返回到g2,此时栈内的数据应该为 

g2(0,1)、mid=0、maxLeft、maxRight 

g1(0,3)、mid=1、第六行 

运行到第八行时g2出栈,返回值直接赋给下一个栈顶也就是g1计算之后也出栈 

此时的栈内应该为: 

g7(2,2) 

g6(2,3)、mid=2、第六行 

g5(2,3)、mid=2、maxLeft=3、第七行 

g7直接返回值9此时回到第六行,此时栈的结构为: 

g8(3,3) 

g6(2,3)、mid=2、maxLeft=9 

g5(2,3)、mid=2、maxLeft=3、第七行 



g8直接返回值6 此时 

g6(2,3)、mid=2、maxLeft=9、maxRight=6 

g5(2,3)、mid=2、maxLeft=3、第七行 



然后 

g5(2,3)、mid=2、maxLeft=3、maxRight=9 



然后 

直接接返回该函数值9 



--递归讲完了,下面来欣赏真正的归并排序 



①首先要考虑的问题时传进来的数组是否需要进行归并排序,也就是该数组是非空且长度大于1的数组才有意义 

void mergeSort(int[] a){ 

 //判断是否有排序的必要 

 if(a==null&&a.length>1){ 

 return; 

 } 

 //调用真正有意义的归并排序方法 

 mergeSort(a,0,a.length-1); 

} 

②当数组长度大于1时,我们调用真正的归并排序,从上面我们介绍什么是归并的时候介绍的非常清晰,就是利用递归来完 

成对子区间的划分,向我们上面举的递归的demo一样,构造递归,对左右两部分进行排序,然后合并。代码如下: 

void mergeSort(int[] a,L,R){ 

 //base case这是在构建递归时第一步要考虑的 

 //就是递归终止的条件 

 if(L == R){ 

 return; 

 } 

 //对左右进行排序 

 int mid = L + (R-L)/2; 

 mergeSort(a,L,mid); 

 mergeSort(a,mid+1,R); 

 //把左右已经排序好的序列整合成一个有序序列 

 merge(a,L,mid,R); 

} 

③最主要的其实就是当划分后对两部分已经有序的序列进行整合成一个有序序列 

void merge(int[] a,L,int mid,int R){ 

 //创建一个临时存储数组来存储这组有序序列 

 int [] help = new int[r-l+1]; 

 //定义几个坐标(指针)来表示辅助数组的坐标以及左右两个待排数组的坐标 

 int i = 0; 

 int p1 = l; 

 int p2 = mid+1;、 

 //谁小就把谁放在辅助数组里,并且对应坐标加一 

 if(p1<=mid && p2<=R){ 

 help[i++] = a[p1]<a[p2]?a[p1++]:a[p2++]; 

 } 

 //因为始终会有一组先排完,其对应的数组下表就会越界 

 //又因为其都是有序的,所以直接拷贝到辅助数组中就完事了 

 while(p1>mid){ 

 help[i++] = a[p2++]; 

 } 

 while(p2>R){ 

 help[i++] = a[p1++]; 

 } 

 //下面再把辅助数组里面的数据拷贝到原数组中,归并排序搞定 

 for(i = 0;i<help.length;i++){ 

 a[L+i] = help[i]; 

 } 
}
其实就是递归保证有序(划分到单个数据默认有序),然后归并方法让其左右两边已经排好序的数据合并有序,
归并排序时间复杂度分析:由master公式可知T(n)=2T(n/2)+O(n),d=1,a=2,b=2所以其时间复杂度为O(nlogn)(不懂
master公式的同学可以去看一下我前面有关于master的博文),由因为在归并排序中需要外排(就是引入新的存储空间来完成
排序),也就是需要和原纪录同样数量的存储空间存放归并结果以及递归时深度为log2 n的栈空间,因此空间复杂度为
O(n+logn),又因为其需要两两进行比较,也就是不存在跳跃式比较交换的过程,所以归并排序是稳定的。

归并排序的非递归解决:
其实递归就是利用父方法对子方法的调用自动进行压栈的过程,所有的递归问题在工程上都是落地的,都是可以用非递归方法解决的。来分析一下归并排序的过程,其实就是先每个数默认有序,然后相邻的两个数相互排好有序,再4个为一组组内排好有序...所以非递归实现可以引入一个控制变量,每次使这个变量的数值进行平方就可以控制该排序算法的进行。因为改变的仅仅是不是递归,所以归并的算法并不需要改变,只需要改变mergeSort即可:
void mergeSort(int[] a,L,R){
 
 for(int i=L,i<=R,i=i*2){ 

 for(int j=low;j<=high-i;j+=2*i){   

        
 merge(a, j, j+i-1, Math.min(j+2*i-1, high));   

            
 }   
  

        } 

}