希尔排序是对插入排序的改进,通过分组插入实现远距离交换;推荐使用Knuth序列(gap=3×gap+1)预生成并倒序遍历gap值;内层排序需确保j≥gap以避免越界和跨组比较。

希尔排序的核心是分组插入排序
希尔排序不是独立的新排序逻辑,而是对 insertion sort 的改进:它先按某个增量 gap 把数组分成若干子序列,对每个子序列做插入排序;然后逐步缩小 gap,直到 gap == 1 时做最后一次普通插入排序。关键在于“远距离交换”能快速把小元素从右往左“挪”得更近,减少后续比较次数。
常见误区是直接套用固定步长序列(比如每次除以 2),但 gap = gap / 2 在某些输入下会导致奇偶位置长期隔离,退化严重。推荐用 Knuth 序列:gap = gap * 3 + 1 生成,再倒序使用——它更均衡,实测在多数场景下比除 2 更稳定。
如何正确生成并遍历 gap 序列
不能边排序边动态算 gap(比如循环里写 gap /= 2),否则容易漏掉初始最大 gap 或提前终止。稳妥做法是先预生成所有 gap 值,存入 vector,再反向遍历:
std::vector<int> gaps;
for (int gap = 1; gap < n; gap = gap * 3 + 1)
gaps.push_back(gap);
for (int i = gaps.size() - 1; i >= 0; --i) {
int gap = gaps[i];
// 对该 gap 执行分组插入排序
}- 起始
gap必须小于n,否则内层循环越界 - 用
gaps.size() - 1而非gaps.size()开始,避免索引越界 - 不要用
while (gap > 0)然后gap /= 2,这会跳过某些有效 gap 值
内层分组插入排序的边界处理
对每个 gap,需对从 gap 到 n-1 的每个位置 i 执行插入操作,但比较和移动只发生在同组内(即下标差为 gap 的倍数)。易错点在循环条件和索引偏移:
立即学习“C++免费学习笔记(深入)”;
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;
}-
j >= gap是必须的判断,否则j - gap会变成负数 - 不能写成
j > 0,那会错误地让不同组元素参与比较 -
arr[j] = temp必须在while外赋值,否则可能覆盖未移动的元素
C++ 实现要注意的细节
原地排序、无需额外空间,但稳定性被破坏(相同值的相对顺序可能改变)。实际使用中几个硬性注意点:
- 输入为
std::vector<int>&或裸指针 +size_t n,避免传值拷贝整个容器 - 若排序自定义类型,确保重载了
operator<或传入比较函数(std::less<>等) - 小数组(
n < 10)直接用std::sort更快;希尔排序优势在n ≈ 1000~10000区间 - 最坏时间复杂度仍是
O(n²),但平均表现接近O(n^1.3),取决于 gap 序列选择
真正难的不是写对一次,而是选对 gap 序列 + 控制好每层循环的上下界——这两个地方写错,算法就 quietly 退化成慢速插入排序,还不好 debug。


















