【数据结构】手撕排序NO.2----直接插入排序与希尔排序

这篇具有很好参考价值的文章主要介绍了【数据结构】手撕排序NO.2----直接插入排序与希尔排序。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

 【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

目录

一. 导入

二. 直接插入排序

        2.1 基本思想

        2.2 过程分析

        2.3 代码实现

        2.4 复杂度/稳定性分析

三. 希尔排序(缩小增量排序)

        3.1 基本思想

        3.2 过程分析 

        3.3 代码实现 

        3.4 复杂度/稳定性分析


 一. 导入

        本期是排序篇的第二期,我们的主角是插入排序。在座的各位或多或少都玩过扑克牌吧!我们在摸扑克牌时,往往会将大牌插到小牌后面,小牌插到大牌前面。当摸完所有的牌后,我们手中的牌自然也就有序了,这实际上就是用到了插入排序的思想【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

本期要点

  • 对直接插入排序进行解析
  • 对希尔排序进行解析
  • 两种插入排序的复杂度和稳定性进行分析

二. 直接插入排序

        2.1 基本思想

        插入排序的基本思想是:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。

【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

        2.2 过程分析

        我们可以将第一个元素作为一个有序序列,从第二个元素开始插入到这个有序序列中,过程如下:

以升序为例:

当插入第i个元素时(i>=1),说明前面的array[0],array[1],…,array[i-1]已经排好序,我们将array[i]位置的值从后往前与array[i-1],array[i-2],…依次进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移


为什么不从前往后开始进行比较?

如果我们从前往后进行比较,当找到插入位置时,根据顺序表的插入我们知道:必须先将后面的元素全部后移,而后移又不能从前往后移,否则会造成元素覆盖,我们必须从后往前移动。折腾了半天又要从后往前遍历,那为什么不一开始就从后往前比较,在比较的同时一并挪动元素,何乐而不为呢?【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

        单趟插入动图:

【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

        全过程动图

【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言


         2.3 代码实现

//插入排序
void InsertSort(int* a, int n)
{
	assert(a); //确保a不为空
	int end;
	for (int i = 1; i < n; i++) //从第二个元素开始插入,下标为1
	{
		int tmp = a[i];
		end = i - 1; //前面的元素已经有序,从末端,也就是i-1处开始比较
		while (end >= 0) //查找插入位置
		{
			if (a[end] > tmp) //大于则往后挪动数据
			{
				a[end + 1] = a[end];
				end--;
			}
			else //a[end] <= tmp,找到插入位置,跳出循环
			{
				break;
			}
		}
		a[end + 1] = tmp; //在合适位置插入
	}
}

【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

         2.4 复杂度/稳定性分析

          复杂度【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

         根据上面的动图我们可以发现,当元素集合越接近有序,直接插入的时间效率越高,当元素集合已经有序时,时间复杂度便是O(N),即遍历一遍数组(最优情况)。

         而时间复杂度我们看的是最坏情况,即数组元素逆序的情况。此时每次插入相当于头插,而头插的时间复杂度为O(N),因此总时间复杂度为O( 插入次数 * 单次插入时间复杂度 ) = 。

         而除了待排序数组之外只有常数级的辅助空间,空间复杂度为O(1)

          稳定性【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

          由于我们是大于tmp才进行元素挪动,当等于tmp时直接将tmp放到后方,不会对相同元素的顺序进行改变,因此直接插入排序是稳定的排序

         

三. 希尔排序(缩小增量排序)

        3.1 基本思想

         希尔排序又称缩小增量法,其基本思想是:选出一个整数gap表示增量,根据gap将待排序的记录分为gap组所有距离为gap的记录分在同一组,然后对每一组进行直接插入排序随着排序次数的增多,增量gap逐渐减少,当gap=1时,即所有记录分在同一组进行直接插入排序,排序完成后序列便有序了,算法结束。分组方法如下:

【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

        3.2 过程分析 

  1. 首先,我们需要对gap的初始值进行确定,这并没有一个确定的答案,一般是按照数据量来进行选取。一般我们选取gap = n/3 + 1或者gap = n/2作为gap的初始值。
  2. 根据算法的思想,我们观察到gap是在不断地进行缩小,最后缩小到1进行直接插入排序。因此我们gap的缩小增量可以继续使用gap = gap/3 + 1gap = n/2来表示。
  3. 对1、2点进行合并后,我们可以这样用来表示gap的迭代更新:
int gap = n; //n为序列元素个数
while(gap > 1)
{
    //gap = gap/2;
    gap = gap/3 + 1;

    //每组进行直接插入排序
    //...
}

上面的循环在以下两种情况下会结束:

  • n == 1:即序列只有一个元素,此时无需进行排序,不会进入循环
  • n != 1 ,gap == 1:由于gap的更新是在插入排序之前,因此当循环判断到gap == 1时,上一次进行的就是以1为gap增量的直接插入排序,此时序列已经有序,退出循环。

为什么要取gap = gap/3 + 1而不是gap = gap/3?

由于最后gap要缩小到1进行直接插入排序,而如果我们选取gap = gap/3时,假设gap初始为6,第一次更新后gap=2,第二次更新后gap=0(向下取整),循环便结束了,并不会进行gap=1时的插入排序。因此,为了避免这种情况的发生,我们让gap = gap/3 + 1保证最后一次gap一定为1。


那为什么取gap = gap/2而不是gap = gap/2 + 1?

这种情况不需要处理的原因是gap不可能等于0,因为进入循环的条件是gap>1,而gap只有等于0或1时gap/2才会为0。因此,无论gap初始为多少,最后一定都会在gap=1处停下。并且,当gap=2时,使用gap = gap/2 + 1会出现死循环【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

      4. 每组之间进行的就是我们上面介绍的直接插入排序,不一样的是相邻元素的距离是gap而不是1。以下是gap = 3时的单趟希尔排序的动图过程:

【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

        5. 下面是希尔排序的全过程(gap = gap/3 + 1为例):

【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

         6. 通过观察,我们可以发现实际上希尔排序就是直接插入排序的优化版。随着每一趟的分组排序,序列越来越接近有序。前面我们说过,直接插入排序在序列越接近有序的情况下效率越高,希尔排序就是通过每趟的预排序来使得序列越来越接近有序,从而提高直接插入排序的整体效率。

        7. 要点提炼gap > 1时,对序列进行分组预排序,目的是使得序列越来越接近有序;当gap == 1时,数组接近有序,此时进行直接插入排序效率大幅提高。


         3.3 代码实现 

//写法1:一组一组排
void ShellSort1(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		//1:gap > 1,预排序
		//2:gap == 1,直接插入排序
		gap = gap / 3 + 1; //缩小增量
		for (int j = 0; j < gap; j++) //一组一组进行插入排序,共gap组
		{
			//对当前组进行直接插入排序,组内相邻元素相距gap。
			//(其实就相当于把上面介绍的直接插入排序代码中的-1改成-gap即可)
			for (int i = j; i < n - gap; i += gap)
			{
				int end = i;
				int tmp = a[end + gap];
				while (end >= 0)
				{
					if (tmp < a[end])
					{
						a[end + gap] = a[end];
						end -= gap;
					}
					else
					{
						break;
					}
				}
				a[end + gap] = tmp;
			}

		}

	}
}

但是,上面的代码实际上还可以写得更加简洁【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

我们知道每个组的元素都相距gap,而组与组之间距离都为1。那么,我们实际上不用一组一组分开排,而是采用多组并排的方式,这样就可以少写一个for循环,代码如下【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

//写法2:多组并排
void ShellSort2(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		//1:gap > 1,预排序
		//2:gap == 1,直接插入排序
		gap = gap / 3 + 1; //缩小增量

		for (int i = 0; i < n - gap; i++) //多组并排,i就不是+gap了,而是+1,排下一组的元素
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}



	}
}

         3.4 复杂度/稳定性分析

          复杂度【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

         尽管上面的代码有多个循环嵌套,但这并不意味着希尔排序的效率低下。我们根据代码来分析一下希尔排序的时间复杂度,过程如下图所示:

【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

         我们可以看到,在gap很大或者gap很小的情况下,每趟排序的时间复杂度为O(N),共进行趟,那我们是不是可以认为希尔排序的时间复杂度为O(NlogN)?实际上并不行,因为当gap处于中间的过程时,时间复杂度的分析实际上是个很复杂的数学问题。每一趟预排序之后都对下一趟排序造成影响,这就好比叠buff的过程。

【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

以下分别是两本书中对希尔排序时间复杂度的说法:

1、《数据结构(C语言版)》--- 严蔚敏
【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言


 2、《数据结构-用面相对象方法与C++描述》--- 殷人昆【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

         因为我们上面的gap是按照Knuth提出的方式取值的,并且Knuth进行了大量的试验统计,时间复杂度我们就按照:到来进行取值。

         然后就是空间复杂度,由于我们依旧只用到了常数级的辅助变量,因此空间复杂度为O(1)

          稳定性【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言

          由于希尔排序是分组进行排序,当相同的数被分到不同组时,很可能就会改变相同的数的顺序,因此,希尔排序是不稳定的排序。 

【数据结构】手撕排序NO.2----直接插入排序与希尔排序,数据结构,算法,排序算法,数据结构,c语言


以上,就是本期的全部内容啦🌸

制作不易,能否点个赞再走呢🙏文章来源地址https://www.toymoban.com/news/detail-621621.html

到了这里,关于【数据结构】手撕排序NO.2----直接插入排序与希尔排序的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包