1959 年一個叫Donald L. Shell (March 1, 1924 – November 2, 2015)
的美國人在Communications of the ACM 國際計算機學會月刊
發佈了一個排序算法,今後名爲希爾排序的算法誕生了。算法
注:ACM = Association for Computing Machinery
,國際計算機學會,世界性的計算機從業員專業組織,創立於1947年,是世界上第一個科學性及教育性計算機學會。segmentfault
希爾排序是直接插入排序的改進版本。由於直接插入排序對那些幾乎已經排好序的數列來講,排序效率極高,達到了O(n)
的線性複雜度,可是每次只能將數據移動一位。希爾排序創造性的能夠將數據移動n
位,而後將n
一直縮小,縮到與直接插入排序同樣爲1
,請看下列分析。數組
希爾排序屬於插入類排序算法。數據結構
有一個N
個數的數列:併發
N
的整數d1
,將位置是d1
整數倍的數們分紅一組,對這些數進行直接插入排序。d1
的整數d2
,將位置是d2
整數倍的數們分紅一組,對這些數進行直接插入排序。d2
的整數d3
,將位置是d3
整數倍的數們分紅一組,對這些數進行直接插入排序。d=1
,接着使用直接插入排序。這是一種分組插入方法,最後一次迭代就至關因而直接插入排序,其餘迭代至關於每次移動n
個距離的直接插入排序,這些整數是兩個數之間的距離,咱們稱它們爲增量。數據結構和算法
咱們取數列長度的一半爲增量,之後每次減半,直到增量爲1。函數
舉個簡單例子,希爾排序一個 12 個元素的數列:[5 9 1 6 8 14 6 49 25 4 6 3]
,增量d
的取值依次爲:6,3,1
:code
x 表示不須要排序的數 取 d = 6 對 [5 x x x x x 6 x x x x x] 進行直接插入排序,沒有變化。 取 d = 3 對 [5 x x 6 x x 6 x x 4 x x] 進行直接插入排序,排完序後:[4 x x 5 x x 6 x x 6 x x]。 取 d = 1 對 [4 9 1 5 8 14 6 49 25 6 6 3] 進行直接插入排序,由於 d=1 徹底就是直接插入排序了。
越有序的數列,直接插入排序的效率越高,希爾排序經過分組使用直接插入排序,由於步長比1
大,在一開始能夠很快將無序的數列變得不那麼無序,比較和交換的次數也減小,直到最後使用步長爲1
的直接插入排序,數列已是相對有序了,因此時間複雜度會稍好一點。協程
在最好狀況下,也就是數列是有序時,希爾排序須要進行logn
次增量的直接插入排序,由於每次直接插入排序最佳時間複雜度都爲:O(n)
,所以希爾排序的最佳時間複雜度爲:O(nlogn)
。排序
在最壞狀況下,每一次迭代都是最壞的,假設增量序列爲:d8 d7 d6 ... d3 d2 1
,那麼每一輪直接插入排序的元素數量爲:n/d8 n/d7 n/d6 .... n/d3 n/d2 n
,那麼時間複雜度按照直接插入的最壞複雜度來計算爲:
假設增量序列爲 ⌊N/2⌋ ,每次增量取值爲比上一次的一半小的最大整數。 O( (n/d8)^2 + (n/d7)^2 + (n/d6)^2 + ... + (n/d2)^2 + n^2) = O(1/d8^2 + 1/d7^2 + 1/d6^2 + ... + 1/d2^2 + 1) * O(n^2) = O(等比爲1/2的數列和) * O(n^2) = O(等比求和公式) * O(n^2) = O( (1-(1/2)^n)/(1-1/2) ) * O(n^2) = O( (1-(1/2)^n)*2 ) * O(n^2) = O( 2-2*(1/2)^n ) * O(n^2) = O( < 2 ) * O(n^2)
因此,希爾排序最壞時間複雜度爲O(n^2)
。
不一樣的分組增量序列,有不一樣的時間複雜度,可是沒有人可以證實哪一個序列是最好的。Hibbard
增量序列:1,3,7,···,2n−1
是被證實可普遍應用的分組序列,時間複雜度爲:Θ(n^1.5)
。
希爾排序的時間複雜度大約在這個範圍:O(n^1.3)~O(n^2)
,具體還沒法用數學來嚴格證實它。
希爾排序不是穩定的,由於每一輪分組,都使用了直接插入排序,但分組會跨越n
個位置,致使兩個相同的數,發現不了對方而產生了順序變化。
package main import "fmt" // 增量序列折半的希爾排序 func ShellSort(list []int) { // 數組長度 n := len(list) // 每次減半,直到步長爲 1 for step := n / 2; step >= 1; step /= 2 { // 開始插入排序,每一輪的步長爲 step for i := step; i < n; i += step { for j := i - step; j >= 0; j -= step { // 知足插入那麼交換元素 if list[j+step] < list[j] { list[j], list[j+step] = list[j+step], list[j] continue } break } } } } func main() { list := []int{5} ShellSort(list) fmt.Println(list) list1 := []int{5, 9} ShellSort(list1) fmt.Println(list1) list2 := []int{5, 9, 1, 6, 8, 14, 6, 49, 25, 4, 6, 3} ShellSort(list2) fmt.Println(list2) list3 := []int{5, 9, 1, 6, 8, 14, 6, 49, 25, 4, 6, 3, 2, 4, 23, 467, 85, 23, 567, 335, 677, 33, 56, 2, 5, 33, 6, 8, 3} ShellSort(list3) fmt.Println(list3) }
輸出:
[5] [5 9] [1 3 4 5 6 6 6 8 9 14 25 49] [1 2 2 3 3 4 4 5 5 6 6 6 6 8 8 9 14 23 23 25 33 33 49 56 85 335 467 567 677]
按照以前分析的幾種排序算法,通常建議待排序數組爲小規模狀況下使用直接插入排序,在規模中等的狀況下可使用希爾排序,但在大規模仍是要使用快速排序,歸併排序或堆排序。
我是陳星星,歡迎閱讀我親自寫的 數據結構和算法(Golang實現),文章首發於 閱讀更友好的GitBook。