聊聊希爾排序

前言

希爾排序是Donald Shell於1959年提出來的一種排序算法,它是第一批突破O(n^2)這個時間複雜度的算法之一。大話數據結構對這個算法的講解,我看得只知其一;不知其二的,以後網上找了下資料,發現維基百科對這個算法的講解很是不錯,特在此整理一波。算法

原理

希爾排序是基於插入排序的如下兩點性質而提出改進方法的:數組

  • 插入排序在對幾乎已經排好序的數據操做時,效率高,便可以達到線性排序的效率
  • 但插入排序通常來講是低效的,由於插入排序每次只能將數據移動一位

先上個維基百科的動圖,不知道大家看不看得懂,反正我不是很懂……bash

說說個人我的理解:數據結構

希爾排序其實就是直接插入排序的升級,原理就是先將整個待排序列按照某個增量(也稱步長)分割成若干個子序列分別進行直接插入排序,而後合併,以後依次縮小增量大小在進行排序,當增量足夠小(一般爲1)時,再對全體元素進行直接插入排序,而此時需排序的數據幾乎是已排好的了,因此此時插入排序較快。ui

固然若是你以爲文字比較乏味就看下面的這些例子吧spa

例如,假設有這樣一組數[9 1 5 8 3 7 4 6 2],若是咱們先以步長爲4進行分割,就是這樣:.net

9 1 5 8
3 7 4 6 
2
複製代碼

而後咱們對每列進行排序(注意每列哦):3d

2 1 4 6
3 7 5 8
9
複製代碼

將上述四行數字,依序合併咱們獲得:[ 2 1 4 6 3 7 5 8 9 ]。此時2已經往前移,而八、9已經在後兩位,而後再以2爲步長進行分割:code

2 1
4 6
3 7
5 8
9
複製代碼

繼續排序:cdn

2 1
3 6
4 7
5 8
9
複製代碼

合併獲得[ 2 1 3 6 4 7 5 8 9],此時序列已經基本有序,需交換數據的狀況大爲減小,這時整列進行直接插入排序效率就很是高。

最終完成排序過程,也就是步長爲1時,獲得最終序列爲:1 2 3 4 5 6 7 8 9

示例代碼(C)

#include <stdio.h>
#define MAXSIZE 100 //用於要排序數組的最大值 

typedef struct //定義一個順序表結構 {
	int r[MAXSIZE+1];	//用於存儲要排序數組,r[0]用做哨兵或者臨時變量 
	int length;			//用於存儲順序表的最大長度 
}SqList;

void ShellSort(SqList *L) {
	int i,j;
	int gap=L->length;	//獲取數組長度 
	
	for(gap/=2;gap>=1;gap/=2)	//步長 
		for(i=gap+1; i<=L->length; i++)	//從第gap+1個元素開始,由於r[0]被當作臨時變量 
			if(L->r[i] < L->r[i-gap])	//每一個元素與本身組內的數據進行直接插入排序 
			{
				L->r[0]=L->r[i];	//把要交換的數據暫存的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]);	//獲取數組長度 
    for(i=0;i<L.length;i++)
    {
		L.r[i+1]=array[i];	//把數組存入順序表結構 
	}
	ShellSort(&L);
	
	//輸出排序後的數組 
	for(i=0;i<L.length;i++)	
	{
		printf("%d ",L.r[i+1]);
	}
	return 0;
}
複製代碼

可能有幾個步驟略難懂,這裏解釋下:

  • 第17行:這裏的步長採用\frac {n} {2},最終判斷條件爲gap>=1,這裏無論你數組初始長度爲多少,除到最後均會等於1,而等於1時,就是執行最後一次循環,這個時候也就是全部元素進行直接插入排序。固然也可寫成gap>0。

  • 第18行:在前面定義順序表結構時,咱們加多了一位,也就是把r[0]當作交換數據時的臨時變量。

  • 第22~23行:對於這個循環咱們直接拿上面的例子中的一列進行講解(9 3 2)

i=5時,9和3進行了一次交換,變爲(3 9 2)(位置爲1 5 9),以後在i=9(此時r[0]=r[i]=2),作出的交換如上圖所示(圖略差...),分爲三個步驟:(gap=4)

  • 第一次進入循環j=i-gap=5,r[j]=r[5]=9>r[0],執行23行得r[j+gap]=r[9]=r[j]=9,此時該列數值變爲(3 9 9)
  • 繼續第二次循環j=j-gap=1,r[j]=r[1]=3>r[0],一樣執行循環r[j+gap]=r[5]=r[j]=3,此時變爲(3 3 9)
  • 跳出循環(注意:跳出循環時多執行了一次j-=gap=-3​),執行r[j+gap]=r[-3+4]=r[1]=r[0]=2​,最終變爲(2 3 9)

步長(增量)選擇

步長的選取很是關鍵,可是步長的選擇沒有統一規定,也沒絕對的規律。只要知足最後一個步長爲1便可。Donald Shell最初建議步長選擇爲\frac {n} {2},雖然這樣去能夠比O(n^2)類的算法更好,但仍然有減小平均時間和最差時間的餘地。維基百科給出的部分步長與最壞狀況下複雜度有:

已知的最好步長序列是由Sedgewick提出的(1, 5, 19, 41, 109,...),該序列的項來自9\times4^i-9\times2^i+12^{i+2}\times(2^{i+2}-3)+1這兩個算式。這項研究也代表「比較在希爾排序中是最主要的操做,而不是交換。」用這樣步長序列的希爾排序比插入排序要快,甚至在小數組中比快速排序和堆排序(後續博客整理),可是在涉及大量數據時希爾排序仍是比快速排序慢。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息