用Java寫算法之六:希爾排序

希爾排序(Shell's sort)是一種很是「神奇」的排序算法。說它「神奇」,是由於沒有任何人能清楚地說明它的性能到底能到什麼狀況。希爾排序因DL.Shell於1959年提出而得名。自從C. A. R. Hoare在1962年提出快速排序後,因爲其更爲簡單,通常採用快速排序。可是,很多數學家們仍是孜孜不倦地尋找希爾排序的最佳複雜度。做爲普通程序員,咱們能夠學習下希爾的思路。
java

順便說一句,在希爾排序出現以前,計算機界廣泛存在「排序算法不可能突破O(n2)」的觀點。希爾排序的出現打破了這個魔咒,很快,快速排序等算法相繼問世。從這個意義上說,希爾排序帶領咱們走向了一個新的時代。程序員


算法概述/思路算法

希爾排序的提出,主要基於如下兩點:shell

1.插入排序算法在數組基本有序的狀況下,能夠近似達到O(n)複雜度,效率極高。數組

2.但插入排序每次只能將數據移動一位,在數組較大且基本無序的狀況下性能會迅速惡化。ide


基於此,咱們可使用一種分組的插入排序方法,具體作法是:(以一個16元素大小的數組爲例)oop

1.選擇一個增量delta,該增量大於1,從數組中按此增量選擇出子數組進行一次直接插入排序。例如,若選擇增量爲5,則對下標爲0,5,10,15的元素進行排序。性能

2.保留該增量delta並依次移動首個元素進行直接插入排序,直到一輪完成。對於上面的例子,則依次對數組[1,6,11],[2,7,12],[3,8,13],[4,9,14]進行排序。學習

3.減少增量,不斷重複上述過程,直到增量減少爲1.顯然,最後一次爲直接插入排序。spa

4.排序完成。

從上面能夠看出,增量是不斷減少的,所以,希爾排序又被成爲「縮小增量排序」。

下面是希爾排序的示意圖(圖片來自維基百科):


代碼實現

實現代碼1:

public static void shellSort(int[] arr){
    int temp;
    for (int delta = arr.length/2; delta>=1; delta/=2){                              //對每一個增量進行一次排序
        for (int i=delta; i<arr.length; i++){              
            for (int j=i; j>=delta && arr[j]<arr[j-delta]; j-=delta){ //注意每一個地方增量和差值都是delta
                temp = arr[j-delta];
                arr[j-delta] = arr[j];
                arr[j] = temp;
            }
        }//loop i
    }//loop delta
}

實現代碼2:

public static void shellSort2(int[] arr){
    int delta = 1;
    while (delta < arr.length/3){//generate delta
        delta=delta*3+1;    // <O(n^(3/2)) by Knuth,1973>: 1, 4, 13, 40, 121, ...
    }         
    int temp;
    for (; delta>=1; delta/=3){
        for (int i=delta; i<arr.length; i++){              
            for (int j=i; j>=delta && arr[j]<arr[j-delta]; j-=delta){
                temp = arr[j-delta];
                arr[j-delta] = arr[j];
                arr[j] = temp;
            }
        }//loop i
    }//loop delta
}

算法性能/複雜度

希爾排序的增量數列能夠任取,須要的惟一條件是最後一個必定爲1(由於要保證按1有序)。可是,不一樣的數列選取會對算法的性能形成極大的影響。上面的代碼演示了兩種增量。

切記:增量序列中每兩個元素最好不要出現1之外的公因子!(很顯然,按4有序的數列再去按2排序意義並不大)。

下面是一些常見的增量序列。

第一種增量是最初Donald Shell提出的增量,即折半下降直到1。據研究,使用希爾增量,其時間複雜度仍是O(n2)。

第二種增量Hibbard:{1, 3, ..., 2^k-1}。該增量序列的時間複雜度大約是O(n^1.5)。

第三種增量Sedgewick增量:(1, 5, 19, 41, 109,...),其生成序列或者是9*4^i - 9*2^i + 1或者是4^i - 3*2^i + 1。

下面的表中有更多的增量(來自http://en.wikipedia.org/wiki/Shellsort):

算法穩定性

咱們都知道插入排序是穩定算法。可是,Shell排序是一個屢次插入的過程。在一次插入中咱們能確保不移動相同元素的順序,但在屢次的插入中,相同元素徹底有可能在不一樣的插入輪次被移動,最後穩定性被破壞,所以,Shell排序不是一個穩定的算法。


算法適用場景

Shell排序雖然快,可是畢竟是插入排序,其數量級並無後起之秀--快速排序O(n㏒n)快。在大量數據面前,Shell排序不是一個好的算法。可是,中小型規模的數據徹底可使用它。


參考資料

1.維基百科 http://zh.wikipedia.org/zh/%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F

相關文章
相關標籤/搜索