【小小前端】前端排序演算法第二期(繞人的希爾排序)

.Ping發表於2020-03-07

希爾排序(Shell Sort)

上回說到,三大基本排序氣泡排序、選擇排序和插入排序。

其中插入排序又叫直接插入排序,其核心思想是通過構建有序序列,對未排序序列中選出首位資料,從已排序序列從後向前掃描,找到相應位置並插入。直接插入排序對小規模資料或基本有序資料十分高效。

希爾排序,1959年由Donald Shell發明,他是第一個突破O(n²)的排序演算法。希爾排序是直接插入排序的改進版(你問我為什麼不和直接插入排序寫在一起?博主太笨了,希爾排序研究了兩天才理解其中繞來繞去的迴圈--)。

希爾排序將序列分割成若干小序列(邏輯上分組),對每一個小序列進行插入排序,此時每一個小序列資料量小,插入排序的效率也提高了。

演算法描述

  • 選擇一個增量序列,t1,t2....tk,其中ti>tj,tk = 1;
  • 按增量序列個數k,對序列進行k次排序;
  • 每次排序,根據增量ti,將待排序序列分成若干子序列,分別對子序列進行直接插入排序。當增量為tk也就是1時,進行最後一次排序,此時子序列為排序序列本身。

動圖演示

【小小前端】前端排序演算法第二期(繞人的希爾排序)

(好吧_(¦3」∠)_,這個圖太抽象了,得多看12345678遍)

程式碼實現

先來看另一篇部落格的運算圖:

【小小前端】前端排序演算法第二期(繞人的希爾排序)

該圖按下標距離為4進行分組,arr[0]和arr[4]為一組,arr[1]和arr[5]為一組,這裡的下標距離4就被稱為增量

【小小前端】前端排序演算法第二期(繞人的希爾排序)

對四個子序列進行插入排序之後:

【小小前端】前端排序演算法第二期(繞人的希爾排序)

此時四個子序列都是有序的,陣列變為:

【小小前端】前端排序演算法第二期(繞人的希爾排序)

然後縮小增量為上個增量的一半:2,繼續劃分分組,此時,每個分組元素個數多了,但是,陣列變的部分有序了,插入排序效率同樣比較高

【小小前端】前端排序演算法第二期(繞人的希爾排序)

最後設定增量為上一個增量的一半:1,則整個陣列被分為一組,此時,整個陣列已經接近有序了:

【小小前端】前端排序演算法第二期(繞人的希爾排序)

如果看到這裡你還不懂的話。。。。。。那你跟我一樣笨,繼續再看12345678遍就好了O(∩_∩)O。

    let arr = [3, 45, 16, 8, 65, 15, 36, 22, 19, 1, 96, 12, 56, 12, 45];
    let len = arr.length;
    let willInsertValue; 
    let gap = len; // 定義增量
    // 動態定義增量序列,每一次增量變為上次一半,最後一次的gap為1
    while(gap>0&&(gap = Math.trunc(gap/2))){
        // 對每個分組進行插入排序,為什麼i開始是gap,因為插入排序預設第一位是已排序序列,arr[gap]是第一個分組第二位
        for(let i = gap;i<len;i++){
            // 待進行插入的值為a[i]
            willInsertValue = arr[i];
            // 按組進行插入,這裡比較繞人,前面說了,只是邏輯上的分組,實際上還是一個序列,這裡按組插入的時候是交叉
            // 進行插入
            let j = i - gap;
            // 下面就是個直接插入排序,只不過每次移位的時候下標差值為gap
            while(j>=0&&arr[j]>willInsertValue){
                arr[j+gap] = arr[j];
                j -= gap
            }
            arr[j+gap] = willInsertValue
        }
    }

複製程式碼

輸出結果為:

【小小前端】前端排序演算法第二期(繞人的希爾排序)

分析一下複雜度:

空間複雜度依然是O(1)

Shell排序的執行時間依賴於增量序列。

好的增量序列的共同特徵:

  • 最後一個增量必須為1;
  • 應該儘量避免序列中的值(尤其是相鄰的值)互為倍數的情況。

這樣來看的話,上述栗子選擇的增量1,2,4這樣的其實並不是很好,使用這種增量序列時間複雜度最壞為O(n平方)。

Hibbard提出了另一個增量序列{1,3,7,...,2^k-1},這種序列的時間複雜度(最壞情形)為O(n^1.5)

Sedgewick提出了幾種增量序列,其最壞情形執行時間為O(n^1.3),其中最好的一個序列是{1,5,19,41,109,...}

對於結果來說,使用哪種增量都沒有影響,只要最後一次的增量變為1即可。上述栗子增量不能大於待排序序列的長度,否則gap為0,無法進行排序。

希爾排序又叫縮小增量排序。

最後說一下穩定性,由於希爾排序是交叉跳躍排序,所以是不穩定的排序。

總結

個人感覺在學習希爾排序的時候,難點在於最後交叉跳躍進行插入排序,前面說了是在邏輯上進行分組,思維完全被分組限制住了,總想著排序的時候也是按組排序,其實是以大序列的順序對子序列進行跳躍式排序。

適用場景

希爾排序是對直接插入排序的一種優化,可以用於大型的陣列,希爾排序比插入排序和選擇排序要快的多,並且陣列越大,優勢越大。

參考

下期預告

【小小前端】前端排序演算法第三期(不簡單選擇排序-堆排序)

相關文章