🔆欢迎来到我的【数据结构】专栏🔆
- 👋我是Brant_zero,一名学习C/C++的在读大学生。
- 🌏️我的博客主页➡➡Brant_zero的主页
- 🙏🙏欢迎大家的关注,你们的关注是我创作的最大动力🙏🙏
🍁前言
在上篇文章中我们实现了堆排序(堆排序与Top-K),是一种效率非常高的排序,接下来我们来学习另外两种排序,插入排序和希尔排序 。
目录
一、 插入排序
1.1 算法概述
⏳插入排序,是指在待排序的元素中,假设前面n-1(其中n>=2)个数已经是排好顺序的,现将第n个数插到前面已经排好的序列中,然后找到合适自己的位置,使得插入第n个数的这个序列也是排好顺序的。按照此法对所有元素进行插入,直到整个序列排为有序的过程,称为插入排序。
1.2 算法思路
🌰实现思路(升序):
- 设置初始值end为0,即从下标为0处开始,假设0处数据是已经排好序;用temp记录下end+1处的数据,进入循环。
- 在循环中,如果end处的值大于temp中的值,则将end+1处覆盖,即arr[end+1]=end;再将end -- ,使end向前移动。
- 如果end处的值 <= temp的值,则跳出循环,无需进行覆盖;直接break跳出循环。
- 跳出循环后,将temp的值赋给end+1,则是将值插入到数组中。
- 设置循环条件为 end >= 0,则 end 减到 -1 时就会跳出循环,表示着遍历完了整个数组。
1.3 代码部分
void InsertSort(int* a, int n)
{
for (int i = 0; i < n-1; i++)
{
int end = i;
int temp = a[end + 1];
while (end >= 0)
{
//如果end处的数据大于end+1 则将end+1处的数据覆盖
if (temp < a[end])
{
a[end + 1] = a[end];
end--;
}
//表示end处的数据小于end+1的数据了,则应该将数据插入到end+1处了
else
{
break;
}
}
a[end + 1] = temp;
}
}
二、 希尔排序
2.1 算法概述
⏳希尔排序(Shell's Sort)是插入排序的一种又称"缩小增量排序",是直接插入排序算法的一种更高效的改进版本。 希尔排序是非稳定排序算法。 该方法因D.L.Shell 于1959 年提出而得名。
2.2 算法思路
💡希尔排序总共分两大步
- 预排序(使其接近有序)
- 直接插入排序( 接近有序时,插排的时间复杂度为O (N) )
💡我们将数组a分为gap组,❤️红色线为1组、💙蓝色线为1组、💜紫色线为1组。
然后进行预排序,如下图。
(观察预排序后数组的顺序)
💡不难发现,原本逆序的数组预排序后,居然十分接近有序了。我们又知道,在接近有序时,插入排序的时间复杂度接近O(N),然后我们使用一次插入排序,是否就能直接将数组变为有序的了呢?
💡这就是希尔排序的思路,因此希尔排序的时间复杂度可以达到O(
)左右。
💡(希尔的时间复杂度计算十分复杂,这里只是给一个大概的值,细致的推到大家可以查阅相关书籍或者网上查找结果)
2.3 算法实现
💡经过上面的分析,那我们要先来实现一下希尔排序的预排序。
💡首先实现一组gap(gap == 3)的排序,大家看看下面的动图🖼️。
1) 单趟的实现
代码如下👇
//这是单趟的代码,实际上是无法运行的,给大家做个演示
void ShellSort(int* a, int n)
{
int gap = 3;
int end = 0;
int temp = a[end + gap];
while (end >= 0)
{
if (temp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
}
a[end + gap] = temp;
}
✅这样,一个gap组的排序就完成了。
这时善于发现的同学(吴彦祖)们就发现了,这单趟的排序不就是特殊的插入排序吗,如果gap == 1,那就直接是单趟的插入排序了呀。所以才说,希尔排序是对插入排序的优化嘛。
2) 多趟的实现
多趟其实非常简单,我们每次排完一趟后,就让end++,就是排下一gap组的一趟,我们只要让每个gap组中的每一趟都走完即可。
上图的意思就是,end每次调整就是一个gap组中的一趟,我们可以让end++,就是在排每一趟,那end最多走到4的位置,也就是下标n-4的位置。
所以我们设置一个循环,让end < n-3即可,而这个循环的范围,就是gap组的划分,因为gap == 3,所以end < n - 3 ,那我们设置一个循环,使end < n - gap,即可完成每组的预排序。
代码如下👇
for (int i = 0; i < n - gap; i++)
{
int end = i;
int temp = a[end + gap];
while (end >= 0)
{
if (temp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
a[end + gap] = temp;
}
}
3) gap的设置
最后一个问题就是gap的设置,发现两个规律。
- 排升序,gap越大,大的数更快到后面,小的数可以更快的到前面,但是越不接近有序。
- 排升序,gap越小,越接近有序的,当gap == 1,就是直接插入排序
我们应该将gap设置为多少呢?根据官方一种好的解决方式,设置每次gap = gap / 3,使预排序有一定的次数。
最后一个细节,我们要控制最后一次排序为插入排序,而插入排序就是gap为1的排序,所以我们要保证最后一次gap为1,做的一个方法就是,gap除以3之后,加上一个1,这样就一定能保证gap最后一定为1。
2.4 代码部分
void ShellSort(int* a, int n)
{
//即->gap>1时是预排序
//gap==1时是直接插入排序
//gap跟n相关
//每次除以3
int gap=n;
while (gap > 1)
{
//除2、除3都可以,但是一定要保证最后一次为插入排序
//当gap==6 gap/=3 ---> 2 ,
//2/3 成了0, 所以可以再末尾加一个1,就可以保证最后一次一定为1;
gap = gap / 3 + 1;
for (int i = 0; i < n - gap; i++)
{
int end = i;
int temp = a[end + gap];
while (end >= 0)
{
if (temp < a[end])
{
a[end + gap] = a[end];
end -= gap;
}
else
{
break;
}
a[end + gap] = temp;
}
}
}
}
总结
本篇博客介绍了插入、希尔排序,我已经尽我最大的努力解释这两个排序算法了。希望能够帮助大家掌握这两个排序的思想。
其中插入排序在接近有序的数组中时间复杂度为O(N),这是一个比选择、冒泡优上许多的排序;而希尔排序更是一个不错的排序,它的时间复杂度近似为O(
),并且实现方便、思想简单。
好了,本篇博客到此就结束了,希望大家能够喜欢,点点关注和赞吧。
🙏🙏🙏你们的鼓励是我更新的最大动力🙏🙏🙏
我们下期再见,下期我们来学习冒泡和快速排序。