本篇文章主要是讲解直接插入排序的优化算法 -- 希尔排序算法。
目录
[1 直接插入排序算法的优化](#1 直接插入排序算法的优化)
[2 算法思想](#2 算法思想)
[3 代码](#3 代码)
[4 时间复杂度与空间复杂度分析](#4 时间复杂度与空间复杂度分析)
[1) 时间复杂度](#1) 时间复杂度)
[2) 空间复杂度](#2) 空间复杂度)
1 直接插入排序算法的优化
在上一篇文章中,我们讲解了直接插入排序,可以了解到直接插入排序的时间复杂度是 O(n^2) 的,时间复杂度较高,所以在 1959 年,唐纳德.L.希尔(Donald.L.Shell)提出了直接插入排序算法的优化算法 -- 希尔排序算法。
直接插入排序是将未排好序的元素直接插入到已经排好序的序列之中,所以会出现大量的数据比较与移动,所以直接插入排序的大部分的时间耗费都是来自于元素的移动,当移动的元素次数越少时,其时间的耗费就会减少,时间复杂度也就会降低 。由此可见,当数组中的元素越接近有序,直接插入排序的时间耗费的越低的。比如:有两个序列(1)1,3,2,5,4和(2)5,4,3,2,1,排升序的话肯定是序列(1)的时间耗费更少。
2 算法思想
希尔排序就是利用了上面的优化方法,先将原来的数组按照一定的间隔分组,再将每组的数据进行直接插入排序,然后将间隔变小,再分组,再排序,最后直到间隔变为1的时候,数组基本就有序了,然后就变成了直接插入排序,希尔排序的过程如图所示:

首先设置每组的间隔(gap)为3 ,然后对整个数组中的数据进行分组,下标为0,3,6,9的数据为一组,下标为1,4,7的数据为一组,下标为2,5,8的数据为一组,对每组分别进行直接插入排序;之后再将间隔(gap)变为2 ,然后下标为0,2,4,6,8的数据为一组,下标为1,3,5,7,9的数据为一组,然后再对每一组进行直接插入排序;经过两次分组的排序之后,数组基本就接近有序了,再将间隔(gap)变为1,这样就变成了之前的直接插入排序,时间复杂度会大大降低。
希尔排序的关键就是 gap 的确定 ,gap 的大小确定了比较的次数以及交换的次数,假设数组长度为n,gap一般都确定为 n/3 + 1,或者是n/2 + 1,最后加1是为了防止gap越除越小的时候除到0,让其最后一次的gap变为1,以最初的直接插入排序结尾。
3 代码
cpp
//交换函数
void Swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
//希尔排序
void ShellSort(int* arr, int n)
{
int gap = n;
while (gap > 1)
{
gap = gap / 3 + 1;//这里的间隔除2,除3都可以,但是最后必须加个1
//接下来就类似于直接插入排序
for (int i = 0;i < n - gap; i++)
{
int end = i;
int tmp = arr[end + gap];
while (end >= 0)
{
Swap(&arr[end], &arr[end + gap]);
end -= gap;
}
else
{
break;
}
arr[end + gap] = tmp;
}
}
}
解释 :这里的希尔排序的实现代码并不是对数据进行分组,依次对每组进行排序,而是对每一个数据跟同组的相邻数据进行比较,然后交换,例如: 当gap为3的时候,先对0下标的数据和3下标的数据进行比较和交换,然后下一次并不是对3下标和6下标的数据进行比较和交换,而是对1下标和4下标的数据进行比较和交换;依次进行下去,直到 n - 1 - gap下标的元素和 n - 1 下标的元素比较和交换完后(n为数组中元素的个数),当前 gap 为 3 的分组的比较就结束了;之后再将 gap 变为gap/3 + 1,继续执行上述过程,直到 gap 变为1,执行完一次直接插入排序之后,整个的希尔排序就结束了。
其实希尔排序相比于直接插入排序来说就只是增加了一个 gap 的循环,里面的比较逻辑变为了和同组的相邻元素之间的比较,其余的逻辑都跟直接插入排序是相同的,所以学懂了直接插入排序,希尔排序也会很容易学懂的。
测试用例:
cpp
//打印函数
void Print(int* arr, int n)
{
for (int i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
//测试用例
int main()
{
int arr[] = { 10, 2, 5, 7, 1, 4, 8, 9, 6};
int n = sizeof(arr) / sizeof(arr[0]);
ShellSort(arr, n);
Print(arr, n);
return 0;
}
4 时间复杂度与空间复杂度分析
1) 时间复杂度
希尔排序的时间复杂度是不确定的,根据gap的不同,时间复杂度也是不一样的,但在平均情况下,时间复杂度是T(n) = O(n^1.3),基本上是接近 T(n) = O(nlogn)的。
2) 空间复杂度
由于希尔排序算法仅仅用了几个有限的变量,并不会随着空间规模的增大而使得使用空间增大,所以空间复杂度是 O(1) 的。