java實現排序(5)-歸併排序

引言

歸併排序也是一種效率非常高的排序算法,它的時間複雜度是O(NlogN)。在本文中,會詳細介紹歸併排序的概念和排序的基本原理。最後用代碼實現歸併排序,供大家參考。筆者目前整理的一些blog針對面試都是超高頻出現的。大家可以點擊鏈接:http://blog.csdn.net/u012403290

技術點

1、分治策略
在如今處理大數據和有效提高執行效率的方法,分治策略顯得非常的普及。比如在前幾篇博文《java中不常見的東西-Fork/join》其實就是一種分治策略。通俗來說,就是把一個問題分而治之。“分”:它把一個大問題分成一些小的問題,然後通過遞歸求解。“治”:把各個分後的小問題的答案統籌合併在一起。或許你聽過map-reduce。都是體現了這種思想,對計算機資源也是一種充分利用的體現。

2、歸併排序
通俗來說,核心就是合併兩個已排好序的數據,那麼對於這個步驟來說顯得非常好理解,我們只需要設立2個指針,分別指向預處理的兩個有序數據的比較位置,同時設立一個新的存儲容器,把比較之後合適的值放入這個新的容器中,再把對應的指針往後移動一位。接下來,我們圖解一下如何實行歸併排序的:

存在三個數組A、B和C,A和B本身就是有序的,C是一個長度合適的空數組,用於存儲新的數據。我們再設立三個指針,分別指向A、B和C當前操作的位置。初始階段如下圖:
這裏寫圖片描述
在初始階段中,C數組的容量是A與B的容量之和。每個數組的指針都指向數組第一個位置。

①歸併排序第一步,比較A[0]與B[0],也就是A指針與B指針指向的地方,發現3<5,所以把3放入C數組中的C[0]位置,也就是C指針指向的地方。在後述中,我們只用指針指向來標誌處理的位置。只要對數據進行了處理,那麼指針就需要往後移動一位,第一步的比較之後,就變成了這樣:
這裏寫圖片描述
在數組A和C中指針發生了變化,因爲A產出了一個3,C接收了一個3,而B數組數據沒有發生變化。

②我們繼續比較A與B數組指針指向的位置,發現15>5,那麼我們就把5放入C數組指針指向的地方。因爲B產出了5,C接收了5,而A數組沒有變化,所以B與C的指針向後移動一位,就變成了這樣:
這裏寫圖片描述

③繼續比較A與B數組指針指向的位置,發現15<18,那麼就把15放入C數組指針指向位置,因爲A與C數組變得,把兩者的指針向後移動一位:
這裏寫圖片描述

④繼續比較A與B數組指針指向位置,發現21>18,那麼把18放入C數組指針指向位置,B與C數組指針向後移動一位:
這裏寫圖片描述

⑤繼續比較A與B數組指針指向位置,發現21>20,那麼把20放入C數組,並移動相關指針:
這裏寫圖片描述

⑥不再贅述,直接放圖:
這裏寫圖片描述

⑦不再贅述,直接放圖:
這裏寫圖片描述
在上圖中,我們發現A數組已經使用完了,當這種情況出現,比較也就結束了,直接把B數組剩餘的部分放到C數組從指針開始的尾部。排序就結束了

⑧A數組已使用完畢,直接把B數組剩餘數據拷貝到C數組以指針指向位置爲起點的位置上:
這裏寫圖片描述

我們總結一下上面的步驟,在歸併排序中合併兩個有序的數據的時間是線性的,最多比較兩組數據總和-1次。那麼,在整體對一系列數據進行歸併排序,第一步就需要把它拆分爲一個數據爲一個有序數組爲止(因爲單個數據總是有序的),比較相鄰有序數據,合併成一個新的數組。然後又把臨近的兩個新的有序數組再次歸併比較,又得到一個新的數組,以此類推。比如說下面這個例子:

原數據:5,9,1,6,2,7,8
初始化:{5},{9},{1},{6},{2},{7},{8}
第一趟歸併:{5,9},{1,6},{2,7},{8}
第二趟歸併:{1,5,6,9},{2,7,8}
第三趟歸併:{1,2,5,6,7,8,9}

代碼實現

思考:
1、在分治策略中,遞歸是必然存在的,所以在設計代碼的時候,如何掌握遞歸顯得非常重要。遞歸的部分肯定是把數據拆分成小組,再把小組拆分成更小的組,直至拆分爲1個數據(1個數據就是有序的)。那麼遞歸的臨界條件就是數據長度大於1。

2、我們必然存在一個c數組去存儲排好序的數據。但是我們可以思考,如果在排序部分創建一箇中間數組來存儲,那麼不斷循環的new出一個新數據,對系統開銷會顯得非常大。我們考慮上面說到的兩個有序數組進行整合成c數組,我們發現:
我們可以把一個預排序數組在邏輯上拆分爲兩個有序數組進行有序合併(實際上是一個數組),同時合併完之後,把合併部分迴歸到原始數組。如此這般,我們還不用一直創建新的c數組,只需要創建一個,只是指針指向不同罷了。下面圖解上面這段邏輯:
這裏寫圖片描述

以下是代碼實現:

package com.brickworkers;

public class MergeSort<T extends Comparable<? super T>> {

    //核心兩組有序數據合併算法,注意在這裏並沒有明顯拆分出2個數組,只是在同一個數組中引用
    private void merge(T[] target, T[] store, int a_pointer, int b_pointer, int end){//入參爲一個要排序的數組和兩個數組的指針,和當前處理數組的長度
        //a數組的結尾
        int ae_pointer = b_pointer - 1;//b數組第一個節點左移一位就是a數組的結尾
        int store_pointer = a_pointer; //這個就是c數組的指針,也就是存儲數組的指針,這樣做的目的是能動態的變動這個數組
        int element_size = end - a_pointer + 1;//這個表示當前處理元素數量

        //while循環,如果數組沒有用完就一直循環
        while(a_pointer <= ae_pointer && b_pointer <= end){
            if(target[a_pointer].compareTo(target[b_pointer]) <= 0){//如果a數組指針指向位置小於b指針指向位置,那麼就要把a數據存儲到c中,並移動指針
                store[store_pointer++] = target[a_pointer++];
            }else{
                store[store_pointer++] = target[b_pointer++];
            }
        }


        //如果某一個數組用完,那麼需要把另外一個數組拷貝到c數組中
        while(a_pointer <= ae_pointer){//b數組用完,則把a數組剩餘拷貝到c中
            store[store_pointer++] = target[a_pointer++];
        }

        while(b_pointer <= end){//a數組用完,則把b數組剩餘拷貝到c中
            store[store_pointer++] = target[b_pointer++];
        }


        //我們把排序好的部分放入目標數組中
        for (int i = 0; i < element_size; i++, end--) {
            target[end] = store[end];
        }
    }



    private void mergeSort(T[] target, T[] store, int start, int end){//需要排序的目標數組
        //遞歸臨界條件,數組必須存在
        if(end > start){
            //把目標數據拆分爲兩部分
            int mid = (start + end)/2;
            //左邊的數組排序
            mergeSort(target, store, start, mid);
            //右邊的數組排序
            mergeSort(target, store, mid + 1, end);

            //執行歸併
            merge(target, store, start, mid+1, end);
        }
    }


    public void mergeSort(T[] target){//方法我們主要是把c數組,也就是存儲數組單獨拿出來創建,這樣一來只會創建一次,可以有效提升效率
        T[] store = (T[]) new Comparable[target.length];
        mergeSort(target, store, 0, target.length - 1);
    }


    public static void main(String[] args) {
        MergeSort<Integer> sort = new MergeSort<Integer>();
        Integer[] target = {6,5,9,7,5,8,4,2,1};
        sort.mergeSort(target);
        for (Integer integer : target) {
            System.out.print(integer + " ");
        }

    }
}


//輸出結果:
//1 2 4 5 5 6 7 8 9 
//

尾記

本來我想一步步寫出優化過程的,首先,我們嚴格按照我們上面所描述的a,b,c三個數組進行操作。優化a,b數組整合。再者,優化存儲數組c,防止它的頻繁創建。但是,我覺得這兩種情況,讓有興趣的人去探究吧。

希望對你有所幫助

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章