排序(四)之希尔排序

希尔排序

希尔排序算法代码如下。

/* 对顺序表L作希尔排序 */
void ShellSort(SqList *L)
{
    int i, j;
    int increment = L->length;
    do
    {
        /* 增量序列 */
        increment = increment / 3 + 1;                
        for (i = increment + 1; i <= L->length; i++)
        {
            if (L->r[i] < L->r[i - increment])
            {                                         
                /* 需将L->r[i]插入有序增量子表 */
                /* 暂存在L->r[0] */
                L->r[0] = L->r[i];                    
                for (j = i - increment; j > 0 && 
                     L->r[0] < L->r[j]; j -= increment)
                    /* 记录后移,查找插入位置 */
                    L->r[j + increment] = L->r[j];    
                /* 插入 */
                L->r[j + increment] = L->r[0];        
            }
        }
    }
    while (increment > 1);
}

1.程序开始运行,此时我们传入的SqList参数的值为length=9,r[10]={0,9,1,5,8,3,7,4,6,2}。这就是我们需要等待排序的序列,如图9-6-4所示。

 

图9-6-4

2.第4行,变量increment就是那个“增量”,我们初始值让它等于待排序的记录数。

3.第5~19行是一个do循环,它提终止条件是increment不大于1时,其实也就是增量为1时就停止循环了。

4.第7行,这一句很关键,但也是难以理解的地方,我们后面还要谈到它,先放一放。这里执行完成后,increment=9/3+1=4。

5.第8~17行是一个for循环,i从4+1=5开始到9结束。

6.第10行,判断L.r[i]与L.r[i-incre-ment]大小,L.r[5]=3小于L.r[i-incre-ment]=L.r[1]=9,满足条件,第12行,将L.r[5]=3暂存入L.r[0]。第13~14行的循环只是为了将L.r[1]=9的值赋给L.r[5],由于循环的增量是j-=increment,其实它就循环了一次,此时j=-3。第15行,再将L.r[0]=3赋值给L.r[j+incre-ment]=L.r[-3+4]=L.r[1]=3。如图9-6-5所示,事实上,这一段代码就干了一件事,就是将第5位的3和第1位的9交换了位置。

 

图9-6-5

7.循环继续,i=6,L.r[6]=7>L.r[i-incre-ment]=L.r[2]=1,因此不交换两者数据。如图9-6-6所示。

 

图9-6-6

8.循环继续,i=7,L.r[7]=4<L.r[i-incre-ment]=L.r[3]=5,交换两者数据。如图9-6-7所示。

 

图9-6-7

9.循环继续,i=8,L.r[8]=6<L.r[i-incre-ment]=L.r[4]=8,交换两者数据。如图9-6-8所示。

 

图9-6-8

10.循环继续,i=9,L.r[9]=2<L.r[i-incre-ment]=L.r[5]=9,交换两者数据。注意,第13~14行是循环,此时还要继续比较L.r[5]与L.r[1]的大小,因为2<3,所以还要交换L.r[5]与L.r[1]的数据,如图9-6-9所示。

图9-6-9

最终第一轮循环后,数组的排序结果为图9-6-10所示。细心的同学会发现,我们的数字1、2等小数字已经在前两位,而8、9等大数字已经在后两位,也就是说,通过这样的排序,我们已经让整个序列基本有序了。这其实就是希尔排序的精华所在,它将关键字较小的记录,不是一步一步地往前挪动,而是跳跃式地往前移,从而使得每次完成一轮循环后,整个序列就朝着有序坚实地迈进一步。

图9-6-10

11.我们继续,在完成一轮do循环后,此时由于increment=4>1因此我们需要继续do循环。第7行得到increment=4/3+1=2。第8~17行for循环,i从2+1=3开始到9结束。当i=3、4时,不用交换,当i=5时,需要交换数据,如图9-6-11所示。

图9-6-11

12.此后,i=6、7、8、9均不用交换,如图9-6-12所示。

图9-6-12

13.再次完成一轮do循环,increment=2>1,再次do循环,第7行得到incre-ment=2/3+1=1,此时这就是最后一轮do循环了。尽管第8~17行for循环,i从 1+1=2开始到9结束,但由于当前序列已经基本有序,可交换数据的情况大为减少,效率其实很高。如图9-6-13所示,图中箭头连线为需要交换的关键字。

 

 

图9-6-13

最终完成排序过程,如图9-6-14所示。

 

9.6.3 希尔排序复杂度分析

通过这段代码的剖析,相信大家有些明白,希尔排序的关键并不是随便分组后各自排序,而是将相隔某个“增量”的记录组成一个子序列,实现跳跃式的移动,使得排序的效率提高。

这里“增量”的选取就非常关键了。我们在代码中第7行,是用increment=increment/3+1;的方式选取增量的,可究竟应该选取什么样的增量才是最好,目前还是一个数学难题,迄今为止还没有人找到一种最好的增量序列。不过大量的研究表明,当增量序列为dlta[k]=2t-k+1-1(0≤k≤t≤)时,可以获得不错的效率,其时间复杂度为O(n3/2),要好于直接排序的O(n2)。需要注意的是,增量序列的最后一个增量值必须等于1才行。另外由于记录是跳跃式的移动,希尔排序并不是一种稳定的排序算法。

不管怎么说,希尔排序算法的发明,使得我们终于突破了慢速排序的时代(超越了时间复杂度为O(n2)),之后,相应的更为高效的排序算法也就相继出现了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值