天天看點

Java常見排序基礎 - 中

在  Java常見排序基礎 - 上  中主要介紹了冒泡排序、選擇排序、插入排序三種基礎排序,本篇文章主要介紹的是 快速排序、歸并排序。

快速排序:

首先看下什麼是快速排序,快速排序(Quicksort)是對冒泡排序的一種改進。快速排序由C. A. R. Hoare在1962年提出。它的基本思想是:通過一趟排序将要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另外一部分的所有資料都要小,然後再按此方法對這兩部分資料分别進行快速排序,整個排序過程可以 

遞歸

 進行,以此達到整個資料變成有序序列。以上概念來自百度百科。

對于 “

通過一趟排序将要排序的資料分割成獨立的兩部分,其中一部分的所有資料都比另外一部分的所有資料都要小

”,簡單的了解可以這樣的:在數組中找一個支點 ( 支點可以是數組中的任意位置,但是這個支點一般是數組的中心位置 ),經過一趟排序後,支點左邊的數都要比支點小,支點右邊的數都要比支點大。

假設現在有這樣一個數組:int arr[ ] = { 1,4,5,62,2,7,8,6,9,14 };

經過一趟排序之後,如果我選擇數組中間的數作為支點:7(任意的),那麼第一趟排序後的結果是這樣的:{1,4,5,6,2,7,8,62,9,14},那麼,1,4,5,6,2,7 則是第一趟排序的左邊,7,8,62,9,14則是第一趟排序的右邊。既然現在确定好了第一趟排序的結果,那麼現在隻需要對第一趟排序的左邊在尋找一個支點,繼續對這裡進行邏輯判斷排序、以此類推,這樣就最終實作了

支點左邊的數比支點小,支點右邊的數比支點大

前面說到,快速排序整個排序過程都可以 

 進行,但是構成遞歸需必須具備兩個條件:首先,子問題須與原始問題為同樣的事,且更為簡單(這裡的意思就是找支點 、對支點左右側進行邏輯判斷);其次不能無限制地調用本身,須有個出口,化簡為非遞歸狀況處理。

那麼下面我們就一步步的完成快速排序:

首先,我們定義一個數組,假設它的支點是數組的中間,那麼計算數組的中間值隻需要知道數組最後一個索引即可(因為第一個索引是0),于是,我們就有了以下代碼:

Java常見排序基礎 - 中

快速排序 - 1

既然我們知道了數組的支點,就可以對其進行判斷了,判斷的步驟簡單點了解 就是 arr[支點]與arr[索引]的具體大小判斷,由于這裡局部變量定義的left與right,分别是數組的最小索引以及數組的最大索引,也就是arr[0]和arr[ arrLength - 1]。是以,為了讓快速排序進行判斷的話 隻需要數組最小索引不斷累加、最大索引不斷累減、依次判斷。隻要 累加之後的最小索引和累減之後的最大索引 次數不一緻即可一直讓其在内部判斷。那麼什麼時候讓最小索引不斷累加,什麼時候讓最大索引不斷累減?理論上來說就是支點左邊的數值比支點小,即可讓其自增;支點右側的數值比支點大,則讓其自減。是以,最後可以這樣寫:

Java常見排序基礎 - 中

快速排序

值得注意的是,快速排序是一種不穩定的排序算法,也就是說,多個相同的值的相對位置也許會在算法結束時産生變動。關于快速排序的内容就介紹到這裡。

歸并排序:

歸并排序(MERGE-SORT)是建立在歸并操作上的一種有效的排序算法,該算法是采用分治法(Divide and Conquer)的一個非常典型的應用。将已有序的子序列合并,得到完全有序的序列;

即先使每個子序列有序,再使子序列段間有序

。若将兩個有序表合并成一個有序表,稱為二路歸并。

歸并排序過程如下:

比較 a [ i ] 和 b [ j ] 的大小,若 a [ i ] ≤ b [ j ],則将第一個有序表中的元素 a [ i ] 複制到 r [ k ] 中,并令 i 和 k 分别加上1;否則将第二個有序表中的元素 b [ j ] 複制到r [ k ]中,并令j和k分别加上1,如此循環下去,直到其中一個有序表取完,然後再将另一個有序表中剩餘的元素複制到r中從下标k到下标t的單元。歸并排序的算法我們通常用遞歸實作,先把待排序區間 [ s,t ] 以中點二分,接着把左邊子區間排序,再把右邊子區間排序,最後把左區間和右區間用一次歸并操作合并成有序的區間[ s,t ]。

歸并操作的工作原理如下:

第一步:申請空間,使其大小為兩個已經排序序列之和,該空間用來存放合并後的序列

第二步:設定兩個指針,最初位置分别為兩個已經排序序列的起始位置

第三步:比較兩個指針所指向的元素,選擇相對小的元素放入到合并空間,并移動指針到下一位置

重複步驟3直到某一指針超出序列尾

将另一序列剩下的所有元素直接複制到合并序列尾

歸并排序的演算過程:

假設現在有兩個已排好順序的數組:

int [ ] arr1 = {2, 7, 8} 、int [ ] arr2 = {1, 4, 9}

還有一個大數組來裝載它們

int  [ ] arr = new int [6] ;

步驟一:

那麼,首先将兩個數組的值進行比較,

誰的值比較小,誰就放入大數組!

比較步驟:拿出arr1[0]和arr2[0]進行比較,顯然是arr2[0]比較小,是以将arr2[0]放入大數組中,

同時arr2指針往後一格(也就是 +1,累加) ,

是以,現在目前為止arr = {1}

步驟二:

接着,拿arr1 [0] 和arr2 [1] 進行比較,arr1[0]較小,将arr1[0]放入大數組中,

同時arr1指針往後一格

是以,現在目前為止arr = {1,2}

步驟三:

然後,拿arr1[1] 和arr2[1] 進行比較,顯然是arr2[1] 比較小,将arr2[1] 放入大數組中,

同時arr2指針往後一格

是以,目前arr = {1,2,4}

…以此類推…

到最後,我們會将兩個已排好序的數組變成一個已排好序的數組,也就是 arr = {1,2,4,7,8,9};至此,整個演算過程結束。

可能你會說,既然歸并排序的前提是需要兩個已經排好順序的數組,但是一般沒有兩個已經排好順序的數組給我們,因為排序的本質就是解決錯綜複雜的大小關系,那這個算法是不是有點本末倒置的剛?

其實不是,下面我們繼續分析。假設現在有這樣一個數組:

 int [ ] arr = {2, 7, 8, 1, 4, 9} ;

如果要對此數組做歸并排序,首先就可以用arr [3],也就是元素為1的那個地方分開。然後用一個指針L指向arr[0],一個指針M指向arr[3],用一個指針R指向arr[5](數組最後一位)。有了指針的幫助,我們就可以将這個數組切割成是兩個有序的數組了(操作的方式就可以和上面一樣了)。但實際開發中中,數組一般是順序錯亂的,類似這樣一個數組:int [ ] arrays = { 9, 2, 5, 1, 3, 2, 9, 5, 2, 1, 8 } ;如果遇到這種情況我們該如何用歸并排序去思考問題?

我們可以這樣想,首先将 int[ ] arr = {2, 7, 8, 1, 4, 9} 這個數組根據索引分隔成每一份,arr[0]它是一個有序的"數組",arr[1]它也是一個有序的"數組",接着利用指針(L,M,R)又可以像操作兩個數組一樣進行排序。最終合成{2,7}…….再不斷拆分合并,是以最後又回到了我們的arr = {1,2,4,7,8,9}。是以,歸并排序可以排序雜亂無章的數組。

将一個大問題分成很多個小問題進行解決,最後重新組合起來,這種思想一般稱為:分治法,

很明顯,歸并排序也可以借鑒使用這種思想去操作。

    * 合并數組

    * @param arrays

    * @param L      指向數組第一個元素

    * @param M      指向數組分隔的元素

    * @param R      指向數組最後的元素

    public static void merge(int[] arrays, int L, int M, int R) {

        //左邊的數組的大小 == 也就是中間的索引減去最小索引

        int [ ] leftArray = new int[M - L];

        //右邊的數組大小 == 數組最大索引減去中間的索引 還要+1

        int [ ] rightArray = new int[R - M + 1];

        //往這兩個數組填充資料

        for (int i = L; i < M; i++) {

            leftArray[i - L] = arrays[i];

        }

        for (int i = M; i <= R; i++) {

            rightArray[i - M] = arrays[i];

        int i = 0, j = 0;

        // arrays數組的第一個元素

        int  k = L;

        //比較這兩個數組的值,哪個小,就往數組上放

        while (i < leftArray.length && j < rightArray.length) {

            //誰比較小,誰将元素放入大數組中,移動指針,繼續比較下一個

            if (leftArray[i] < rightArray[j]) {

                arrays[k] = leftArray[i];

                i++;

                k++;

            } else {

                arrays[k] = rightArray[j];

                j++;

            }

}

        //如果左邊的數組還沒比較完,右邊的數都已經完了,那麼将左邊的數抄到大數組中(剩下的都是大數字)

        while (i < leftArray.length) {

            arrays[k] = leftArray[i];

            i++;

            k++;

        //如果右邊的數組還沒比較完,左邊的數都已經完了,那麼将右邊的數抄到大數組中(剩下的都是大數字)

        while ( j < rightArray.length ) {

            arrays[k] = rightArray[j];

            j++;

    }

以上代碼是合并的代碼,下面是歸并排序的參考代碼:

Java常見排序基礎 - 中

歸并排序

最後是測試代碼:

Java常見排序基礎 - 中

測試代碼

另外:歸并排序是一種穩定的算法。

如果這篇文章對你有幫助,希望各位看官留下寶貴的star,謝謝。

Ps:著作權歸作者所有,轉載請注明作者, 商業轉載請聯系作者獲得授權,非商業轉載請注明出處(開頭或結尾請添加轉載出處,添加原文url位址),文章請勿濫用,也希望大家尊重筆者的勞動成果。