一、引言
排序(Sorting)是计算机科学中最基础、应用最广的算法之一。
想想这些场景:
-
从通讯录里找一个人 → 按姓氏排序后,二分查找秒到
-
淘宝按价格从低到高筛选商品 → 价格排序
-
考试后按分数排名 → 成绩排序
如果没有排序,这些操作都会变得极其低效。

本篇文章是排序系列的第一篇 ,我会带你从最简单、最直观的直接插入排序 开始,然后逐步递进到它的升级版------希尔排序。
学完这篇,你不仅能掌握这两种排序的实现,还能理解一个非常重要的算法设计思想:如何从朴素的思路出发,通过"预排序"来优化性能。
二、直接插入排序
核心思想
直接插入排序的核心思想,可以用一个生活场景完美解释------理扑克牌。
当你打牌时,会一张一张摸牌,每摸到一张新牌,你会从右向左跟手里已有的牌比较,找到合适的位置插进去,始终保持手里的牌是有序的。
- 将待排序的数据分为"已排序区间"和"未排序区间"。每次从未排序区间取出第一个元素,插入到已排序区间中的正确位置,使已排序区间仍然保持有序。
下面我们来画图理解

理解了直接插入排序的思想之后,我们来用代码实现一下
cpp
void InsertSort(int* arr, int n)
{
for (int i = 0;i < n - 1;i++)
{
int end = i;
int tmp = arr[end + 1];
while (end >= 0)
{
if (arr[end] > tmp)
{
arr[end + 1] = arr[end];
end--;
}
else
{
break;
}
}
arr[end + 1] = tmp;
}
}
接下来我们来分析一下直接插入排序的时间复杂度:
| 情况 | 复杂度 | 说明 |
|---|---|---|
| 最好(数组已经有序) | O(n) | 每个元素只比较一次,不移动 |
| 最坏(数组逆序) | O(n²) | 第 i 个元素需要比较 i 次,总次数 = 1+2+...+(n-1) ≈ n²/2 |
| 平均 | O(n²) | 随机数据,平均比较和移动约 n²/4 |
平均O(n²)的时间复杂度太高了,面对无序且大量的数据,直接插入排序的效率很低,因此在此排序思想的基础上,又演变出了一种更加高效的排序方法:希尔排序。
三、希尔排序
核心思想
希尔排序也叫**"缩小增量排序"**(Diminishing Increment Sort)。它的核心思想是:
先进行"预排序",让数组变得"基本有序",最后再进行一次直接插入排序。
怎么预排序?------按间隔分组。

例如图中的一组数据,我们以 gap = 4 的间隔来分组,可以分为四个组。
对每一组分别进行插入排序 。这样,元素就可以一次跨越 gap 步到达相对正确的位置。
预排序完成后,缩小 gap,继续分组排序......直到 gap = 1,此时就是标准的直接插入排序。但因为数组已经基本有序,插入排序的效率很高(接近 O(n))。
对于 gap,我们通常让 gap = gap / 3 + 1
下面是希尔排序的代码实现
cpp
void ShellSort(int* arr, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = 0;i < n - gap;i++)
{
int end = i;
int tmp = arr[end + gap];
while (end >= 0)
{
if (arr[end] > tmp)
{
arr[end + gap] = arr[end];
end -= gap;
}
else
{
break;
}
}
arr[end + gap] = tmp;
}
}
}
时间复杂度的分析:
| 情况 | 复杂度 | 说明 |
|---|---|---|
| 最好 | O(n log n) | 数据基本有序时 |
| 最坏 | O(n²) | 取决于 gap 序列,某些序列可避免 O(n²) |
| 平均 | O(n^1.3 ~ n^1.5) | 远优于 O(n²) |
由此可见,在优化后的希尔排序比优化之前的直接插入排序时间复杂度要小很多,这就是这种预排序思想所带来的好处
五、总结
一句话记住这两种排序:
直接插入排序 :像理扑克牌,一张一张插入到正确位置。
希尔排序:先分组排序让数组"大概有序",再最后插入排序收尾。
建议:
-
先熟练掌握直接插入排序
-
理解希尔排序的"预排序 + 最终插入"思想------这是重要的算法设计思路
-
一定要手写一遍代码,感受两种排序的异同