排序算法(四) 希爾排序(插入排序的進化)

參考
Java排序算法(四):希爾排序
常見排序算法 - 希爾排序 (Shell Sort)
數據結構和算法(Golang實現)(22)排序算法-希爾排序

希爾排序算法是按其設計者希爾(Donald Shell)的名字命名,該算法由1959年公布,是插入排序的一種更高效的改進版本。它的作法不是每次一個元素挨一個元素的比較。而是初期選用大跨步(增量較大)間隔比較,使記錄跳躍式接近它的排序位置;然后增量縮小;最后增量為 1 ,這樣記錄移動次數大大減少,提高了排序效率。希爾排序對增量序列的選擇沒有嚴格規定。
希爾排序是基于插入排序的以下兩點性質而提出改進方法的:

  • 插入排序在對幾乎已經排好序的數據操作時, 效率高, 即可以達到線性排序的效率
  • 但插入排序一般來說是低效的, 因為插入排序每次只能將數據移動一位

假設有數組 array = [80, 93, 60, 12, 42, 30, 68, 85, 10],首先取 d1 = 4,將數組分為 4 組,如下圖中相同顏色代表一組:

增量為4

然后分別對 4 個小組進行插入排序,排序后的結果為:

同顏色組內排序

然后,取 d2 = 2,將原數組分為 2 小組,如下圖:

增量為2

然后分別對 2 個小組進行插入排序,排序后的結果為:

同顏色組內排序

最后,取 d3 = 1,進行插入排序后得到最終結果:

增量為1

該方法的基本思想是:先將整個待排元素序列分割成若干個子序列(由相隔某個“增量”的元素組成的)分別進行直接插入排序,然后依次縮減增量再進行排序,待整個序列中的元素基本有序(增量足夠小)時,再對全體元素進行一次直接插入排序。因為直接插入排序在元素基本有序的情況下(接近最好情況),效率是很高的,因此希爾排序在時間效率上比前兩種方法有較大提高。

public class ShellSort {

    public static void main(String[] args) {
        int[] arr = { 6, 5, 3, 1, 8, 7, 2, 4 };
        System.out.println("排序之前:");
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }

        // 希爾排序
        shellSort(arr);

        System.out.println();
        System.out.println("排序之后:");
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
    }

    /**
     * 希爾排序
     */
    private static void shellSort(int[] arr) {
        int j;
        for (int gap = arr.length / 2; gap > 0; gap = gap / 2) {
            for (int i = gap; i < arr.length; i++) {
                int tmp = arr[i];
                for (j = i; j >= gap && tmp < arr[j - gap]; j = j - gap) {
                    arr[j] = arr[j - gap];
                }
                arr[j] = tmp;
            }
        }
    }

}

舉個簡單例子,希爾排序一個 12 個元素的數列:[5 9 1 6 8 14 6 49 25 4 6 3],增量d的取值依次為:6,3,1:

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(n1.3)~O(n2),具體還無法用數學來嚴格證明它。

希爾排序不是穩定的,因為每一輪分組,都使用了直接插入排序,但分組會跨越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)
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,885評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,312評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,993評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,667評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,410評論 6 411
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,778評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,775評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,955評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,521評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,266評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,468評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,998評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,696評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,095評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,385評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,193評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,431評論 2 378

推薦閱讀更多精彩內容

  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 將一個記錄插入到已排序好...
    依依玖玥閱讀 1,267評論 0 2
  • 數據結構與算法--排序之冒泡、選擇、插入、希爾 我們關注的主要對象是重新排列數組元素的算法,每個元素都有一個主鍵,...
    sunhaiyu閱讀 1,154評論 2 12
  • 概述 排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部...
    蟻前閱讀 5,211評論 0 52
  • 概述:排序有內部排序和外部排序,內部排序是數據記錄在內存中進行排序,而外部排序是因排序的數據很大,一次不能容納全部...
    每天刷兩次牙閱讀 3,740評論 0 15
  • 年年歲歲花相似 歲歲年年考不同 莘莘學子 決戰三日體能空 書不盡 十余載未放松 青春似火譜華章 成鳳或成龍 回眸 ...
    千秋筆閱讀 213評論 0 7