淺解前端必須掌握的算法(四):希爾排序

前言

雖然前端面試中不多會考到算法類的題目,可是你去大廠面試的時候就知道了,對基本算法的掌握對於從事計算機科學技術的咱們來講,仍是必不可少的,天天花上 10 分鐘,瞭解一下基本算法概念以及前端的實現方式。前端

另外,掌握了一些基本的算法實現,對於咱們平常開發來講,也是如虎添翼,能讓咱們的 js 業務邏輯更趨高效和流暢。面試

算法介紹

希爾排序,也稱遞減增量排序算法,是插入排序的一種更高效的改進版本。希爾排序是非穩定排序算法。——維基百科算法

希爾排序是 D.L.Shell 於 1959 年提出來的一種排序算法,在這以前排序算法的時間複雜度基本都是 O(n²),希爾排序算法是突破該事件複雜度的第一批算法之一。shell

科學家希爾研究出來的這種排序方法,對直接插入排序改進後能夠增長效率。數組

算法闡釋

上一節咱們講到的「直接插入排序」,它的效率在數組自己就是基本有序以及元素個數較少時,它的效率是很高的。但問題就是,這兩個條件自己就很苛刻。如何讓程序爭取實現這倆條件呢?答案就是講本來有大量元素的數組進行分組,分隔成若干子數組,這樣每一個子數組的待排序的元素個數就比較少了,而後在子數組內分別進行「直接插入排序」,當整個數組基本有序時,再對全體元素進行一次「直接插入排序」。bash

所謂基本有序,就是小的元素基本在前面,大的基本在後面,不大不小的基本在中間。要注意像 [2, 1, 3, 6, 4, 7, 5, 8, 9] 這樣的能夠稱爲基本有序,但 [1, 5, 9, 3, 7, 8, 2, 4, 6] 這樣的就談不上了。微信

所以咱們在分割子數組時,須要採起跳躍分割的策略:將相距某個增量的記錄組成一個子數組,這樣才能保證在子數組內分別進行直接插入排序後的獲得的結果是基本有序,而不是局部有序spa

希爾排序算法圖示

舉例說明

這個算法不管怎麼解釋都會顯得含糊不清,直接來個栗子,就拿上圖來講明。code

假設如今有一數組 arr:[8, 9, 1, 7, 2, 3, 5, 4, 6, 0],咱們設定初始化步長爲 gap = arr.length/2 = 10/2,即 5。按照咱們上面說的「跳躍分割策略」,按增量爲 5 分割子數組,將每列當作是一個子數組:cdn

// 列1 列2 列3 列4 列5
   8   9   1   7   2
   3   5   4   6   0
複製代碼

而後對每列進行類直接插入排序,可得:

// 列1 列2 列3 列4 列5
   3   5   1   6   0
   8   9   4   7   2
複製代碼

則此時原數組順序應變成:[3, 5, 1, 6, 0, 8, 9, 4, 7, 2],而後再縮小增量,gap = 5/2 = 2,則數組分割以下:

// 列1 列2
   3   5
   1   6
   0   8
   9   4
   7   2
複製代碼

繼續對每列進行直接插入排序,可得:

// 列1 列2
   0   2
   1   4
   3   5
   7   6
   9   8
複製代碼

則此時元素組順序應變成:[0, 2, 1, 4, 3, 5, 7, 6, 9, 8],這就是基本有序了。最後一輪再進行微調便可,因此此時增量應計算得爲:gap = 2/2 = 1,則直接對數組應用直接插入排序便可,最後獲得:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
複製代碼

具體實現

var shell_sort = function(arr){
  var i, j, temp, gap;
  var len = arr.length;

  // 逐步縮小增量
  for (gap=len>>1; gap>=1; gap>>=1) {
    // 類直接插入排序算法
    for (i=gap; i<len; i++) {
      if (arr[i] < arr[i-gap]) {
        temp = arr[i];
        for (j=i-gap; j>=0 && temp<arr[j]; j-=gap) {
          // 記錄後裔,查找插入位置
          arr[j+gap] = arr[j];
        }
        // 插入
        arr[j+gap] = temp;
      }
    }
  }

  return arr;
};

shell_sort([8, 9, 1, 7, 2, 3, 5, 4, 6, 0]);
複製代碼

不曉得你們有沒有觀察到,第一層循環裏面的兩層嵌套循環算法,其實就是「直接插入排序」,不一樣就在於多了一個變量 gap,但其實當 gap === 1 時,那就跟咱們上一節學到的算法,是徹底同樣的。

算法實現總結

經過以上代碼的剖析,你們能夠看到,希爾排序的關鍵不是簡單地按 1 爲增量進行分組排序後,再合併總體排序;而是選好一個初始化增量,不斷地遞減增量,每次遞減之間都須要通過一次直接插入排序,使得排序的效率提升。

另外只要最終增量爲 1,則任何增量序列均可以工做,由於最終當增量爲 1 時,算法就變爲「直接插入排序」,這就保證了數據必定會被排序。

複雜度分析

Donald Shell最初建議步長選擇爲 n/2 而且對步長取半直到步長達到1。雖然這樣取能夠比 O(n²) 類的算法(插入排序)更好,但這樣仍然有減小平均時間和最差時間的餘地。——維基百科

參考了一下維基百科及相關文章,得到以下結論:

  1. 希爾排序原始增量序列爲 n/(2^i),也就是:n/2, n/4, ..., 1;最壞狀況下時間複雜度爲 O(n²)
  2. Hibbard 提出的增量序列爲 2^k-1,也就是:1, 3, 7, ..., 2^k-1;最壞狀況下時間複雜度爲 O(n^(3/2))
  3. Sedgewick 提出的增量序列爲已知的最好增量序列,也就是:1, 5, 19, 41, 109, .... ;該項序列的項來自

綜上所述,希爾排序算法的出現,咱們終於突破了慢速排序的時代,也即超越了時間複雜度爲 O(n²)。後面的幾篇文章,咱們還會介紹更爲高效的排序算法。

參考連接

zh.wikipedia.org/wiki/%E5%B8…

faculty.simpson.edu/lydia.sinap…


微信公衆號 以爲本文不錯的話,分享一下給小夥伴吧~

相關文章
相關標籤/搜索