排序4:插入排序(希尔排序)

来源:互联网 发布:amd是什么软件 编辑:程序博客网 时间:2024/06/06 14:26
       从前面的介绍可以看到,一般情况下,对于同一序列的排序,尽管在实际的操作次数上我们进行了不断的改进,但是,仍然无法改变其时间复杂度为O(n2)的事实,因此,当序列元素个数n变得越来越大时,之前所介绍的排序算法在性能上的改进也将显得无力。在插入排序中,有种排序或许能在n比较大的时候,在性能上有较好的表现,这就是接下来要介绍的希尔排序。
       我们知道,直接插入排序的最好情况是序列完全顺序,初始序列越接近完全顺序,排序过程中所要进行的操作次数也就越少。而且显然,序列元素个数越少,性能也就相对越好。希尔排序正是基于这两个事实而来的,其核心思想是:把完整的序列按一定的增量分割成若干部分,每部分的元素个数相对较少。然后各个部分进行直接插入排序,从而得到了部分有序的结果。各部分重新合为完整的序列,这个完整序列比初始的序列显得更有序了。然后再调整增量,重复上述步骤。这样一来,先前得到的部分有序的结果将被后续的步骤所利用、整合,从而后续步骤中进行直接插入排序的各个部分将能大大减少操作次数。到这里,读者可能觉得挺抽象,下面来个例子让大家更好理解理解。
       设序列:49、38、65、97、76、13、27、49、55、4。让我们以增量5来分割序列,将会得到如下的若干部分:49、13;38、27;65、49;97、55;76、4。对这几个部分分别进行直接插入排序,将得到:13、49;27、38;49、65;55、97;4、76,由此,整个序列现在变为:13、27、49、55、4、49、38、65、97、76。可以发现,通过上面的步骤,整个序列现在比原来变得更有序了,因为每个被分割出来的部分都已有序。接着,我们再以3为增量,分割现在的整个序列,得到:13、55、38、76;27、4、65;49、49、97。对这几个部分进行直接插入排序,得:13、38、55、76;4、27、65;49、49、97,完整序列变为:13、4、49、38、27、49、55、65、97、76。可知,又比上一回的排序结果更有序,原因就在于缩小了增量,使得更频密的被分割部分都已有序。最后,以1为增量分割现在的整个序列,再重复上述。而显然,增量为1分割序列的结果就是整个序列本身,最后的步骤实际上就是对当前的完整序列再总体进行一回直接插入排序。最终得:4、13、27、38、49、49、55、65、76、97。
       相信通过上面的例子,读者能体会到希尔排序的思想和好处。此处,读者会提出个问题:增量应该怎样设定?对于这个问题,连计算机科学家目前也暂时无法明确回答。有人提出,增量序列为delta,delta[i]=pow(2,times-i+1)
-1,times 为所需排序的趟数,且应有:1itimeslog2(n+1)⌋,这里n为序列元素个数。无论如何,增量序列里头所有增量的公因子都应该只有1,至于原因,可见上例,若开头选取增量4,则有:49、76、55;38、13、4;65、27;97、49。此趟排序后有:49、4、27、49、55、13、65、97、76、38。然后取增量2,则有:49、27、55、65、76;4、49、13、97、38。大家可发现,两趟排序中,49、55、76必定会出现在同一分割部分内,还有其它好几个元素也有这样的现象。这意味着,第2趟排序中,这几个元素也要进行相应的操作,而在第1趟排序后,它们就已经相互间有序了,这样的重复操作显然会带来性能上的损失。最后,增量1必定要作为最后一趟排序的增量。
       从上文可看出,希尔排序是不稳定排序。代码如下:
#include <cmath>/*  开头有可能我们不会写出ShellSortKernel,直接写ShellSort。这样会造成delta需要用数组存起来,  而delta数组的元素个数与序列元素个数有关,使得空间复杂度上升。  通过调用ShellSortKernel,使得整个排序过程中不再同时产生与序列元素个数有关的辅助空间。 */void ShellSortKernel(int list[],int length,int delta){for(int from=0;from<delta;++from){for(int i=from+delta;i<length;i+=delta){int temp=list[i];int j;for(j=i-delta;(j>=from)&&(temp<list[j]);j-=delta)list[j+delta]=list[j];list[j+delta]=temp;}}}void ShellSort(int list[],int length){int times=floor(log(length+1)/log(2));for(int i=1;i<=times;++i){int delta=pow(2,times-i+1)-1;ShellSortKernel(list,length,delta);}}
       设序列元素个数为n。上文提到过,希尔排序的增量选取暂时没有能通过数学证明的标准,就算用上述有人所提出的公式,但这公式取决于times,而times本身也并非固定。即便我们进一步固定times,目前也只能知道,最好情况仍为序列完全顺序,而最坏情况依然难以确定。由此,时间复杂度的分析也就无法予以严格的数学推导。大量的实验表明,上文提到的确定增量的方法能使时间复杂度大致为O(n1.5)。由于算法没有使用随序列元素个数变化而改变数量的辅助存储空间,则空间复杂度为O(1)。
0 0
原创粉丝点击