1. 程式人生 > >【演算法】希爾排序C語言實現

【演算法】希爾排序C語言實現

上一篇文章我們一起學習了直接插入排序,它的原理就是把前i個長度的序列變成有序序列,然後迴圈迭代,直至整個序列都變為有序的.但是說來說去它還是一個時間複雜度為(n^2)的演算法,難道就不能再進一步把時間複雜度降低一階麼?可能有很多同學說快速排序,堆排序,我都會,這些簡單的插入排序我都不屑於用.確實,以上幾種演算法相對於之前的O(n^2)級別的演算法真的是弱爆了,效率可能還會差上千萬倍,但是我們不妨翻看一下歷史,你就會感覺每一種演算法的出現都是很可貴的.

1959年D.L.Shell正式提出了我們今天的主角shell演算法,這是相當酷的一件事情,為什麼這麼說呢?因為shell排序時第一個突破了O(n^2)時間複雜度的排序演算法,這應該是排序演算法歷史上比較閃耀的時刻了,因為在1959年之前的相當長的一段時間裡,各種各樣的排序演算法無論是怎麼花樣繁多,都始終無法突破O(n^2)雷池一步,在當時直接插入排序已經是相當優秀的了,而排序演算法不可能突破O(n^2)

的聲音成為了當時的主流.

看見了這段歷史之後你有什麼感受呢,我們在課堂上不願意學的演算法確仍然是科學家們多年苦苦思索才發明出來的,是不是覺得他們很不容易呢?其實你也沒必要內疚啦,即使你曾經對它不屑一顧過,沒有認真的學它,So what?現在開始學也不晚是不是?

希爾排序是基於插入排序的以下兩點性質而提出改進方法的:
  • 插入排序在對幾乎已經排好序的資料操作時,效率高,即可以達到線性排序的效率。
  • 但插入排序一般來說是低效的,因為插入排序每次只能將資料移動一位。
其實直接插入排序並不是那麼遜的,它在待排序資料基本有序並且數量較少的時候威力還是很大的,甚至比一些高階演算法還要高效.對於第二點,我只能說這就是我們shell演算法的牛逼的地方了,插入排序每次只能移動資料一位,而shell演算法成功的解決了這個問題.

shell演算法的核心還是分組,但是這個分組就有門道兒了,因為它會實現取一個小於總資料長度的整數值gap作為分組的步長,什麼意思呢?假如我們的待排序陣列為:

序號 1      2     3      4      5     6      7      8     9     10

49,38,65,97,76,13,27,49,55,04

設定gap的值為長度10的一半也就是5,那麼第一個和第六個元素就是一組,第二個和第七個元素就是一組,第三個和第八個元素就是一組,第四個和第九個元素就是一組,第五個和第十個元素就是一組,所以一共分為了gap = 5組,

 組    一    二   三    四       五

     序號1    6  2    7               3    8                4      9                 5    10

     資料 49  13        38  27       65  49       97    55            76   04

  交換後   13  49               27  38             49  65              55     97               04   76

然後如上面每一組之間進行再直接插入排序,比較如果前一個元素比較大,則交換兩個元素的位置,直至5組全部交換完畢.此時陣列的順序為

13   27   49   55   04    49   38   65   97   26.

然後gap的值再減半為2,重新分組,也就是第一個 第三個 第五個 第七個 第九個 元素為第一組是13  49  4  38  97, 第二個 第四個 第六個 第八個 第十個元素為一組是27  55  49  65  26.

組                   一

序號1      3      5      7      9            2      468  10

資料1349   04   3897      27   55      4965 26

交換後     04    13   38   49      97          26   27      49     55     65

然後如上面對它們兩個組分別進行直接插入排序,得到結果為

4   26   13   27   38    49   49   55   97   65,

之後gap的值再減半為1(要知道gap的值小於1的時候在分組就沒意義了,一位你的每一個組至少要有一個元素才能組成一個序列.)這次我們直接對上一次的結果進行一次真正的直接插入排序(為什麼說是真正的呢,因為此時步長已經為1)直至得出最終結果:

4   13   26   27   38    49   49   55   65   97.

下面是shell演算法的實現程式碼:

void shell_sort(int array[], int length){
	int i;
	int j;
	int k;
	int gap;	//gap是分組的步長
	int temp;	//希爾排序是在直接插入排序的基礎上實現的,所以仍然需要哨兵
	for(gap=length/2; gap>0; gap=gap/2){
		for(i=0; i<gap; i++){
			for(j=i+gap; j<length; j=j+gap){	//單獨一次的插入排序
				if(array[j] < array[j - gap]){
					temp = array[j];	//哨兵
					k = j - gap;
					while(k>=0 && array[k]>temp){
						array[k + gap] = array[k];
						k = k - gap;
					}
					array[k + gap] = temp;
				}
			}
		}
	}
}


這是完全按照希爾演算法的思想寫的,並沒有做任何更改.但還是有幾點要說一下

  • 希爾排序的時間複雜度為O(n*logn).
  • 我們怎麼確定步長才能使演算法達到最高效呢?其實這是一個很嚴謹的數學證明問題,可惜的是我們的科學家們目前為止並沒有尋找到一個唯一的答案,但是根據維基百科的介紹,還是有幾種比較高效的步長的,大家如果感興趣的話可以到這個連結看一下:希爾排序-維基百科.
  • 希爾排序是不穩定的,可以通過我們上面的兩個相同的49就可以看得出來.(其中一個49已經被下劃線標記了下來)

我在部落格上看到過有些大神為了追求程式碼的"簡潔之美",儘量使程式碼保持最短,(強調一下,如果是為了讓演算法執行的效率更高的話,我們當然要膜拜)但是這樣的話可能就不那麼好理解了,會給他人造成困擾,因為並不是每一個人都是對這個演算法相當熟稔,新手看了你的程式碼會很氣餒,對自信心也是一種傷害程式碼畢竟還是要給人看的,我們要不為難別人,這樣即節省別人的時間也會節省自己的時間,何樂而不為呢,畢竟我們中的絕大多數人還要在一個團隊裡混飯吃.一家之言,可能是我層次還比較低,錯了就錯了,努力學習總是沒錯的,謝謝大家.