public class MergeSort{
//递归实际上是环境给你提供了栈来存储
public void start_Iteration(int[] a,int p,int r){
if (p<r) {
//divide into two part
int q=(p+r)/2;
//left part
start_Iteration(a,p,q);
//right part
start_Iteration(a,q+1,r);
merge(a,p,q,r);
}
}
//排序有没有排好这里比较特别,是看每一半是不是都排好,而不是看整个部分都排好,原因是,这儿是合并排序
public boolean notSort(int []a,int start,int end){for (int i=start;i<(end+start)/2;i++ ) {
if (a[i+1]<a[i]) {
return true;
}
}
for (int i=(end+start)/2+1;i<end ;i++ ) {
if (a[i+1]<a[i]) {
return true;
}
}
return false;
}
//存数据的时候不能破坏原有的数据结构,而且必须把左数组和右数组的下标都存进去,不然回溯的时候找不到数据了
public void save(Stack<Integer> s,int start,int end){s.push(end);
s.push(start);
int mid=(end+start)/2;
//finish left first
s.push(end);
s.push(mid+1);
//
s.push(mid);
s.push(start);
}
//这里是通过自己写的栈来实现非递归的合并排序,根据栈的性质,先进后出
//所以下面的push,先将后面的坐标放到栈里面,再将前面的坐标放到栈中,对于左右来说,为了,先取出左边的数组,先放的是左边的数
///后放是的是右边的数
public void start_MyNoIt(int[] a){
//java自带的栈
Stack<Integer> s=new Stack<Integer>();int start=0;
int end=a.length-1;
s.push(end);s.push(start);
//由于第一次开始排序必须要到左右数组都只剩下一个数组的时候才开始,所以这之前都只能存数据,in就是用来处理这个的
boolean in=false;while(!s.empty()){
start=s.pop();
end=s.pop();
if (end-start>1 && !in) {
//存数据
save(s,start,end);
continue;
}else{
//第一次执行这里的时候,代表已经第一次满足可以合并的条件,此时,栈中的数据可抽象为如下的样子
// ( start1,end1 )
// / \
// (start2,end2) (start3,end3)
// / \
// (start4,end4) (start5,end5)
// / \
// (start6,end6) (start7,end7)
//其中end6-start6=1这些数字都代表原数组中的下标,可把它(栈)想象成一个二叉树
//下面的做法主要是说,如果是在左子树,那么直接合并,如果是右子树,那么先看看是不是右子树已经排好序(这里的排好序的详解建notSort代码的解释)了,没有就继续存
//否则就排右子树
}
//如果就两个子树了直接排序就好,下面都不需要处理了
if(s.empty()) {merge(a,start,(start+end)/2,end);
break;
}
//一个节点(每次都取两个数,这两个数都是下标可将这两个数看成是一个节点),如果只有一个元素,那么显然默认是排好序的
if(end==start){
continue;
}
//一个节点只有两个元素那么只需要直接排序就好
if (end-start==1) {
merge(a,start,start,end);
continue;
}
//区分左子树和右子树很关键:如果是左子树,那么它的end下标一定会和下一次取出的数满足nextStart==end+1的关系
//这是我在存数据的时候就这么做的,由于只有两个数据的时候不会执行到这里,所以不用担心为空的情况
//取int nextStart=s.pop();
//存,不能破环原有的结构
s.push(nextStart);
//满足原有条件则为左子树
if (nextStart==end+1) {
merge(a,start,(start+end)/2,end);
continue;
}
//右子树没有排好序,就直接存储下标
if(notSort(a,start,end)){//right
save(s,start,end);
}else{
merge(a,start,(start+end)/2,end);
}
}
}
//根据合并排序的特性而写的非递归实现合并
//特性:合并排序是先将原先的数组划分成2分,再分下去,直至每边的数组都只有一个数据,这时两个数组都必然是已排好序的(只有一个数)
//再将两者合并,也就是说,刚开始合并的时候,每个已排好序的数组的长度是1,然后合并之后,已排好序的数组的长度是2,再合并后已排好序的
//的数组的长度是4。。。以此类推,直到已排好序的数组的元素个数和原数组相比差值不超过已排好序数组的长度(这部分解释见代码部分)
public void start_NonIterative(int[] a,int p,int r){//s代表刚开始只有1个元素的数组都是排好序的
int s=1;
//只有在已排好序元素的个数小于原数组元素个数的时候才会排序,不然会有溢出
while(s<r){int i=0;
//for循环中 i<数组长度-2*s 很关键,原因如下:
//我这边要做的是合并两个已经排好序的数组,那么意味着每次在合并的时候总共需要的移动量是 2倍已排好序数组的长度
//所以为了确保不会有溢出必须保证这2倍已排好序数组的长度的增量在原数组下标访问的允许范围之内
for (;i<r-2*s;) {//s=1 意思是左数组只有1个元素
//要确保每个数组的长度都为s,所以需要减1
merge(a,i,i+s-1,i+2*s-1);
//每一次合并,必须要把整个数组都遍历到,所以,for循环是必须的
i+=2*s;}
//当出现数组的长度不是2的幂次方的时候,最后那一部分无法访问的
//由于是以2的倍数的形式递增的,所以最终没有处理的也就只有最后一个元素
//那么到了最后一次,会将最后一个元素和前面一个元素一起来实现合并排序
//比如共有9个元素,那么最后一次s=8,此时,还有一个元素即最后一个,也可以看作一个已经排好序的
if(i+s<r){merge(a,i,i+s-1,r-1);
}
//这里代表原来s的合并已经全部结束,也就是 合并元素从 1到2到4到8等等的实现
s*=2;}
}
//这个方法主要是实现两个已经排好的数组的合并,在调用这个方法之前原数组a中下标从p到q已经排好,这部分下标的数组称为左数组
//q+1到r已经排好,这部分下标的数组后面称为右数组
//思路:对于已经排好的两个数组,先根据相应的下标,把原数组中的这两部分分别放到left数组和right数组中,并在数组的最后放置哨兵,只要到达哨兵的
//位置,那么没到达的那一部分就可以全部放进去了。通过比较左右两个数组的数的大小,再把这些数放回到原数组中,这里需要3个变量,也即下面代码中的
//i,j,k,有一点需要理解,这里是直接用左数组或有数组中的数替换了原数组的数,比较时用的也是左右数组中的数,因为这里是给已经排好序的两个数组再排序
public void merge(int[]a,int p,int q,int r){//左数组的长度
int n1=q-p+1;
//右数组的长度,右数组的下标是从q+1开始的,这里容易犯错
int n2=r-q;
int[] left=new int[n1+1];
int[] right=new int[n2+1];
//初始化两个数组,注意数组的起始位置
for (int i=0;i<n1;i++ ) {
left[i]=a[p+i];
}
for (int j=0;j<n2 ;j++ ) {
right[j]=a[q+j+1];
}
//标记,到达这个标记则表示有一边已经遍历结束了
left[n1]=Integer.MAX_VALUE;
right[n2]=Integer.MAX_VALUE;
int i=0;
int j=0;
for (int k=p;k<=r;k++ ) {
if (left[i]>right[j]) {
a[k]=right[j];
j++;
}else{
a[k]=left[i];
i++;
}
}
}
public static void main(String[] args) {
MergeSort ms=new MergeSort();
//要排序的数组
int a[]={9,8,7,6,5,4,3,0,1,234,93247,456889,345,12,-34,-6587};
//第一种方法采用的是递归合并排序
// ms.start_Iteration(a,0,a.length-1);
//第二种方法采用的是非递归排序,这里主要用的方法是根据合并排序的特性来实现从递归到非递归的转化
// ms.start_NonIterative(a,0,a.length);
//第三种方法采用的是非递归,主要是通过自己写栈来实现从递归到非递归的转化
ms.start_MyNoIt(a);
//这里是打印合并排序后的结果
for (int i=0;i<a.length;i++ ) {System.out.println(a[i]);
}
}
}