目录
一、 引言
二、算法思想
三、算法步骤
四、代码实现
五、复杂度
💓 博客主页:C-SDN花园GGbond
⏩ 文章专栏:探索数据结构与算法
一、 引言
希尔排序(Shell Sort)是插入排序的一种更高效的改进版本,也称为缩小增量排序。
希尔排序的直接灵感来源于插入排序,但它在插入排序的基础上进行了显著的改进,旨在提高排序效率,特别是针对大规模数据集。
二、算法思想
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次标准的直接插入排序。
这里的“基本有序”是指:待排序的数组元素值满足某个增量序列的“局部有序”,即对于某个变量gap,序列中所有距离为gap的元素之间是有序的。随着变量gap的逐渐减小,当gap减小到1时,整个序列恰好被“基本有序”,此时再对全体元素进行一次直接插入排序即可
三、算法步骤
- 选取一个gap对数据进行分组,每间隔gap个元素分为一组,一共gap组。
- 以gap为基准单位,对其进行插入排序。
- 依次缩小gap的范围,直至gap为1,相当于进行一次正常的插入排序。
动图演示
1.外循环进行多轮预排序
选择一个变量序列gap:
这个序列是逐渐减小的,gap的值较大时,数据可以更快的前后变动,但不容易"基本有序";gap较小时数据前后变动较慢,但更接近"基本有序"。 通常可以选取gap = n/3, gap = gap/3, ...,直到gap= 1。
.递减变量gap并重复上述分组排序过程:
每完成一轮按变量gap的分组排序后,将变量gap减小,然后重复分组排序过程,直到变量gap为1,此时整个数组恰好被分成一组,进行最后一次直接插入排序。
注意,如果直接每次都/3,可能面临的情况就是最后一组gap的值跳过了1,比如n=8时,gap第一次等于2,第二次等于0,解决方法也很简单,gap每次不是/3,而是gap=gap/3+1,就可以让gap最后一次一定会减小到1
2.第二层循环,每一轮预排序中进行分组
按gap进行分组:根据当前的变量gap,将待排序的数组元素下标按gap分组,总共可以分成gap组。比如gap为3时,每一组元素的首元素分别是0,1,2
每一组的数据有n/gap个,下标为0,gap, 2gap, 3gap,...的元素分为一组;下标为1,gap+1,2gap+1,3gap+1……的元素分为一组……
3.第三层循环,分组之后,控制组里数据执行插入排序
这一层循环一个需要注意的细节就是预防数组的越界:。每次选取的要插入的数据下标是end+gap,那么这个下标不能超过n-gap。比如数组有10个元素,gap为3,第一组数据最后一个数据的下标是9,要保证这一组数据访问到下标9之后,不再向后访问,因为下一次访问end为9,要插入的数据,9+gap的位置已经没有数据了。
4.第四层循环,实现插入排序的过程
每个数据向前扫描和移动,找到合适的位置后插入,直接在插入排序代码的基础上稍加修改即可
四、代码实现
//希尔排序分组进行
void ShellSort1(int* a,int len)
{
int gap = len;
while (gap > 1)//多组预排序,最后一组gap==1为直接插入排序
{
gap = gap / 3 + 1;
for (int i = 0; i < gap; i++)//控制分组的组数:gap组
{
for (int j=i; j < len - gap; j += gap)//控制每组的插入元素个数:n/gap个
{
int end = j;
int tmp = a[end + gap];
while (end >= 0)
{
if (a[end] > tmp)//比较和移动元素
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;//满足大小关系后插入到指定位置
}
}
}
}
//希尔排序同时进行
void ShellSort2(int* a, int len)
{
int gap = len;
while (gap > 1)
{
gap = gap / 3 + 1;
for (int i = 0; i < len-gap; i++)
{
int end = i;
int tmp = a[end + gap];
while (end >= 0)
{
if (a[end] > tmp)
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = tmp;
}
}
}
希尔排序不分组同时进行
将第二层第三层循环合为一层循环,
以前是四层循环时,我们是将分组作为一层循环,每组里的数据插入作为一层循环
将两层循环合为一层之后,不是一组一组进行预排序,而是将数据逐个的,与它对应的组里的数据进行预排序,互不影响
优化之后代码更加简洁,但效率没有提升
五、复杂度
- 时间复杂度:希尔排序的时间复杂度一般较为难计算,通过大量测验一般认为其时间复杂为O(N1.3 )。
- 空间复杂度:没有开辟额外的空间大小,所以空间复杂度为O(1)。
- 希尔排序是原地排序算法,它只需要一个额外的空间来存储临时变量(用于数据交换),因此其空间复杂度为O(1)。这意味着希尔排序在排序过程中不会占用额外的存储空间,这对于内存资源有限的环境非常有利。
稳定性:不稳定的
希尔排序是不稳定的排序算法。在排序过程中,由于存在跳跃式的比较和移动,相同元素的相对位置可能会发生变化。因此,在需要保持元素原始顺序的场景中,希尔排序可能不是最佳选择。