排序算法(二):归并排序

归并排序编程

归并排序基本的操做是合并两个已排序的数组,以下面的例子:数组

A{1,2,4,7}ide

B{2,2,5,9}spa

第一步:3d

比较A[0]B[0]A[0]<B[0],将A[0]复制到C[0]中,获得code

C{0}blog

第二步:排序

比较A[1]B[0]A[1]=B[0],将A[1]复制到C[0]中,获得递归

C{1,2}it

 

循环以上步骤,即获得排序后的序列:

C{1,2,2,2,4,5,7,9}

 

这就是归并排序的基本原理,那么咱们能够先给出合并两个已经有序的数组的代码:

    public void merge(Integer[]a,Integer low,Integer mid,Integer high) {
        Integer i = low;
        
    /*    这里为何不能用mid,由于以前在递归时是以mid+1分割的:
                [1,2,8,3,4]
                low=0 mid=2 high=4
                i=0 j=2
                [1]
                i=1 j=2
                [1,2]
                i=2 j=2
                [1,2,8,3,4]*/
        
        Integer j = mid + 1;
        Integer[] b = new Integer[high + 1];
        for(int k=low;k<=high;k++) {
            b[k] = a[k];
        }
        
        print("b",b);
        
        for(int k=low;k<=high;k++) {
            //第一个有序子数组已经遍历完
            if(i > mid)
                a[k] = b[j++];
            //第二个有序子数组已经遍历完
            else if(j > high) 
                a[k] = b[i++];
            else if(b[i] < b[j])
                a[k] = b[i++];
            else 
                a[k] = b[j++];
        }
    }

 

这段代码实现了合并两个已经有序的数组到一个新数组。只不过在这里咱们使用一个数组代替了两个有序数组,以下:

A’[1,2,4,7,2,2,5,9]  是把上面提到的两个有序数组AB放在一个数组A’中,用mid=3来分割。

最后在代码中新建一个数组B[],A’中的元素复制到B

再用循环依次将元素排序放回A中。

 

可是如今这段代码只能将两个已经有序的数组,归并后到一个新数组,让这个新数组编程有序的。

若是是一个无序的数组呢?

这里就须要用到一下的思想:

 

咱们能够先将一个无序数组A,按照2位单位,分红诸多长度为2的子数组:

以下:

假若有数组A[2 ,1 ,5 ,9 ,0 ,6 ,8 ,7 ,3]

能够分红如下长度为1的子数组:

{2}{1}{5}{9}{0}{6}{8}{7}{3}

那么对这9个子数组进行归并排序,也即便用上面提到的代码进行排序,那么就能够获得

{1,2}{5,9}{0,6}{7,8}{3}

这样咱们就有5个有序的子数组了,再讲这五个子数组两两归并,即获得:

{1,2,5,9}{0,6,7,8}{3}

就这样依次归并下去,即获得一个有序的数组B

{0 ,1 ,2 ,3 ,5 ,6 ,7 ,8 ,9}

 

在这里,很明显能感受到一丝递归的意味,那么先直接给出代码:

    //递归实现,自顶向下
    public void mergeSort(Integer[] a,Integer low,Integer high) {
        if(low >= high)
            return;
        
        Integer mid = (low + high)/2;
        mergeSort(a,low,mid);
        mergeSort(a,mid+1,high);
        merge(a,low,mid,high);
    }

 

 

相信若是理解了以上所说的递归排序的原理,这段代码应该很是好懂,用递归的好处就是逻辑简单,符合人的直观思惟,只须要将一个待排序的数组依次分红2,4,8...等若干个数组,直到获得A.length个长度为1的子数组,依次归并后获得若干个长度为2的有序子数组,再进行归并,获得若干个长度为4的子数组(固然,有可能最后一个子数组长度不必定恰好为24,即数组的长度不必定为2的倍数)。

 

这样一直归并下去,即获得有序的数组。

 

 

这是使用递归来实现,那么应该还有一种不使用递归的实现,以下:

    //非递归,自底向上
    public void mergeSortNonRecursion(Integer[] a) {
        //第一层循环 表示归并排序子数组的长度 从1 , 2 , 4 ,8 .....
        for(int i=1;i<a.length;i *= 2) {
            //第二层循环表示每两个自数组之间归并排序,肯定起始和终止INDEX
            for(int low=0;low<a.length;low += 2*i) {
                merge(a, low, low + i- 1, Math.min(low + 2*i - 1, a.length - 1));
            }
        }
    }

 

第一层循环表示归并的次数,

第一次分红n个长度为1的子数组,进行归并

第二次分红n/2个长度为2的子数组....

结论就是,一个长度为n的数组须要归并logn次。

 

第二层循环表示把两个子数组进行归并

 

 

效率:归并排序的时间复杂度为NlogN

 

完整代码以下:

public class MergeSort extends SortBase {

    @Override
    public Integer[] sort(Integer[] a) {
        // TODO Auto-generated method stub
        print("init",a);
        //mergeSort(a,0,a.length-1);
        mergeSortNonRecursion(a);
        print("result",a);
        return a;
    }
    
    
    //递归实现,自顶向下
    public void mergeSort(Integer[] a,Integer low,Integer high) {
        if(low >= high)
            return;
        
        Integer mid = (low + high)/2;
        mergeSort(a,low,mid);
        mergeSort(a,mid+1,high);
        merge(a,low,mid,high);
    }
    
    public void merge(Integer[]a,Integer low,Integer mid,Integer high) {
        Integer i = low;
        
    /*    这里为何不能用mid,由于以前在递归时是以mid+1分割的:
                [1,2,8,3,4]
                low=0 mid=2 high=4
                i=0 j=2
                [1]
                i=1 j=2
                [1,2]
                i=2 j=2
                [1,2,8,3,4]*/
        
        Integer j = mid + 1;
        Integer[] b = new Integer[high + 1];
        for(int k=low;k<=high;k++) {
            b[k] = a[k];
        }
        
        print("b",b);
        
        for(int k=low;k<=high;k++) {
            //第一个有序子数组已经遍历完
            if(i > mid)
                a[k] = b[j++];
            //第二个有序子数组已经遍历完
            else if(j > high) 
                a[k] = b[i++];
            else if(b[i] < b[j])
                a[k] = b[i++];
            else 
                a[k] = b[j++];
        }
    }
    
    //非递归,自底向上
    public void mergeSortNonRecursion(Integer[] a) {
        //第一层循环 表示归并排序子数组的长度 从1 , 2 , 4 ,8 .....
        for(int i=1;i<a.length;i *= 2) {
            //第二层循环表示每两个自数组之间归并排序,肯定起始和终止INDEX
            for(int low=0;low<a.length;low += 2*i) {
                merge(a, low, low + i- 1, Math.min(low + 2*i - 1, a.length - 1));
            }
        }
    }
    
    public static void main(String[] args) {
        Integer[] a = {2,1,5,9,0,6,8,7,3};
        (new MergeSort()).sort(a);
    }
}

 

运行结果以下:

init: [2 ,1 ,5 ,9 ,0 ,6 ,8 ,7 ,3]

归并2和1

b: [2 ,1]

归并5和9,即依次归并两个长度为1的子数组,获得长度为2的有序子数组

b: [null ,null ,5 ,9]

b: [null ,null ,null ,null ,0 ,6]

b: [null ,null ,null ,null ,null ,null ,8 ,7]

b: [null ,null ,null ,null ,null ,null ,null ,null ,3]

归并一、二、五、9,即依次归并两个长度为2的子数组,获得长度为4的有序子数组

b: [1 ,2 ,5 ,9]

b: [null ,null ,null ,null ,0 ,6 ,7 ,8]

b: [null ,null ,null ,null ,null ,null ,null ,null ,3]

归并1 ,2 ,5 ,9 ,0 ,6 ,7 ,8,即依次归并两个长度为4的子数组,获得长度为8的有序子数组

b: [1 ,2 ,5 ,9 ,0 ,6 ,7 ,8]

b: [null ,null ,null ,null ,null ,null ,null ,null ,3]

b: [0 ,1 ,2 ,5 ,6 ,7 ,8 ,9 ,3]

result: [0 ,1 ,2 ,3 ,5 ,6 ,7 ,8 ,9]

 

 

从结果上看,能够很清晰的看出来是两两归并。

相关文章
相关标签/搜索