算法学习记录-排序——希尔排序
希尔排序:
直接插入排序在在本身数量比较少的时候情况下效率很高,如果待排数的数量很多,其效率不是很理想。
回想一下直接插入排序过程,排序过程中,我们可以设置一条线,左边是排好序的,右边则是一个一个等待排序,
如果最小的那个值在最右边,那么排这个最小值的时候,需要将所有元素向右边移动一位。
是否能够减少这样的移位呢?
我们不希望它是一步一步的移动,而是大步大步的移动。希尔排序就被发明出来了,它也是当时打破效率
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 }
结果: