1. 程式人生 > >排序算法系列:Shell 排序演算法

排序算法系列:Shell 排序演算法

概述

希爾排序(Shell Sort)是 D.L.Shell 於 1959 年提出來的一種排序演算法,在這之前排序演算法的時間複雜度基本都是 O(n2) 的,希爾排序演算法是突破這個時間複雜度的第一批演算法之一。希爾排序是一種插入排序演算法。

版權說明

目錄

演算法簡介

希爾排序(Shell Sort)是 D.L.Shell 於 1959 年提出來的一種排序演算法,在這之前排序演算法的時間複雜度基本都是 O(n2) 的,希爾排序演算法是突破這個時間複雜度的第一批演算法之一。
                                              – 《大話資料結構》

演算法原理分析

在上一篇《排序算法系列:插入排序演算法》一文中,我們說到了一種複雜度為 O(n2) 的排序演算法。而對於插入排序而言,如果我們的排序序列基本有序,或是資料量比較小,那麼它的排序效能還是不錯的。為什麼?可能你會這樣問。資料量小,這個不用多說,對於多數排序演算法這一點都適用;如果一個序列已經基本有序(序列中小數普遍位於大數的前面),那麼我們在插入排序的時候,就可以在較少的比較操作之後使整體有序。如果,這一點你還不是很明白,可以先看看《排序算法系列:插入排序演算法》一文中的解釋。
基於上面基本有序和小資料量的提示,這裡就有了希爾排序的實現。希爾所做的就是分組,在每個分組中進行插入排序。如下就是希爾排序中分組的示意圖:
這裡寫圖片描述


在上圖中,我們將一個長度為 10 的序列,分組成 3 組。除最後一組以外的分組都包含了 4 個元素。可是,我們排序的時候並不是在這些分組內部進行的。而是我們要按照上面圓形的標號來進行插入排序,也就是排序中的 [4, 9, 7]。你可以先想一想這是為什麼,在文章的最後,我再說明這個問題。

上面從詳細地說明了希爾排序的由來及希爾排序的分組精髓。那麼現在就要說希爾排序中的插入排序,是的沒有錯。畢竟希爾排序的核心就是分組+插入排序嘛。在這個示意圖中,我們可以很明顯地看出它想表達的東西。這裡只是將[3, 9, 7]看成了一個整體,對於其它的元素,暫時與[3, 9, 7]無關。
這裡寫圖片描述

通過上面的兩步操作,我們可以得到一個序列 T1 = [4, 0, 6, 1, 7, 2, 8, 5, 9, 3]。咦,這個序列還是無序的呀,怎麼回事?我們說希爾排序的核心是分組和獲得一個基本有序的陣列。這裡說的是基本有序,並未做出承諾說這個序列已經有序。那麼,現在要做的就是繼續分組+插入排序。當然,對應的 step 是要進行修改的。詳細過程,參見下面的演算法步驟。

演算法步驟

通過上面的演算法原理分析,這裡可以總結出演算法實現的步驟。如下:
1. 獲得一個無序的序列 T0 = [4, 3, 6, 5, 9, 0, 8, 1, 7, 2],並計算出當前序列狀態下的步長 step = length / 3 + 1;
2. 對序列 T0 按照步長週期分組;
3. 對每個子序列進行插入排序操作;
4. 判斷此時的步長 step 是否大於 1?如果比 1 小或是等於 1,停止分組和對子序列的插入排序,否則,就繼續;
5. 修改此時的步長 step = step / 3 + 1,並繼續第 2 到第 4 步;
6. 排序演算法結束,序列已是一個整體有序的序列。

邏輯實現

這是Shell 排序的核心模組,也是分組的關鍵步驟

private void core(int[] array) {
        int arrayLength = array.length;
        int step = arrayLength;
        do {
            step = step / 3 + 1;
            for (int i = 0; i < step; i++) {
                insert(array, i, step);
            }
            System.err.println(Arrays.toString(array));
        } while (step > 1);
    }

希爾排序的直接插入排序,注意這裡不同的是它只是針對某一個分組子序列進行插入排序

private void insert(int[] array, int offset, int step) {
        int arrayLength = array.length;
        int groupCount = arrayLength / step + (arrayLength % step > offset ? 1 : 0);
        for (int i = 1; i < groupCount; i++) {
            int nextIndex = offset + step * i;
            int waitInsert = array[nextIndex];

            while(nextIndex - step >= 0 && waitInsert < array[nextIndex - step]) {
                array[nextIndex] = array[nextIndex - step];
                nextIndex -= step;
            }

            array[nextIndex] = waitInsert;
        }
    }

更多詳細邏輯實現,請參見文章結尾的原始碼連結。

複雜度分析

排序方法 時間複雜度 空間複雜度 穩定性 複雜性
平均情況 最壞情況 最好情況
Shell 排序 O(n3/2) O(n2) O(n) O(n) 不穩定 較複雜

問題解答

  1. 為什麼我們排序的時候並不是在這些分組內部進行的?
    答:這裡我們分組的目的在於要完成的是對整個序列的基本序列處理,那麼我們肯定想要讓這些分組的資料儘量地分散開。如果要針對每個分組內部進行插入排序,那麼之後的每次操作,都會是在內部進行的,這樣的結果就是每個分組內部已經有序,只是整體仍然是無序的。
  2. 分組步長的計算公式為什麼是 step = step / 3 + 1?
    答:這裡的步長計算很關鍵,可是究竟應該選取什麼樣的增量才是最好的,目前還尚未解決。不過大量的研究表明,當增量序列為 dlta[k] = 2tk+1 - 1 (0 <= k <= t <= [log2(n+1)])時,可以獲得不錯的效果,其時間複雜度為 O(n3/2)。

Ref

  • 《大話資料結構》

Github原始碼下載

相關推薦

排序系列Shell 排序演算法

概述 希爾排序(Shell Sort)是 D.L.Shell 於 1959 年提出來的一種排序演算法,在這之前排序演算法的時間複雜度基本都是 O(n2) 的,希爾排序演算法是突破這個時間複雜度的第一批演算法之一。希爾排序是一種插入排序演算法。 版權說

排序系列歸併排序演算法

概述 上一篇我們說了一個非常簡單的排序演算法——選擇排序。其複雜程式完全是冒泡級的,甚至比冒泡還要簡單。今天要說的是一個相對比較複雜的排序演算法——歸併排序。複雜的原因不僅在於歸併排序分成了兩個部分進行解決問題,而是在於,你需要一些演算法的思想支撐。 歸併排

排序系列快速排序演算法

概述  在前面說到了兩個關於交換排序的演算法:氣泡排序與奇偶排序。  本文就來說說交換排序的最後一拍:快速排序演算法。之所以說它是快速的原因,不是因為它比其他的排序演算法都要快。而是從實踐中證明了快速排序在平均效能上的確是比其他演算法要快一些,不然快速一說豈

排序系列歸併排序(Merge sort)(C語言)

通俗理解:運用分而治之的思想,編寫遞迴函式,將大陣列排序轉化為小陣列排序,最後再將其合併。void merge_sort(int*p,int low,int high) { int mid = (low+high)/2; if (low <high) { m

排序系列奇偶排序演算法

概述   在上一篇中我們說到了氣泡排序的原理及實現詳解。氣泡排序是一種交換排序,本文還是接著上一講,說說另一種交換排序演算法——奇偶排序。 版權說明 目錄 奇偶排序演算法 奇偶排序實際上在多處理器環境中很有用,處

系列冒泡排序

冒泡 return 二層 單向 while 數組 代碼 示例 執行 一、基本思路 通過兩兩比較,然後交換雙方位置的一種排序方法。 二、示例代碼 $arr = array(1,4,2,6,3,8); for($i=0;$i<count($arr)-1;$i++){

PHP排序系列希爾排序

希爾排序 希爾排序(Shell Sort)是插入排序的一種。也稱縮小增量排序,是直接插入排序演算法的一種更高效的改進版本。希爾排序是非穩定排序演算法。 原理 先將整個待排元素序列分割成若干個子序列(由相隔某個“增量(gap)”的元素組成的)分別進行直接

排序排序

family getch 電子郵箱 -s class csharp soft n) 郵件   在我們的生活的這個世界到處都是被排序過的東西。站隊的時候會按照身高排序,考試的名次需要按照分數排序,網上購物的時候會按照價格排序,電子郵箱中的郵件按照時間排序……可以說排序無處不在

(九)數據結構之簡單排序實現冒泡排序、插入排序和選擇排序

html lan 獎章 tmx 4tb wot 數據結構 lec get d59FG8075P7伊http://www.zcool.com.cn/collection/ZMTg2NTU2NjQ=.html 312V畏蝗淤ZP哦睬http://www.zcool.com.c

排序系列---希爾排序(C++)

希爾排序:明白它的原理之後,希爾演算法其實就是更加先進的一種插入排序,之前的直接插入排序是逐個比較,找到合適的位置就插入,希爾排序是跳躍式的插入,拿當前的數和n(自己定義的跳躍數)個之前的數比較,較小的數位置提前。希爾演算法相當於先把整個序列分割成幾塊序列,塊和

排序系列——八大排序演算法對比分析

本系列最後一篇,綜合分析下前面介紹的八種排序演算法的效率,以及各自的適用情況。 下面先看看八種排序演算法的時間複雜度表格: 圖中八種排序被分成了兩組,一組時間複雜度為O(n^2),另一組相對高效些。 下面先對第一組O(n^2)的四種排序演算法進行對比,

資料結構與系列十(排序演算法概述)

1.引子 1.1.為什麼要學習資料結構與演算法? 有人說,資料結構與演算法,計算機網路,與作業系統都一樣,脫離日常開發,除了面試這輩子可能都用不到呀! 有人說,我是做業務開發的,只要熟練API,熟練框架,熟練各種中介軟體,寫的程式碼不也能“飛”起來嗎? 於是問題來了:為什麼還要學習資料結構與演算法呢? #理

數據結構與系列研究九——排序的一些探討

停止 不同 位置 集合 完全 設置 img com 去除 四種排序 一.實驗內容 輸入20個整數,分別用希爾排序、快速排序、堆排序和歸並排序實現由小到大排序並輸出排序結果。二.關鍵數據結構與核心算法 關鍵數據結構:由於是排序為了簡單起見,選用線性表中的數組作為存

】一個小白的筆記排序 (>д<)

根節點 節點和 -- 樹形 new 示例 () 是否 family 參考資料 《算法(第4版)》 — — Robert Sedgewick, Kevin Wayne 什麽是二叉堆 在了解堆排序之前,

八大排序總結(五)快速排序

con 遞歸調用 結果 width 算法總結 調用 小數 排序算法總結 png 目的:掌握 快速排序 的 基本思想與過程、代碼實現、時間復雜度 1、基本思想與過程:(分治思想,挖坑填數)   (1)從數列中選擇一個數作為key值;   (2)將比這個數小的數全部放在它的左邊

JS排序總結(八)基數排序

clas style dig ret .com strong spa radi 基本 目的:掌握 基數排序 的 基本思想與過程、代碼實現、時間復雜度 1、基本思想與過程:(只針對數字)   (1)首先確定基數為10,數組的長度也就是10.每個數都會在這10個數中尋找自己的位

JS排序總結八種對比

線性 nlog 對比 http xxx 運行 bsp 排序 image 目的:掌握 排序算法的分類及不同算法復雜度的對比、搞清楚 XXX與數組初始狀態無關分為幾種情況:   a、算法復雜度與初始狀態無關;   b、元素總比較次數與初始狀態無關;   c、元素總移動次數與初始

資料結構與系列11--桶排序、計數排序、基數排序

線性排序演算法介紹 1.線性排序演算法包括桶排序、計數排序、基數排序 2.線性排序的演算法的時間複雜度為:O(n) 3.此三種排序演算法都不涉及元素之間的比較操作,是非基於比較的排序演算法。 4.對要排序的資料要求很苛刻,只能適用在某些特殊場景,所以重點掌握此3鍾演算法的適用場景。

圖解系列之選擇排序

選擇排序 (1)演算法描述 對於給定的一個線性空間,維護兩個區域——“已排序區域”和“未排序區域”。遍歷每一個元素,找出該元素後邊所有元素中,比當前元素絕對值相差最大的元素,與該元素交換位置。 (2)演算法圖解 void selectionSort(int arr[], int number);

圖解系列之插入排序(Low版)

(1)演算法描述 對於給定的線性空間,依次考察每個元素,當指定的元素比後一個元素大(或者小)的時候就交換位置,然後交換過來的後一個元素繼續向前比較,只要比該元素大(或者小)就兩兩交換,直到不符合交換條件或者到達最前端。 (2)演算法圖解 (3)C/C++程式碼實現 CustomSort.h