希尔排序
直接插入排序和折半插入排序在数组基本有序时效率很高,但面对无序数组时,时间复杂度为 O ( n 2 ) O(n^2) O(n2)。希尔排序通过"分组插入、逐步缩增量"的策略,大幅提升了插入排序的效率,它是插入排序的进阶优化版本。
1. 希尔排序的核心思想
希尔排序将待排序数组按"增量(间隔)"分成若干组,每组内的元素进行直接插入排序;然后逐步缩小增量,重复分组和组内排序的过程,直到增量为1时,进行最后一次全局的直接插入排序。这里的"增量"决定了分组的依据,例如增量为 d d d时,下标为 i , i + d , i + 2 d , ... i, i+d, i+2d, \dots i,i+d,i+2d,...的元素为一组。
通过这种"先宏观调整,再微观整理"的方式,数组会逐渐趋于有序,最终在增量为1时,插入排序的效率会非常高(因为数组已基本有序)。
2. 希尔排序的执行流程(结合示例图片)
我们以示例图片中的初始关键字arr = {49, 38, 65, 97, 76, 13, 27, 49, 55, 04}为例,详细分析希尔排序的每一趟过程:
-
第一趟:增量 d 1 = 5 d_1 = 5 d1=5
按增量5分组,数组被分为5组:
(49,13)、(38,27)、(65,49)、(97,55)、(76,04)。对每组内的元素进行直接插入排序:- 组
(49,13)排序后为(13,49); - 组
(38,27)排序后为(27,38); - 组
(65,49)排序后为(49,65); - 组
(97,55)排序后为(55,97); - 组
(76,04)排序后为(04,76)。
将各组结果按顺序拼接,得到第一趟排序结果:13, 27, 49, 55, 04, 49, 38, 65, 97, 76(如图中"第一趟排序结果"所示)。
- 组
-
第二趟:增量 d 2 = 3 d_2 = 3 d2=3
缩小增量为3,重新分组:
(13,55,38,76)、(27,04,65)、(49,49,97)。对每组内的元素进行直接插入排序:- 组
(13,55,38,76)排序后为(13,38,55,76); - 组
(27,04,65)排序后为(04,27,65); - 组
(49,49,97)排序后为(49,49,97)。
拼接后得到第二趟排序结果:13, 04, 49, 38, 27, 49, 55, 65, 97, 76(如图中"第二趟排序结果"所示)。
- 组
-
第三趟:增量 d 3 = 1 d_3 = 1 d3=1
增量缩小为1,此时数组已基本有序,进行一次全局的直接插入排序。最终得到有序数组:
04, 13, 27, 38, 49, 49, 55, 65, 76, 97(如图中"第三趟排序结果"所示)。
3. 希尔排序的代码实现
希尔排序的关键是选择增量序列,常见的增量选择是"初始增量为 n / 2 n/2 n/2,每次减半"( n n n为数组长度)。以下是希尔排序的C语言实现:
c
void ShellSort(int arr[], int n) {
int d, i, j, temp;
for (d = n / 2; d > 0; d /= 2) { // 增量逐步缩小
for (i = d; i < n; i++) { // 遍历每组的第二个及以后元素
temp = arr[i];
for (j = i - d; j >= 0 && arr[j] > temp; j -= d) {
arr[j + d] = arr[j]; // 组内元素后移
}
arr[j + d] = temp; // 插入元素
}
}
}
代码说明:
- 外层循环控制增量 d d d的缩小,从 n / 2 n/2 n/2开始,每次减半;
- 中层循环遍历"每组"的待插入元素(从第 d d d个元素开始,因为每组第一个元素默认在组内有序);
- 内层循环在组内进行直接插入排序,通过
j -= d实现组内元素的比较和移动。
4. 希尔排序的性能与特性
- 时间复杂度 :希尔排序的时间复杂度与增量序列的选择密切相关,目前没有精确的数学分析,但在平均情况下,其时间复杂度为 O ( n 1.3 ) O(n^{1.3}) O(n1.3),远优于直接插入排序的 O ( n 2 ) O(n^2) O(n2)。
- 空间复杂度 :仅需一个临时变量
temp,空间复杂度为 O ( 1 ) O(1) O(1)。 - 稳定性 :由于分组排序时会打乱相同元素的相对顺序,因此希尔排序是不稳定的。
5. 适用场景
希尔排序适合数据量较大且无序的场景,它能快速将数组"宏观调整"为基本有序,大幅降低最终插入排序的时间开销。例如,在处理 thousands 级别的数据时,希尔排序的效率明显高于直接插入排序。
综上,希尔排序通过"分组插入、逐步缩增量"的策略,解决了直接插入排序在无序数组上的低效问题,是插入排序家族中性能更优的算法。理解其分组排序的逻辑和增量选择的影响,能帮助我们在实际场景中选择更高效的排序方式。