👨💻 关于作者:会编程的土豆
"不是因为看见希望才坚持,而是坚持了才看见希望。"
你好,我是会编程的土豆,一名热爱后端技术的Java学习者。
📚 正在更新中的专栏:
-
《数据结构与算法》😊😊😊
-
《leetcode hot 100》🥰🥰🥰🤩🤩🤩
-
《数据库mysql》
💕作者简介:后端学习者

概念
希尔排序 = 插入排序 + 分组跳跃
它不是一次只和前面相邻的元素比,而是先隔着很远比,然后慢慢缩小距离,最后变成普通的插入排序
为什么需要希尔排序?
简单插入排序有个明显的软肋:当较小的数都堆在数组尾部时,排序效率会很低。因为插入排序每次只能交换相邻元素,要把尾部的小数挪到前面,需要一步一步"冒泡"过去,非常耗时。
看一下插入排序的代码:
cpp
public static void insertionSort(int[] arr) {
int len = arr.length;
for (int i = 1; i < len; i++) {
int j = i;
int temp = arr[j];
while (j > 0 && arr[j - 1] > temp) {
//如果前一个元素比当前元素大,则将前一个元素向后移动一位。
//将 j 的值减1,继续比较前一个元素。
arr[j] = arr[j - 1];
j--;
}
arr[j] = temp;
}
}
而希尔排序通过引入增量(Gap) 的概念,允许元素跳跃式移动,让小数能更快地"蹦"到前面,从而解决了这一问题
希尔排序代码
cpp
void shellSort(vector<int>& arr) {
int n = arr.size();
// 外层循环:控制 gap 从 n/2 开始,每次减半
for (int gap = n / 2; gap > 0; gap /= 2) {
// 内层循环:对每个分组做插入排序
// 注意这里从 gap 开始,而不是从 1 开始
for (int i = gap; i < n; ++i) {
int key = arr[i];
int j = i - gap;
// 和前面相隔 gap 的元素比较
while (j >= 0 && arr[j] > key) {
arr[j + gap] = arr[j]; // 大的往后挪 gap 个位置
j -= gap; // 往前跳 gap 个位置继续比
}
arr[j + gap] = key; // 插入到正确位置
}
}
}
插入排序 vs 希尔排序 代码对比
| 插入排序 | 希尔排序 |
|---|---|
for (int i = 1; i < n; ++i) |
for (int gap = n/2; gap > 0; gap/=2) |
for (int i = gap; i < n; ++i) |
|
int j = i - 1; |
int j = i - gap; |
while (j >= 0 && arr[j] > key) |
while (j >= 0 && arr[j] > key) |
arr[j + 1] = arr[j]; |
arr[j + gap] = arr[j]; |
j -= 1; |
j -= gap; |
arr[j + 1] = key; |
arr[j + gap] = key; |
唯一的区别 :把插入排序里的 1 全部换成了 gap,再套一层 gap 递减的循环。
复杂度与特点
| 特性 | 说明 |
|---|---|
| 时间复杂度 | 取决于 gap 序列,经典希尔增量 O(n²),优化序列可达 O(n^1.3) |
| 空间复杂度 | O(1),原地排序 |
| 稳定性 | 不稳定(跳跃式交换可能打乱相同元素的顺序) |
| 适用场景 | 中等规模数据,嵌入式设备等内存敏感场景 |
一句话总结
希尔排序 = 带间隔的插入排序,通过"大步跳跃 + 逐步逼近"来优化小数后移的问题。
希尔排序的核心原理
核心概念:增量(Gap)
希尔排序引入了一个间隔 gap ,它不是让元素和相邻的比,而是和相隔 gap 个位置的元素比。
过程分三步:
-
分组:按 gap 把数组分成若干组
-
组内插入排序:对每组分别做插入排序
-
缩小 gap:gap 减半,重复上述过程,直到 gap = 1
当 gap = 1 时,就是普通的插入排序,但此时数组已经基本有序,插入排序会非常快。
图解演示
数组:[80, 93, 60, 12, 42, 30, 68, 85, 10]
第一轮:gap = 4
把相隔 4 个位置的元素分为一组:
cpp
下标: 0 1 2 3 4 5 6 7 8
值: 80 93 60 12 42 30 68 85 10
分组情况(相同标记的为一组):
组1 (▲): 80, 42, 10 → 下标 0, 4, 8
组2 (●): 93, 30 → 下标 1, 5
组3 (■): 60, 68 → 下标 2, 6
组4 (◆): 12, 85 → 下标 3, 7
对每组分别做插入排序:
-
组1 排序后:10, 42, 80
-
组2 排序后:30, 93
-
组3 排序后:60, 68
-
组4 排序后:12, 85
放回原数组:
cpp
排序前: [80, 93, 60, 12, 42, 30, 68, 85, 10]
排序后: [10, 30, 60, 12, 42, 93, 68, 85, 80]
观察 :最小的 10 从最后一位(下标 8)跳到了第一位(下标 0)!一步跨了 8 个位置,这就是希尔排序的效率来源。
第二轮:gap = 2
cpp
当前: [10, 30, 60, 12, 42, 93, 68, 85, 80]
分组:
组1 (▲): 10, 60, 42, 68, 80 → 下标 0,2,4,6,8
组2 (●): 30, 12, 93, 85 → 下标 1,3,5,7
分别插入排序后放回:
cpp
排序前: [10, 30, 60, 12, 42, 93, 68, 85, 80]
排序后: [10, 12, 42, 30, 60, 85, 68, 93, 80]
第三轮:gap = 1
就是普通的插入排序:
cpp
排序前: [10, 12, 42, 30, 60, 85, 68, 93, 80]
排序后: [10, 12, 30, 42, 60, 68, 80, 85, 93]
此时数组已经基本有序,插入排序只需要很少的移动就能完成。
注意排序时:
每一组元素只在属于自己组的下标位置 之间进行排序和交换,完全不碰其他组的元素。
说人话就是:比如说这一组数,就是在原来数组的这一组的基础之上排序换位置,而其他组的位置丝毫不影响,只在自己的那个gap组里面换;