定义

希尔排序(Shell Sort)也称缩小增量排序,是插入排序的改进版本:按固定间隔将数组分成若干子序列分别做插入排序,再逐步缩小间隔,最终间隔为 1 时完成整体排序。

插入排序的弱点在于每次只能把元素移动 一位。元素离目标位置很远时,要经历大量相邻交换,最坏 。但它对 近乎有序 的数据很快(最好 )。

希尔排序的优化思路是:先用 较大间隔 做插入排序,让元素能一次跨越多个位置,快速接近目标区域;间隔逐步缩小后,数组已「大致有序」,最后的间隔为 1 的插入排序只需少量调整。换言之,用粗粒度的预排序,降低细粒度插入排序的工作量。

特性:平均 ~ · 最好 · 最坏 · 空间 · 原地 · 不稳定

过程

间隔 即同组元素在原数组中下标之差。分组时从下标 各起一条链:下标 归为同一子序列,组内 做插入排序,各组互不影响。完成一趟后缩小 ,并在 写回后的新数组 上重新分组; 时即对整个数组做普通插入排序。

下面采用常见希尔增量(初始 ,每趟减半至 ),以 [8, 3, 1, 2, 7, 5, 6, 4] 为例。 时,下标 同组得 87,下标 同组得 35,共 组。各趟分组与组内排序如下(同组元素按下标先后列出):

间隔组数各组元素排序结果
44[8, 7][3, 5][1, 6][2, 4][7, 8][3, 5][1, 6][2, 4]
22[7, 1, 8, 6][3, 2, 5, 4][1, 6, 7, 8][2, 3, 4, 5]
11[1, 2, 6, 3, 7, 4, 8, 5][1, 2, 3, 4, 5, 6, 7, 8]

性质

稳定性

希尔排序是 不稳定 排序算法。间隔大于 1 时,组内插入排序的交换跨度为 ,元素会与不 相邻 的位置互换——一次换位可能 跨过 中间的相等键,从而打乱它们的相对顺序。

[8, 5a, 5b, 1] 为例(5a5b 值均为 5),间隔序列为 2、1。间隔 2 时分两组:下标 0,2 为 [8, 5b],下标 1,3 为 [5a, 1],组内各自做插入排序:

步骤说明结果
组 0,25b < 8,交换[5b, 5a, 8, 1]
组 1,31 < 5a,交换[5b, 1, 8, 5a]

组 0,2 交换时,5b 从下标 2 跳到 0,跨过了 中间的 5a。输入时 5a5b 前面,此趟结束后 5b 已位于 5a 之前,相对顺序被打乱;间隔 1 排完得 [1, 5b, 5a, 8],也无法恢复。

时间复杂度

时间复杂度与 增量序列 的选取有关。

情况复杂度说明
最好已有序时内层可提前 break
最坏希尔增量()可退化至此
平均 ~ 取决于增量序列;实践中优于简单二次排序

下文代码采用 Knuth 增量(,即 ),可进一步降低复杂度上界。

空间复杂度

。原地排序,仅使用常数个辅助变量。

代码实现

采用 Knuth 增量序列(初始 gap 取不超过 len/3 的最大 ),保存 key 后移元素,减少不必要的交换:

void shell_sort(int* arr, int len) {
    int gap = 1;
    while (gap < len / 3) gap = gap * 3 + 1;
 
    while (gap > 0) {
        for (int i = gap; i < len; i++) {
            int key = arr[i];
            int j = i - gap;
            while (j >= 0 && arr[j] > key) {
                arr[j + gap] = arr[j];
                j -= gap;
            }
            arr[j + gap] = key;
        }
        gap /= 3;
    }
}

参考阅读