希爾排序:
直接插入排序在在本身數量比較少的時候情況下效率很高,如果待排數的數量很多,其效率不是很理想。
回想一下直接插入排序過程,排序過程中,我們可以設置一條線,左邊是排好序的,右邊則是一個一個等待排序,
如果最小的那個值在最右邊,那么排這個最小值的時候,需要將所有元素向右邊移動一位。
是否能夠減少這樣的移位呢?
我們不希望它是一步一步的移動,而是大步大步的移動。希爾排序就被發明出來了,它也是當時打破效率
O(n2)的算法之一。希爾排序算法通過設置一個間隔,對同樣間隔的數的集合進行插入排序,此數集合中的元素
移位的長度是以間隔的長度為准,這樣就實現了大步位移。但是最后需要對元素集合進行一次直接插入排序,所以
最后的間隔一定是1。
下面舉一個例子:
第一趟希爾排序,間隔為4
第二趟排序:間隔是2
第三趟 間隔為1,即 直接插入排序法:
。。。
。。
。
有人問,這個間隔怎么確定,這是個數學難題,至今沒有解答。但是通過大量的實驗,還是有個經驗值。
減小間隔
上面已經演示了以4為初始間隔對包含10個數據項的數組進行排序的情況。對於更大的數組開始的間隔也應該更大。然后間隔不斷減小,直到間隔變成1。
舉例來說,含有1000個數據項的數組可能先以364為增量,然后以121為增量,以40為增量,以13為增量,以4為增量,最后以 1為增量進行希爾排序。用來形成間隔的數列被稱為間隔序列。這里所表示的間隔序列由Knuth提出,此序列是很常用的。數列以逆向形式從1開始,通過遞歸表達式
h=3*b+1
來產生,初始值為1。
在排序算法中,首先在一個短小的循環中使用序列的生成公式來計算出最初的間隔。h值最初被賦為1,然后應用公式h=3*h+1生成序列1,4,13,40,121,364,等等。當間隔大於數組大小的時候,這個過程停止。對於一個含有1000個數據項的數組,序列的第七個數字,1093就太大了。因此,使用序列的第六個數字作為最大的數字來開始這個排序過程,作364-增量排序。然后,每完成一次排序全程的外部循環,用前面提供的此公式倒推式來減小間隔:
h=(h-1)/3
這個倒推的公式生成逆置的序列364,121,40,13,4,1。從364開始,以每一個數字作為增量進行排序。當數組用1-增量排序后,算法結束。
希爾排序比插入排序快很多,它是基於什么原因呢?當h值大的時候,數據項每一趟排序需要移動元素的個數很少,但數據項移動的距離很長。這是非常有效率的。當h減小時,每一趟排序需要移動的元素的個數增多,但是此時數據項已經接近於它們排序后最終的位置,這對於插入排序可以更有效率。正是這兩種情況的結合才使希爾排序效率那么高。
注意后期的排序過程不撤銷前期排序所做的工作。例如,已經完成了以40-增量的排序的數組,在經過以13-增量的排序后仍然保持了以40-增量的排序的結果。如果不是這樣的話,希爾排序就無法實現排序的目的。
其他間隔序列
選擇間隔序列可以稱得上是一種魔法。至此只討論了用公式h=h*3+1生成間隔序列,但是應用其他間隔序列也取得了不同程序的成功,只是一個絕對的條件,就是逐漸減小的間隔最后一定要等於1,因此最后一趟排序是一次普通的插入排序。
在希爾的原稿中,他建議初始的間距為N/2,簡單地把每一趟排序分成了兩半。因此,對於N=100的數組逐漸減小的間隔序列為50,25,12,6,3,1。這個方法的好處是不需要在不開始排序前為找到初始的間隔而計算序列;而只需要用2整除N。但是,這被證明並不是最好的數列。盡管對於大多數的數據來說這個方法還是比插入排序效果好,但是這種方法有時會使運行時間降到O(N2),這並不比插入排序的效率更高。
這個方法的一個變形是用2.2而非2來整除每一個間隔。對於N=100的數組來說,會產生序列45,20,9,4,1。這比用2整除顯著改善了效果,因為這樣避免了某些導致時間復雜度為O(N2)的最壞情況的發生。不論N為何值,都需要一些額外的代碼來保證序列的最后取值為1。這產生了和清單中所列的Knuth序列差不多的結果。
遞減數列的另一個可能是
if(h<5)
h=1;
else
h=(5*h-1)/11;
間隔序列中的數字互質通常被認為很重要:也就是說,除了1之外它們沒有公約數。這個約束條件使每一趟排序更有可能保持前一趟排序已排好的效果。希爾最初以N/2為間隔的低效性就是歸咎於它沒有遵守這個准則。
或許還可以設計出像如上講述的間隔序列一樣好的間隔序列。但是不管這個間隔序列是什么,都應該能夠快速地計算,而不會降低算法的執行速度。
這就是希爾算法的思想:
先將整個待排記錄序列分割成為若干子序列分別進行直接插入排序,待整個序列中的記錄“基本有序”時,在對全體進行一次直接插入排序。
代碼:
因為希爾排序就是有增量的直接插入排序,所以將原先直接插入代碼修改一下,把步進長度改為增量即可。
1 void shellSort(myDataType *ary,int len) 2 { 3 int i,j; 4 int increment = len;//增量 5 myDataType key; 6 while(increment > 1)//最后在增量為1並且是執行了情況下停止。 7 { 8 increment = increment/3 + 1;//根據公式 9 //printf("increment:%d\n",increment); 10 for (i=increment;i<len;i++)//從[0]開始,對相距增量步長的元素集合進行修改。 11 { 12 key = ary[i]; 13 //以下和直接插入排序類似。 14 j=i-increment; 15 while(j >= 0) 16 { 17 if (key < ary[j] ) 18 { 19 myDataType temp = ary[j]; 20 ary[j] = key; 21 ary[j+increment] = temp; 22 } 23 j=j-increment; 24 } 25 } 26 } 27 }
完整代碼:
1 #include "stdafx.h" 2 3 4 typedef int myDataType; 5 myDataType src_ary[10] = {9,1,5,8,3,7,6,0,2,4}; 6 7 void prt_ary(myDataType *ary,int len) 8 { 9 int i=0; 10 while(i < len) 11 { 12 printf(" %d ",ary[i++]); 13 } 14 printf("\n"); 15 } 16 void shellSort(myDataType *ary,int len) 17 { 18 int i,j; 19 int increment = len;//增量 20 myDataType key; 21 while(increment > 1)//最后在增量為1並且是執行了情況下停止。 22 { 23 increment = increment/3 + 1;//根據公式 24 //printf("increment:%d\n",increment); 25 for (i=increment;i<len;i++)//從[0]開始,對相距增量步長的元素集合進行修改。 26 { 27 key = ary[i]; 28 //以下和直接插入排序類似。 29 j=i-increment; 30 while(j >= 0) 31 { 32 if (key < ary[j] ) 33 { 34 myDataType temp = ary[j]; 35 ary[j] = key; 36 ary[j+increment] = temp; 37 } 38 j=j-increment; 39 } 40 } 41 } 42 } 43 44 45 int _tmain(int argc, _TCHAR* argv[]) 46 { 47 printf("before sort:\n"); 48 prt_ary(src_ary,10); 49 50 //bubble_sort(src_ary,10); 51 //bubble_sort_modify1(src_ary,10); 52 //bubble_sort_opt(src_ary,10); 53 //selectionSort(src_ary,10); 54 //insertionSort(src_ary,10); 55 shellSort(src_ary,10); 56 57 printf("after sort:\n"); 58 prt_ary(src_ary,10); 59 60 61 62 getchar(); 63 return 0; 64 }
結果: