內容介紹
希爾排序簡介
冒泡排序在1956年就已經被研究。可是排序速度是比較慢的,冒泡排序的時間複雜度是O(n^2),然而在後面的很長的時間裏,儘管人們發明各類排序算法(好比前面選擇排序和插入排序),但時間複雜度都是0(n^2),時間複雜度彷佛無法超越0(n^2)。此時,計算機學術界充斥着「排序算法不可能突破O(n^2)的聲音」。終於有一天,當一位科學家發佈超越了0(n^2)新排序算法後,緊接着就出現了好幾種能夠超越0(n^2)的排序算法,並把內排序算法的時間複雜度提高到了0(nlogn)。 「不可能超越0(n^2)」完全成爲了歷史。java
希爾排序按其設計者希爾(Donald Shell)的名字命名,該算法由1959年公佈。希爾排序也是一種插入排序,它是簡單插入排序通過改進以後的一個更高效的版本,也稱爲縮小增量排序,在這以前排序算法的時間複雜度基本都是0(n^2)的,希爾排序算法是突破這個時間複雜度的第一批算法之一。算法
前面講的插入排序,它的效率在數據近乎有序時候是很高的,只須要少許的插入操做,就能夠完成整個排序工做,此時直接插入很高效。還有就是數據比較少時,直接插入的優點也比較明顯。可問題在於,兩個條件自己就過於苛刻,實際開發中數據少或者基本有序都屬於特殊狀況。shell
不過別急,條件不存在,咱們創造條件也是能夠去作的。希爾排序(Shell'sSort)是插入排序的一種又稱「縮小增量排序」(Diminishing Increment Sort),是直接插入排序算法的一種更高效的改進版本。該方法因D.L.Shell於1959年提出而得名。雖然插入排序是O(n^2)的排序複雜度,可是若是已是有序的,則能夠提早結束,能夠提升效率咱們知道插入排序在近乎有序的數組中效率很高, 如何讓待排序的記錄數量少而且近乎有序呢?很容易想到的就是將本來有大量記錄數的記錄進行分組。分割成若干個子序列,此時每一個子序列待排序的記錄個數就比較少了,而後在這些子序列內分別進行直接插入排序,當整個序列都基本有序時,注意只是基本有序時,再對全體記錄進行一次直接插入排序。編程
有同窗會疑惑。這不對呀,好比咱們如今有個序列是{9, 1, 5, 8, 3, 7, 4, 6, 2},如今將它分紅三組:{9, 1, 5},{8, 3, 7},{4, 6, 2},哪怕將它們各自排序排好了,變成{1, 5, 9},{3, 7, 8},{2,4,6}再合併它們成{1, 5, 9, 3, 7, 8, 2, 4, 6},此時,這個序列仍是雜亂無序,談不上基本有序,要排序仍是重來一遍直接插入有序,這樣作沒有用。須要強調一下,所謂的基本有序,就是小的關鍵字基本在前面,大的基本在後面,不大不小的基本在中間,像{2, 1, 3, 6, 4, 7, 5, 8, 9}這樣 能夠稱爲基本有序了。但像{1, 5, 9, 3, 7, 8, 2, 4, 6} 這樣的9在第三位,2在倒數第三位就談不上基本有序。問題其實也就在這裏,咱們分割待排序記錄的目的是減小待排序記錄的個數,並使整個序列向基本有序發展。而像上面這樣分完組後就各自排序的方法達不到咱們的要求。所以,咱們須要採起跳躍分割的策略:將相距某個「增量」 的記錄組成一一個子序列,這樣才能保證在子序列內分別進行直接插入排序後獲得的結果是基本有序而不是局部有序。數組
希爾排序的思想
希爾排序,經過增量(gap)將元素兩兩分組,對每組使用直接插入排序算法排序;增量(gap)逐漸減小,當增量(gap)減至1時,整個數據恰被分紅一組,最後進行一次插入排序,整個數組就有序了。微信
希爾排序動畫演示
通常沒有特殊要求排序算法都是升序排序,小的在前,大的在後。 數組由{7, 3, 1, 9, 5, 4, 2, 8, 6} 這9個無序元素組成。學習
第一次:gap = 9/2 = 4 動畫: 動畫
第二次:gap = 4/2 = 2 動畫: 設計
第三次:gap = 2/2 = 1 動畫: 3d
希爾排序分析
希爾排序,經過增量(gap)將元素分紅n組,對每組使用直接插入排序算法排序。增量(gap)逐漸減小,當增量(gap)減至1時,整個數據恰被分紅一組,最後進行一次插入排序。
第一次:gap = 9/2 = 4,每一個元素和後面的元素之間間隔4,元素被分紅4組。其中元素7, 5, 6分紅一組,元素3,4分紅一組,元素1, 2分紅一組,元素9,8分紅一組,效果以下圖:
第二次:gap = 4/2 = 2,每一個元素和後面的元素之間間隔2,元素被分紅2組。
第三次:gap = 2/2 = 1,每一個元素和後面的元素之間間隔1,元素被分紅1組。
希爾排序代碼編寫
回顧一下剛纔的分析,當咱們有9個元素時,gap分別取值4, 2, 1。
元素數量不一樣gap取值也不一樣,須要一個循環控制gap的取值。當取好gap後須要使用插入排序對每組進行排序,須要使用嵌套循環。 在進行小範圍插入排序時,要注意從哪一個位置開始。咱們先回顧一下以前學習的插入排序,第一個元素6
不須要動,從第二元素5
開始才須要進行比較和插入。
希爾排序拆分紅多個小組後,每一個小組使用插入排序,也是從每一個小組的第二個數據開始比較插入,以下圖所示:
從上圖中能夠看出,第一組從索引4開始比較,第二組從索引5開始比較,第三組從索引6開始比較,第四組從索引7開始比較。
希爾排序代碼以下:
public class ShellSortTest2 { public static void main(String[] args) { int[] arr = new int[] {7, 3, 1, 9, 5, 4, 2, 8, 6}; shellSort(arr); } public static void shellSort(int[] arr) { // 增量gap,經過gap來劃分出不一樣的子序列,gap不斷減小,劃分的子序列更多 for (int gap = arr.length / 2; gap > 0; gap /= 2) { // 循環獲得不一樣的gap,gap = 4, 2, 1 System.out.println("gap: " + gap); for (int i = gap; i < arr.length; i++) { // 每組使用插入排序 for (int j = i; j-gap >= 0; j -= gap) { if (arr[j-gap] > arr[j]) { swap(arr, j-gap, j); System.out.print("\t交換: " + arr[j-gap] + "和" + arr[j]); System.out.println(Arrays.toString(arr)); } } } } } public static void swap(int[] arr, int start, int end) { int temp = arr[start]; arr[start] = arr[end]; arr[end] = temp; } }
執行效果:
gap: 4 交換: 5和7[5, 3, 1, 9, 7, 4, 2, 8, 6] 交換: 8和9[5, 3, 1, 8, 7, 4, 2, 9, 6] 交換: 6和7[5, 3, 1, 8, 6, 4, 2, 9, 7] gap: 2 交換: 1和5[1, 3, 5, 8, 6, 4, 2, 9, 7] 交換: 4和8[1, 3, 5, 4, 6, 8, 2, 9, 7] 交換: 2和6[1, 3, 5, 4, 2, 8, 6, 9, 7] 交換: 2和5[1, 3, 2, 4, 5, 8, 6, 9, 7] gap: 1 交換: 2和3[1, 2, 3, 4, 5, 8, 6, 9, 7] 交換: 6和8[1, 2, 3, 4, 5, 6, 8, 9, 7] 交換: 7和9[1, 2, 3, 4, 5, 6, 8, 7, 9] 交換: 7和8[1, 2, 3, 4, 5, 6, 7, 8, 9]
希爾排序的增量和複雜度
經過上面動畫和代碼,相信你們有些明白,希爾排序的關鍵並非隨便分組後各自排序,而是將相隔某個「增量」的記錄組成一個子序列,實現跳躍式的移動,使得排序的效率提升。這裏"增量」的選取就很是關鍵了。咱們在代碼中是用int gap = arr.length / 2;
的方式選取增量的,可究竟應該選取什麼樣的增量纔是最好,目前仍是一個數學難題,迄今爲止尚未人找到一種最好的增量序列。
迄今爲止,除了在一些特殊的狀況下,尚未人可以從理論上分析希爾排序的效率。有各類各樣基於試驗的評估,估計它的時間複雜度從0(N^3/2)到0(N^7/6)。
希爾排序常見的增量序列:
-
Shell 增量序列,Shell 增量序列的遞推公式爲:
-
Hibbard 增量序列通項公式爲:
-
Knuth 增量序列通項公式爲:
-
Gonnet 增量序列遞推公式爲:
-
Sedgewick 增量序列通項公式爲:
須要注意的是,增量序列的最後一個增量值必須等於1才行,最後一趟排序是一次普通的插入排序。希爾排序算法的發明,使得咱們終於突破了慢速排序以後,更爲高效的排序算法也就相繼出現了。
總結
希爾排序,是直接插入排序算法的一種更高效的改進版本。經過增量(gap)將元素進行分組,對每組使用直接插入排序算法排序;增量(gap)逐漸減小,當增量(gap)減至1時,整個數據恰被分紅一組,最後進行一次插入排序。
原創文章和動畫製做真心不易,您的點贊就是最大的支持! 想了解更多文章請關注微信公衆號:表哥動畫學編程