數(shù)據(jù)結(jié)構(gòu):希爾排序

前言

希爾排序是Donald Shell于1959年提出來(lái)的一種排序算法,它是第一批突破O(n2)這個(gè)時(shí)間復(fù)雜度的算法之一。大話(huà)數(shù)據(jù)結(jié)構(gòu)對(duì)這個(gè)算法的講解,我看得一知半解的,之后網(wǎng)上找了下資料,發(fā)現(xiàn)維基百科對(duì)這個(gè)算法的講解非常不錯(cuò),特在此整理一波。

原理

希爾排序是基于插入排序的以下兩點(diǎn)性質(zhì)而提出改進(jìn)方法的:

  • 插入排序在對(duì)幾乎已經(jīng)排好序的數(shù)據(jù)操作時(shí),效率高,即可以達(dá)到線(xiàn)性排序的效率
  • 但插入排序一般來(lái)說(shuō)是低效的,因?yàn)椴迦肱判蛎看沃荒軐?shù)據(jù)移動(dòng)一位
image

先上個(gè)維基百科的動(dòng)圖,不知道你們看不看得懂,反正我不是很懂……
什么鬼.png

說(shuō)說(shuō)我的個(gè)人理解:

希爾排序其實(shí)就是直接插入排序的升級(jí),原理就是先將整個(gè)待排序列按照某個(gè)增量(也稱(chēng)步長(zhǎng))分割成若干個(gè)子序列分別進(jìn)行直接插入排序,然后合并,之后依次縮小增量大小在進(jìn)行排序,當(dāng)增量足夠?。ㄍǔ?)時(shí),再對(duì)全體元素進(jìn)行直接插入排序,而此時(shí)需排序的數(shù)據(jù)幾乎是已排好的了,所以此時(shí)插入排序較快。

當(dāng)然如果你覺(jué)得文字比較乏味就看下面的這些例子吧

例如,假設(shè)有這樣一組數(shù)[9 1 5 8 3 7 4 6 2],如果我們先以步長(zhǎng)為4進(jìn)行分割,就是這樣:

9 1 5 8
3 7 4 6 
2

然后我們對(duì)每列進(jìn)行排序(注意每列哦):

2 1 4 6
3 7 5 8
9

將上述四行數(shù)字,依序合并我們得到:[ 2 1 4 6 3 7 5 8 9 ]。此時(shí)2已經(jīng)往前移,而8、9已經(jīng)在后兩位,然后再以2為步長(zhǎng)進(jìn)行分割:

2 1
4 6
3 7
5 8
9

繼續(xù)排序:

2 1
3 6
4 7
5 8
9

合并得到[ 2 1 3 6 4 7 5 8 9],此時(shí)序列已經(jīng)基本有序,需交換數(shù)據(jù)的情況大為減少,這時(shí)整列進(jìn)行直接插入排序效率就非常高。

最終完成排序過(guò)程,也就是步長(zhǎng)為1時(shí),得到最終序列為:1 2 3 4 5 6 7 8 9。

笑.png

示例代碼(C)

#include <stdio.h>
#define MAXSIZE 100 //用于要排序數(shù)組的最大值 

typedef struct      //定義一個(gè)順序表結(jié)構(gòu) 
{
    int r[MAXSIZE+1];   //用于存儲(chǔ)要排序數(shù)組,r[0]用作哨兵或者臨時(shí)變量 
    int length;         //用于存儲(chǔ)順序表的最大長(zhǎng)度 
}SqList;

void ShellSort(SqList *L)
{
    int i,j;
    int gap=L->length;  //獲取數(shù)組長(zhǎng)度 
    
    for(gap/=2;gap>=1;gap/=2)   //步長(zhǎng) 
        for(i=gap+1; i<=L->length; i++) //從第gap+1個(gè)元素開(kāi)始,因?yàn)閞[0]被當(dāng)做臨時(shí)變量 
            if(L->r[i] < L->r[i-gap])   //每個(gè)元素與自己組內(nèi)的數(shù)據(jù)進(jìn)行直接插入排序 
            {
                L->r[0]=L->r[i];    //把要交換的數(shù)據(jù)暫存的L->r[0]中 
                for(j=i-gap; j>0&&L->r[j] > L->r[0]; j-=gap)    
                    L->r[j+gap] = L->r[j];  //記錄后移,查找插入位置 
                    
                L->r[j+gap]=L->r[0];    //插入 
            }
} 

int main()
{
    int i=0;
    int array[] = {39,80,76,41,13,29,50,78,30,11,100,7,41,86};
    
    SqList L;
    L.length = sizeof(array)/sizeof(array[0]);  //獲取數(shù)組長(zhǎng)度 
    for(i=0;i<L.length;i++)
    {
        L.r[i+1]=array[i];  //把數(shù)組存入順序表結(jié)構(gòu) 
    }
    ShellSort(&L);
    
    //輸出排序后的數(shù)組 
    for(i=0;i<L.length;i++) 
    {
        printf("%d ",L.r[i+1]);
    }
    return 0;
}

可能有幾個(gè)步驟略難懂,這里解釋下:

  • 第17行:這里的步長(zhǎng)采用\frac {n} {2},最終判斷條件為gap>=1,這里不管你數(shù)組初始長(zhǎng)度為多少,除到最后均會(huì)等于1,而等于1時(shí),就是執(zhí)行最后一次循環(huán),這個(gè)時(shí)候也就是所有元素進(jìn)行直接插入排序。當(dāng)然也可寫(xiě)成gap>0。

  • 第18行:在前面定義順序表結(jié)構(gòu)時(shí),我們加多了一位,也就是把r[0]當(dāng)做交換數(shù)據(jù)時(shí)的臨時(shí)變量。

  • 第22~23行:對(duì)于這個(gè)循環(huán)我們直接拿上面的例子中的一列進(jìn)行講解(9 3 2)

image

當(dāng)i=5時(shí),9和3進(jìn)行了一次交換,變?yōu)?code>(3 9 2)(位置為1 5 9),之后在i=9時(shí)(此時(shí)r[0]=r[i]=2),做出的交換如上圖所示(圖略差...),分為三個(gè)步驟:(gap=4)

  • 第一次進(jìn)入循環(huán)j=i-gap=5,r[j]=r[5]=9>r[0],執(zhí)行23行得r[j+gap]=r[9]=r[j]=9,此時(shí)該列數(shù)值變?yōu)?code>(3 9 9);
  • 繼續(xù)第二次循環(huán)j=j-gap=1,r[j]=r[1]=3>r[0],同樣執(zhí)行循環(huán)r[j+gap]=r[5]=r[j]=3,此時(shí)變?yōu)?code>(3 3 9);
  • 跳出循環(huán)(注意:跳出循環(huán)時(shí)多執(zhí)行了一次j-=gap=-3?),執(zhí)行r[j+gap]=r[-3+4]=r[1]=r[0]=2?,最終變?yōu)?code>(2 3 9)。

步長(zhǎng)(增量)選擇

步長(zhǎng)的選取非常關(guān)鍵,但是步長(zhǎng)的選擇沒(méi)有統(tǒng)一規(guī)定,也沒(méi)絕對(duì)的規(guī)律。只要滿(mǎn)足最后一個(gè)步長(zhǎng)為1即可。Donald Shell最初建議步長(zhǎng)選擇為\frac {n} {2},雖然這樣去可以比O(n^2)類(lèi)的算法更好,但仍然有減少平均時(shí)間和最差時(shí)間的余地。維基百科給出的部分步長(zhǎng)與最壞情況下復(fù)雜度有:

image

已知的最好步長(zhǎng)序列是由Sedgewick提出的(1, 5, 19, 41, 109,...),該序列的項(xiàng)來(lái)自9\times4^i-9\times2^i+12^{i+2}\times(2^{i+2}-3)+1這兩個(gè)算式。這項(xiàng)研究也表明“比較在希爾排序中是最主要的操作,而不是交換?!庇眠@樣步長(zhǎng)序列的希爾排序比插入排序要快,甚至在小數(shù)組中比快速排序和堆排序(后續(xù)博客整理),但是在涉及大量數(shù)據(jù)時(shí)希爾排序還是比快速排序慢。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 概述 排序有內(nèi)部排序和外部排序,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序,而外部排序是因排序的數(shù)據(jù)很大,一次不能容納全部...
    蟻前閱讀 5,303評(píng)論 0 52
  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 將一個(gè)記錄插入到已排序好...
    依依玖玥閱讀 1,354評(píng)論 0 2
  • <希爾排序> 詳細(xì)代碼請(qǐng)參考Algorithm。參考代碼比文字好理解。希爾排序,也稱(chēng)遞減增量排序算法,是插入排序的...
    明明的魔樣閱讀 1,875評(píng)論 0 1
  • 序言: 除了憤怒和仇恨,我感覺(jué)不到任何情感。被絕望的氣息籠罩著,恐懼和無(wú)助張牙舞爪地向我撲來(lái),我無(wú)處可逃。我...
    褐返閱讀 489評(píng)論 0 1
  • 人生有很多不如意,但是你心心念念的卻未必是最適合你的。 三葉一直夢(mèng)想著去東京,離開(kāi)小鎮(zhèn)。有一天起來(lái)發(fā)現(xiàn)和一個(gè)東京的...
    惜惜皮皮閱讀 319評(píng)論 0 0

友情鏈接更多精彩內(nèi)容