本页面将简要介绍希尔排序。

定义

希尔排序(英语:Shell sort),也称为缩小增量排序法,是 插入排序 的一种改进版本。希尔排序以它的发明者希尔(英语:Donald Shell)命名。

过程

排序对不相邻的记录进行比较和移动:

  1. 将待排序序列分为若干子序列(每个子序列的元素在原始数组中间距相同);
  2. 对这些子序列进行插入排序;
  3. 减小每个子序列中元素之间的间距,重复上述过程直至间距减少为

性质

稳定性

希尔排序是一种不稳定的排序算法。

时间复杂度

希尔排序的最优时间复杂度为

希尔排序的平均时间复杂度和最坏时间复杂度与间距序列的选取有关。设间距序列为 ,下面给出 的两种经典选取方式,这两种选取方式均使得排序算法的复杂度降为 级别。

命题 1

若间距序列为 (从大到小),则希尔排序算法的时间复杂度为

命题 2

若间距序列为 (从大到小),则希尔排序算法的时间复杂度为

为证明这两个命题,我们先给出一个重要的定理并证明它,这个定理反应了希尔排序的最主要特征。

定理 1

只要程序执行了一次 ,不管之后怎样调用 函数, 数组怎样变换,下列性质均会被一直保持:

接下来我们证明定理 1。

我们先证明引理 1。

引理 1

对于整数 、正整数 与两个数组 ,满足如下要求:

则我们将两个数组分别升序排序后,上述要求依然成立。

再回到原命题的证明:

我们实际上只需要证明调用完 的紧接着下一次调用 后, 个子列仍有序即可,之后容易用归纳法得出。下面只考虑下一个调用:

执行完 后,如下组已经完成排序:

而之后执行 ,则会将如下组排序:

对于每个 ,考虑如下两个组:

第二个组前面也加上“”的原因是可能 从而前面也有元素。

则第二个组就是引理 中的 数组,第一个组就是 数组, 就是第二个组从 之后顶到末尾的长度, 是第二个组中前面那个“”的长度, 是第一个组去掉前 个后剩下的个数。

又因为有:

所以由引理 可得执行 将两个组分别排序后,这个关系依然满足,即依然有

若有 ,容易发现取正整数 再加上若干个 即可得到 ,则之前的情况已经蕴含了此情况的证明。

综合以上论述便有:执行完 依然有

因此定理 1 得证。

这个定理揭示了希尔排序在特定集合 下可以优化复杂度的关键,因为在整个过程中,它可以一致保持前面的成果不被摧毁(即 个子列分别有序),从而使后面的调用中,指针 的移动次数大大减少。

接下来我们单拎出来一个数论引理进行证明。这个定理在 OI 界因 小凯的疑惑 一题而大为出名。而在希尔排序复杂度的证明中,它也使得定理 得到了很大的扩展。

引理 2

均为正整数且互素,则不在集合 中的最大正整数为

而下面这个定理则揭示了引理 是如何扩展定理 的。

定理 2

如果 ,则程序先执行完 后,执行 的时间复杂度为 ,且对于每个 ,其 的移动次数是 级别的。

认真观察定理 的证明过程,可以发现:定理 1 可以进行「线性组合」,即 为间隔有序,以 为间隔亦有序,则以 的非负系数线性组合仍是有序的。而这种「线性性」即是由引理 保证的。

有了这两个定理,我们可以证明命题

空间复杂度

希尔排序的空间复杂度为

实现

[list2tab]

  • C++1

    template <typename T>
    void shell_sort(T array[], int length) {
      int h = 1;
      while (h < length / 3) {
        h = 3 * h + 1;
      }
      while (h >= 1) {
        for (int i = h; i < length; i++) {
          for (int j = i; j >= h && array[j] < array[j - h]; j -= h) {
            std::swap(array[j], array[j - h]);
          }
        }
        h = h / 3;
      }
    }
  • Python

    def shell_sort(array, length):
        h = 1
        while h < length / 3:
            h = int(3 * h + 1)
        while h >= 1:
            for i in range(h, length):
                j = i
                while j >= h and array[j] < array[j - h]:
                    array[j], array[j - h] = array[j - h], array[j]
                    j -= h
            h = int(h / 3)

参考资料与注释

Footnotes

  1. 希尔排序 - 维基百科,自由的百科全书