排序算法(二):歸併排序

歸併排序編程

歸併排序基本的操做是合併兩個已排序的數組,以下面的例子:數組

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]

 

 

從結果上看,能夠很清晰的看出來是兩兩歸併。

相關文章
相關標籤/搜索