归并排序的两个实现版本的总结

一、merge算法

合并算法merge()总是基于这样一个事实:单个元素总是有序的。

对于原待排序列,因为单元素是最小的有序单元,因此
合并算法总是从两个单元素作为待合并的子序列开始,逐步向上合并。

例如:
int[] ary = {9,12,78,10,30,8,20,15,50,60};
其合并轨迹为:

low:0--mid:0--high:1
low:0--mid:1--high:2
low:3--mid:3--high:4
low:0--mid:2--high:4
low:5--mid:5--high:6
low:5--mid:6--high:7
low:8--mid:8--high:9
low:5--mid:7--high:9
low:0--mid:4--high:9

merge算法实现:

    private static void merge(int[] ary,int low,int mid,int high){
//        System.out.println("low:"+low+"--"+"mid:"+mid+"--"+"high:"+high);
        int[] aux = ary.clone();//辅助数组
        int i = low,j = mid+1;//i为前一个分数组头部开始,j从后一个分数组头部开始
        for (int k = low; k <= high; k++) {
            if (i > mid) {ary[k] = aux[j++];}//前一个分数组已全部合并,只需拷贝后一个分数组
            else if(j > high){ary[k] = aux[i++];}//后一个分数组已全部合并
            else if(aux[i] <= aux[j]){ary[k] = aux[i++];}//<=的条件保证合并算法的稳定性
            else {ary[k] = aux[j++];}
        }
    }

merge的参数中low,mid和high均是数组元素的下标值,应注意for循环的边界取值。

merge方法的mid参数为两待合并子序列的分界位置(也即前一个子序列的结束位置),设置这样一个参数对于merge方法有更好的通用性,可适用于任意两子序列的合并。

例如:

int[] ary = {1,3,5,8,12,4,10};

merge(ary,0,4,6)

这就是一个比较任意的合并(相对于用mid分界的合并)。

一、自顶向下的版本(递归)

1     public static void mergeSort(int[] ary,int low,int high){
2         if (low >= high) {return;}//递归基
3         int mid = low + (high-low)/2;
4         mergeSort(ary, low, mid);//mid位置的元素归属前一个子序列,且为前一个子序列的最后一个位置
5 mergeSort(ary, mid+1, high);//后一个子序列从mid+1开始
6 merge(ary, low, mid, high);//递归从两个单元素序列开始,如merge(ary,0,0,1) 7 }

对于任何的局部乃至全局,两子序列的分界位置mid的取值

int mid = low + (high-low)/2;

mid位置的元素总数归属于待合并两序列的前一个子序列,无论原数组的长度为奇为偶。

例如:

int[] ary = {3,2,2,0};

mid = low + (high-low)/2=1

两个待合并子序列:{3,2},{2,0}

int[] ary = {3,2,2,0,4};

mid = low + (high-low)/2=2

两个待合并子序列:{3,2,2},{0,4}

由于mid位置总是会被划分到待合并的两子序列的前一个子序列,因此导致前一个子序列长度总是大于(奇数)或者等于(偶数)后一个子序列。

当待排序列的个数是偶数总是情况很好理解。

当待排序列的个数是奇数是,单下的某个元素总是以单元素的方式参与某次合并。

三、自底向上的版本(非递归)

 1     public static void mergeSort(int[] ary,int low,int high){
 2         int mid;
 3         int lenght = high-low+1;//待排序列的长度
 4         //size为待合并的子序列的长度(单个子序列长度)
 5         //size呈现等比数列增长:1,2,4,8,16.。。
 6         for (int size = 1; size < lenght; size=2*size) {
 7             System.out.println(size);
 8             //start为待合并的两子序列的前一个子序列的起始位置
9 //start+size为待合并的两子序列的第二个子序列的起始位置
10 for (int start = low; start+size <= high; start+=2*size) { 11 mid = start+size-1; 12 //start+2*size-1为待合并的两子序列的第二个子序列的结束位置
13 //Math.min(start+2*size-1,high)主要是考虑第二个合并的子序列长度可能不足size,原待排序列长度为奇数
14 merge(ary, start, mid,Math.min(start+2*size-1,high)); 15 } 16 } 17 }

当待排序列的个数是偶数时刚好能够按照完全等比数列的方式向上合并,情况很好理解。

当待排序列的个数是奇数是,size等于1时总是会单下最后一个元素。

单下的最后一个元素是在size等于多少的时候以单元素的形式参与合并要是具体情况认定。

例如:

int[] ary = {9,12,8,10,7};

最后的元素7在size等于4的时候参与合并:{9,12,8,10}和{7}

再例如:

int[] ary = {9,12,78,10,30,8,20};

最后的单下的元素20在size等于2的时候参与合并:{30,8}和{20}

合并轨迹为:

size:1
low:0--mid:0--high:1
low:2--mid:2--high:3
size:2
low:0--mid:1--high:3
size:4
low:0--mid:3--high:4
原文地址:https://www.cnblogs.com/qcblog/p/7534836.html