希尔排序 最坏时间_究竟是怎么打破二次时间屏障的?浅谈希尔排序的思想和复杂度证明...

4c8f66ece701caa64abf639b1b9a935e.png

作为最早突破

equation?tex=O%28N%5E%7B2%7D%29 复杂度的排序算法之一,希尔排序(Shell Sort)一直是闪耀着编程艺术之美的存在。

本文尽可能通俗的解释了希尔排序为什么快,以及打破二次时间屏障的关键一击是什么。

复杂度证明放最后了,有点烧脑,但不影响上文阅读。

希尔排序的思想:

希尔排序是插入排序的改进版,通过比较一定间隔的元素进行插入排序,并且不断缩小间隔,直到比较相邻元素。我们管这个间隔叫增量,增量为h的排序我们成为h-排序。

441c56744d7c5fc29eb5a149bad6490e.png
图1:待排序数据

如果我们使用插入排序的话,最后一步将元素1从最后一个位置移动到第一个位置,需要把其余元素依次往后挪动一位,这种开销十分巨大。

而使用希尔排序时,我们通过较大的间隔(增量),将元素1快速送到第一个位置。

第一趟排序:

4126ac22c0e9ae699959ca27fc051f75.png
图2:第一趟,4-排序结果

根据图2,增量为4时,我们只对间隔为4的元素进行插入排序。将[8,4]进行插入排序,得到[4,8]。将[7,3]进行插入排序,得到[3,7]。将[6,2]进行插入排序,得到[2,6]。将[5,1]进行插入排序,得到[1,5]。

第一趟排序后,通过每隔四个元素进行插入排序,花费较少的比较次数,算法将小元素快速的送到前面,同理大元素被挤到后面。

第二趟排序:

e24da4abaa36517589248717e7c8d752.png
图3:第二趟,2-排序结果

根据图3,增量为2时,我们对间隔为2的元素进行插入排序。将[4,2,8,6]进行插入排序,得到[2,4,6,8]。将[3,1,7,5]进行插入排序,得到[1,3,5,7]。

第二趟排序后,通过每隔二个元素进行插入排序,小元素继续被快速的送到前面,同理大元素被挤到后面。

经过两趟大增量的排序,数据在宏观上是比较有序的。此时只需要最后执行一次增量为1的排序,也就是普通的插入排序,将现在的数据进行微调,就能得到有序的结果。

对[2, 1, 4, 3, 6, 5, 8, 7]这个半成品进行插入排序,显然比对[8, 7, 6, 5, 4, 3, 2, 1]进行插入排序容易得多。

      /*希尔排序c代码,来自《数据结构与算法分析》*/
      void ShellSort(ElementType A[], int N) {
          int i, j, increment;
          ElementType tmp;

          // 每一趟排序增量折半,当然也可以使用其他增量
/* 1*/    for (increment = N / 2; increment > 0; increment /= 2) {
/* 2*/        for (i = increment; i < N; ++i) {
/* 3*/            tmp = A[i];
                  // 对A[i],A[i-increment],A[i-2*increment]...进行插入排序
/* 4*/            for (j = i; j > increment; j -= increment) {
/* 5*/                if (tmp < A[j - increment]) {
/* 6*/                    A[j] = A[j - increment];
/* 7*/                }
/* 8*/                else {
/* 9*/                    break;
/*10*/                }
/*11*/           }
/*12*/           A[j] = tmp;
/*13*/        }
/*14*/    }
      }

通俗的讲,希尔排序能够以较大的步伐将小元素往前送,这样大大的减少了需要比较的次数,从而提高了速度。

使用1, 2, 4, ...,

equation?tex=%5Clfloor+%5Cfrac%7BN%7D%7B2%7D+%5Crfloor 这种不断减半的增量大多数情况下能够提速,但是遇到最坏的情况仍然是
equation?tex=%5CTheta%28N%5E%7B2%7D%29

比如图4:

61af1236348fdc8a168f15992eca2f47.png
图4:4-排序和2-排序没有改变任何元素位置,只能通过1-排序完成排序任务。此时算法退化成插入排序。

因此Hibbard提出了著名的Hibbard增量:1, 3, 7, ...,

equation?tex=2%5E%7Bk%7D-1 。使用这个增量的希尔排序最坏运行时间是
equation?tex=O%28N%5E%7B3%2F2%7D%29

通俗来说,能打破二次时间界的核心原因是:

在执行

equation?tex=h_%7Bk+%7D
排序之前,我们已经执行了
equation?tex=h_%7Bk%2B1%7D
equation?tex=h_%7Bk%2B2%7D
排序。而这两个排序使得序列在宏观上更有序,并且严格地保证了对于某个位置的元素E,在这个元素左侧,且到E一定距离以上的元素一定小于E。

所以4-11行代码循环次数并没有想象中的那么多。

证明:使用Hibbard增量的希尔排序最坏运行时间是

equation?tex=O%28N%5E%7B3%2F2%7D%29

假设我们已经进行到

equation?tex=h_%7Bk%7D -排序,在此之前序列一定满足
equation?tex=h_%7Bk%2B1%7D -排序和
equation?tex=h_%7Bk%2B2%7D -排序。

所以对于位置 P和位置 P-d上的两个元素,如果d是

equation?tex=h_%7Bk%2B1%7D 或者
equation?tex=h_%7Bk%2B2%7D 的整数倍,显然A[P-d]≤A[P]。

同样的,当d是

equation?tex=h_%7Bk%2B1%7D
equation?tex=h_%7Bk%2B2%7D 的线性组合时,一样可以证明A[P-d]≤A[P]。

比如d=1*3+2*7,那么根据7-排序结果,A[17]≥A[10]≥A[3],根据3-排序结果,A[3]≥A[0],所以A[17]≥A[0]。

因为

equation?tex=h_%7Bk%2B2%7D%3D2%2Ah_%7Bk%2B1%7D%2B1 ,所以
equation?tex=h_%7Bk%2B1%7D
equation?tex=h_%7Bk%2B2%7D 没有公因数,因此大于等于
equation?tex=%28h_%7Bk%2B1%7D-1%29%28h_%7Bk%2B2%7D-1%29 的所有整数都能表示成
equation?tex=h_%7Bk%2B1%7D+
equation?tex=h_%7Bk%2B2%7D 的线性组合(参数非负),证明略。

所以4-11行for循环代码只需要检查和当前tmp左侧且距离不超过

equation?tex=%28h_%7Bk%2B1%7D-1%29%28h_%7Bk%2B2%7D-1%29%3D8h_%7Bk%7D%5E%7B2%7D%2B4h_%7Bk%7D 的元素即可,其余的能表示成线性组合,
所以一定不大于tmp

又因为增量是

equation?tex=h_%7Bk%2B1%7D ,所以4-11行的for循环实际运行的次数不超过
equation?tex=%5Cfrac%7B8h_%7Bk%7D%5E%7B2%7D%2B4h_%7Bk%7D%7D%7Bh_%7Bk%7D%7D%3D8h_%7Bk%7D%2B4%3DO%28h_k%29

因为代码第二行的for循环执行

equation?tex=N-h_%7Bk%7D 次,所以
equation?tex=h_%7Bk%7D 排序运行的上界是
equation?tex=O%28%28N-h_k%29%2Ah_%7Bk%7D%29%3DO%28Nh_%7Bk%7D%29

但是当

equation?tex=h_%7Bk%7D 较大时,
equation?tex=%28h_%7Bk%2B1%7D-1%29%28h_%7Bk%2B2%7D-1%29%3D8h_%7Bk%7D%5E%7B2%7D%2B4h_%7Bk%7D 会过大,导致上述分析失效。因此我们不能使用该上界,而是使用另一个上界,也就是将
equation?tex=h_%7Bk%7D -排序看做是
equation?tex=h_%7Bk%7D 个 大小为
equation?tex=%5Cfrac%7BN%7D%7Bh_%7Bk%7D%7D的序列的插入排序,得到一个更大一点的上界:
equation?tex=O%28h_%7Bk%7D%2A%28%5Cfrac%7BN%7D%7Bh_%7Bk%7D%7D%29%5E%7B2%7D%29%3DO%28%5Cfrac%7BN%5E%7B2%7D%7D%7Bh_%7Bk%7D%7D%29

所以我们约定,当

equation?tex=h_%7Bk%7D%3C%5Csqrt%7BN%7D 时,我们用较小一点的上界
equation?tex=O%28Nh_%7Bk%7D%29 ,当
equation?tex=h_%7Bk%7D%3E%5Csqrt%7BN%7D 时,我们用较大一点的上界
equation?tex=O%28%5Cfrac%7BN%5E%7B2%7D%7D%7Bh_%7Bk%7D%7D%29

所以假设增量个数为t。总复杂度为:

equation?tex=O%28%5Csum_%7Bk%3D1%7D%5E%7Bt%2F2%7D%7BNh_%7Bk%7D%7D%2B%5Csum_%7Bk%3Dt%2F2%2B1%7D%5E%7Bt%7D%7BN%5E%7B2%7D%2Fh_%7Bk%7D%7D%29

equation?tex=%3DO%28N%5Csum_%7Bk%3D1%7D%5E%7Bt%2F2%7D%7Bh_%7Bk%7D%7D%2BN%5E%7B2%7D%5Csum_%7Bk%3Dt%2F2%2B1%7D%5E%7Bt%7D%7B1%2Fh_%7Bk%7D%7D%29

equation?tex=%3DO%28N%2A%7Bh_%7Bt%2F2%7D%7D%2BN%5E%7B2%7D%2A1%2Fh_%7Bt%2F2%7D%29 (两个求和都是几何级数,所以结果都是最大的一项)

equation?tex=%3DO%28N%2A%5Csqrt%7BN%7D%2B%5Cfrac%7BN%5E%7B2%7D%7D%7B%5Csqrt%7BN%7D%7D%29 (利用了
equation?tex=h_%7Bt%2F2%7D%3D%5CTheta%28%5Csqrt%7BN%7D%29 ,因为
equation?tex=h_%7Bt%7D%3DO%28N%29
equation?tex=h_%7Bt%2F2%7D%3D%5CTheta%28%5Csqrt%7Bh_%7Bt%7D%7D%29 )

equation?tex=%3DO%28N%5E%7B3%2F2%7D%29

有了这个上界,排序的速度就得到了极大的保障。举个例子,对10000个元素进行排序,如果普通插入排序最坏情形不超过100秒,那么Hibbard增量的希尔排序最坏情况不会超过1秒。

ps:最后证明过程来自Weiss的《数据结构与算法分析》,但是总复杂度证明那里把

equation?tex=%5Csum_%7Bk%3D1%7D%5E%7Bt%2F2%7D%7BNh_%7Bk%7D%7D 错写成了
equation?tex=%5Csum_%7Bk%2B1%7D%5E%7Bt%2F2%7D%7BNh_%7Bk%7D%7D ,不知是我买了盗版还是作者真的写错了。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值