【譯】Swift演算法俱樂部-希爾排序

Andy_Ron發表於2018-12-29

本文是對 Swift Algorithm Club 翻譯的一篇文章。

Swift Algorithm Clubraywenderlich.com網站出品的用Swift實現演算法和資料結構的開源專案,目前在GitHub上有18000+⭐️,我初略統計了一下,大概有一百左右個的演算法和資料結構,基本上常見的都包含了,是iOSer學習演算法和資料結構不錯的資源。

?andyRon/swift-algorithm-club-cn是我對Swift Algorithm Club,邊學習邊翻譯的專案。由於能力有限,如發現錯誤或翻譯不妥,請指正,歡迎pull request。也歡迎有興趣、有時間的小夥伴一起參與翻譯和學習?。當然也歡迎加⭐️,?????。

本文的翻譯原文和程式碼可以檢視?swift-algorithm-club-cn/Shell Sort


希爾排序(Shell Sort)

希爾排序是插入排序的一種更高效的改進版本,方法是將原始列表分成較小的子列表,然後使用插入排序對其進行單獨排序。

Sapientia大學建立了一個很好的視訊,顯示了匈牙利民間舞蹈的過程。(譯註:類似希爾排序的過程,油管視訊需要翻牆)

怎麼執行的

插入排序是比較相連的元素,如果它們順序不對就交換它們,而希爾排序演算法會比較相距很遠的元素。

元素之間的距離稱為 gap。 如果被比較的元素的順序錯誤,則它們會在 gap 中交換。 這消除了插入排序中常見的許多中間副本。

譯註: gap已經被翻譯成步長/增量/間距等,為了避免歧義,本文就不做翻譯,直接寫成gap

這個想法是,通過在大 gap 上移動元素,陣列變得非常快速地部分排序。 這使得之後的排序過程更快,因為他們不再需要交換那麼多項。

一輪完成後,gap變小,新一輪開始。 這將重複,直到 gap 大小為1,此時演算法的功能就像插入排序一樣。 但是由於資料已經很好地排序,所以最後一輪可以非常快。

例子

假設我們想使用希爾排序對陣列 [64, 20, 50, 33, 72, 10, 23, -1, 4] 進行排序。

我們首先將陣列的長度除以2:

n = floor(9/2) = 4
複製程式碼

這是 gap 大小。

我們建立n子列表。 在每個子列表中,每一項的間隔是大小為ngap 。 在我們的示例中,我們需要製作其中四個子列表。 子列表按insertionSort()函式排序。

這可能沒有多大意義,所以讓我們仔細看看會發生什麼。

第一輪如下。 我們有n = 4,所以我們製作了四個子列表:

sublist 0:  [ 64, xx, xx, xx, 72, xx, xx, xx, 4  ]
sublist 1:  [ xx, 20, xx, xx, xx, 10, xx, xx, xx ]
sublist 2:  [ xx, xx, 50, xx, xx, xx, 23, xx, xx ]
sublist 3:  [ xx, xx, xx, 33, xx, xx, xx, -1, xx ]
複製程式碼

如您所見,每個子列表僅包含原始陣列中的每間隔4的項。 不在子列表中的項用xx表示。 所以第一個子列表是[64,72,4],第二個子列表是[20,10],依此類推。 我們使用這個“gap”的原因是我們不必實際製作新的陣列。 相反,我們將它們交織在原始陣列中。

我們現在在每個子列表上呼叫一次insertionSort()

插入排序的這個特定版本從後面到前面排序。子列表中的每個專案都與其他專案進行比較。如果它們的順序錯誤,則交換值並一直向下移動,直到我們到達子列表的開頭。

因此對於子列表0,我們將472交換,然後將464交換。 排序後,此子列表如下所示:

sublist 0:  [ 4, xx, xx, xx, 64, xx, xx, xx, 72 ]
複製程式碼

排序後的其他三個子列表:

sublist 1:  [ xx, 10, xx, xx, xx, 20, xx, xx, xx ]
sublist 2:  [ xx, xx, 23, xx, xx, xx, 50, xx, xx ]
sublist 3:  [ xx, xx, xx, -1, xx, xx, xx, 33, xx ]
複製程式碼

完整的陣列看上去是:

[ 4, 10, 23, -1, 64, 20, 50, 33, 72 ]
複製程式碼

它還沒有完全排序,但它比以前更加排序。 這完成了第一次輪操作。

在第二輪中,我們將 gap 大小除以2:

n = floor(4/2) = 2
複製程式碼

這意味著我們現在只建立兩個子列表:

sublist 0:  [  4, xx, 23, xx, 64, xx, 50, xx, 72 ]
sublist 1:  [ xx, 10, xx, -1, xx, 20, xx, 33, xx ]
複製程式碼

每個子列表包含每個間隔為2的項。 我們再次呼叫insertionSort()來對這些子列表進行排序。 結果是:

sublist 0:  [  4, xx, 23, xx, 50, xx, 64, xx, 72 ]
sublist 1:  [ xx, -1, xx, 10, xx, 20, xx, 33, xx ]
複製程式碼

請注意,在每個列表中只有兩個元素位置順序不對(譯註:sublist 0是64和50,sublist 1是10和-1)。 因此插入排序非常快。 那是因為我們已經在第一輪中對陣列進行了一些排序。

總陣列現在看起來像這樣:

[ 4, -1, 23, 10, 50, 20, 64, 33, 72 ]
複製程式碼

這樣就完成了第二輪。 最後一輪的gap是:

n = floor(2/2) = 1
複製程式碼

gap 大小為1表示我們只有一個子列表,即陣列本身,我們再次呼叫insertionSort()對其進行排序。 最終排序的陣列是:

[ -1, 4, 10, 20, 23, 33, 50, 64, 72 ]
複製程式碼

在大多數情況下,希爾排序的效能為O(n^2),如果幸運,則為 O(nlogn)。 該演算法是不穩定的排序; 它可能會改變具有相等值的元素的相對順序。

gap 序列

gap 序列”確定 gap 的初始大小以及每次迭代如何使 gap 變小。 良好的 gap 序列對於希爾排序表現良好非常重要。

上面實現例子中的 gap 序列是希爾原始版本中的 gap 序列:初始值是陣列大小的一半,然後每次除以2。 還有其他方法可以計算 gap 序列。

只是為了好玩...

這是 Matthijs 很久以前使用的一箇舊的Commodore 64 BASIC版本的希爾排序,並且移植到他曾經使用的幾乎所有程式語言中:

61200 REM S is the array to be sorted
61205 REM AS is the number of elements in S
61210 W1=AS
61220 IF W1<=0 THEN 61310
61230 W1=INT(W1/2): W2=AS-W1
61240 V=0
61250 FOR N1=0 TO W2-1
61260 W3=N1+W1
61270 IF S(N1)>S(W3) THEN SH=S(N1): S(N1)=S(W3): S(W3)=SH: V=1
61280 NEXT N1
61290 IF V>0 THEN 61240
61300 GOTO 61220
61310 RETURN
複製程式碼

程式碼

希爾排序的Swift實現:

public func insertSort(_ list: inout[Int], start: Int, gap: Int) {
    for i in stride(from: (start + gap), to: list.count, by: gap) {
        let currentValue = list[I]
        var pos = I
        while pos >= gap && list[pos - gap] > currentValue {
            list[pos] = list[pos - gap]
            pos -= gap
        }
        list[pos] = currentValue
    }
}

public func shellSort(_ list: inout [Int]) {
    var sublistCount = list.count / 2
    while sublistCount > 0 {
        for pos in 0..<sublistCount {
            insertionSort(&list, start: pos, gap: sublistCount)
        }
        sublistCount = sublistCount / 2
    }
}

var arr = [64, 20, 50, 33, 72, 10, 23, -1, 4, 5]

shellSort(&arr)
複製程式碼

擴充套件閱讀

希爾排序的維基百科

Rosetta code的希爾排序(譯註:大概70種不同語言實現希爾排序??)

作者:Mike Taghavi,Matthijs Hollemans
翻譯:Andy Ron
校對:Andy Ron


翻譯後補充

希爾排序,也稱遞減增量排序演算法,按其設計者希爾(Donald Shell)的名字命名,在1959年公佈。

定義

希爾排序是將待排序的陣列元素按下標的一定增量分組 ,分成多個子序列,然後對各個子序列進行直接插入排序演算法排序;然後依次縮減增量再進行排序,直到增量為1時,進行最後一次直接插入排序,排序結束。

希爾排序的原理圖

【譯】Swift演算法俱樂部-希爾排序

【譯】Swift演算法俱樂部-希爾排序

參考

圖解排序演算法(二)之希爾排序

排序:希爾排序(演算法)

相關文章