希尔排序

       对于大规模乱序数据插入排序很慢,因为它只会交换相邻的元素,因此元素只能一点一点地从数组的一端移动到另一端。希尔排序简单改进了插入排序,交换不相邻的元素以对数组的局部进行排序,并最终用插入排序将局部有序的数组排序。希尔排序把记录按下标的一定增量分组,对每组使用直接插入排序算法排序,随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个序列恰好被分成一组。因此希尔排序也叫缩小增量排序(Diminishing Increment Sort)。

算法思想

       在插入排序中如果数组元素大部分乱序且元素数量很大,内循环一次我们就需要比较和移动大量的数据。希尔排序的思想是使数组中任意间隔为div的元素都是有序的,即整个数组是由div个互相独立的有序子数组编织在一起组成的。因此当增量div很大时,我们就能将元素移动到更远的地方,从子序列的角度来说,当div很大时我们的子序列元素个数就相对小,对这些小的子序列使用插入排序的时间开销较小。一趟排序后,当增量缩小时我们得到的子序列相对前一次增量的子序列来说是局部有序的,而插入排序对有序序列排序有很好的时间复杂度。

       因此希尔排序的思想很简单:在一个循环内,选择一个增量将序列分为若干个小分组,对每个小分组进行简单插入排序。随着循环的进行增量的数值也不断减小直至为1。

       如前所述,如何选择增量是影响希尔排序性能的重要因素,哪一种增量的选择是最好的,其数学证明至今是个难题。以下源码以最简单的二分来作为增量计算的策略。

源码

void ShellSort(int a[], int n) {
    if (n <= 1)
        return ;
    for (int div = n >> 1; n >= 1; n >>= 1) //定增量
        for (int i = 0; i < div; ++i) //分组
            for (int j = i; j < n; j += div) //插入排序
                for (int k = j; k < n; k += div)
                    if (a[k] < a[j])
                        swap(a[k], a[j]);
}

       上面的源码看起来比较恐怖——用了4层循环。但实际上某些层的循环的工作量都不是很大,这点从循环步长就可以看出了。如果从每组序列的第二个元素开始向前交换,可以得到更简洁的代码:

void ShellSort(int a[], int n) {
    if (n <= 1)
        return ;
    for (int div = n >> 1; div >= 1; div >>= 1) //定增量
        for (int i = div; i < n; ++i) //分组
            for (int j = i; j >= div && a[j] < a[j-div]; j -= div) //插入排序
                swap(a[j], a[j-div]);
}

时间复杂度

       希尔排序的性能与增量选择策略密切相关, 目前还没有一个确切的数值,只知道希尔排序的运行时间达不到平方级别。例如若增量选择策略为div= div+1(初值为1,小于元素个数的最大div值即为排序过程中的第一个增量值),则希尔排序的比较次数与元素个数n的3/2次方成正比。

稳定性

       我们知道一次插入排序是稳定的,不会改变相同元素的相对顺序,但在希尔排序中要使用多次插入排序,且针对的都是交织在一起的子序列,因此不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值