希尔排序本质是分组插入排序,通过步长序列实现跨步比较,先大间隔分组排序再逐步缩小至gap=1;Knuth序列(1,4,13,40…)更优,需预计算最大合法gap再递除3。

希尔排序的本质是分组插入排序
希尔排序不是独立的新算法,而是把插入排序的“逐个比较”改成“跨步比较”。关键在步长序列(gap sequence):先按大间隔分组排序,再逐步缩小间隔,最后 gap=1 时就是普通插入排序。这个过程让数组提前接近有序,大幅减少后续移动次数。
常见误区是直接套用 gap = n/2, gap /= 2,但这种序列最坏时间复杂度仍是 O(n²)。更稳妥的选择是 Knuth 序列:gap = gap * 3 + 1 反向生成(如 1, 4, 13, 40…),它在多数情况下表现稳定。
标准实现中 gap 初始化和收缩方式要匹配
如果用 Knuth 序列,必须先“探到最大合法 gap”,再逐次除以 3。不能先设 gap = n 再 gap /= 3——可能跳过关键步长,导致排序错误。
- 正确做法:用循环预计算最大 gap,例如
while (gap - 收缩时用
gap /= 3,确保每轮都覆盖 Knuth 序列的逆序 - 内层插入排序逻辑和普通插入排序一致,只是比较和移动的索引步长为
gap,例如j = i; while (j >= gap && arr[j]
为什么比普通插入排序快?看数据移动量
对随机数组,插入排序平均移动约 n²/4 次元素;希尔排序用 Knuth 序列后,平均移动次数降到约 n^(3/2) 级别。直观理解:第一轮 gap=40 时,一个离位元素可能一步就从位置 100 跳到 60;而插入排序只能一步步挪过去。
立即学习“C++免费学习笔记(深入)”;
但注意:希尔排序是不稳定排序。比如两个相等元素 A 和 B,A 在前、B 在后,若某轮 gap 排序中 B 被提前交换到 A 前面,顺序就永久破坏了——std::sort 不采用它,正是因为稳定性要求。
C++ 实现需注意边界和类型安全
数组下标运算容易越界,尤其 j - gap 必须 ≥ 0。推荐用 size_t 存长度,但 gap 计算中涉及除法和减法,混用有符号/无符号易触发隐式转换警告。稳妥写法是统一用 int 处理索引,或强转:
void shellSort(std::vector& arr) { int n = static_cast (arr.size()); if (n <= 1) return; int gap = 1; while (gap <= n / 3) gap = gap * 3 + 1; for (; gap > 0; gap /= 3) { for (int i = gap; i < n; ++i) { int temp = arr[i]; int j = i; while (j >= gap && arr[j - gap] > temp) { arr[j] = arr[j - gap]; j -= gap; } arr[j] = temp; } } }
真正难调的不是逻辑,而是 gap 序列选错或收缩条件写成 gap > 1(漏掉最后一轮 gap=1),结果排不全。动手前先手写三五个数跑一遍 gap 变化过程,比直接编译省时间。










