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

news2024/10/6 18:35:37

 

目录

一. 导入

二. 直接插入排序

        2.1 基本思想

        2.2 过程分析

        2.3 代码实现

        2.4 复杂度/稳定性分析

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

        3.1 基本思想

        3.2 过程分析 

        3.3 代码实现 

        3.4 复杂度/稳定性分析


 一. 导入

        本期是排序篇的第二期,我们的主角是插入排序。在座的各位或多或少都玩过扑克牌吧!我们在摸扑克牌时,往往会将大牌插到小牌后面,小牌插到大牌前面。当摸完所有的牌后,我们手中的牌自然也就有序了,这实际上就是用到了插入排序的思想

本期要点

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

二. 直接插入排序

        2.1 基本思想

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

        2.2 过程分析

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

以升序为例:

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


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

如果我们从前往后进行比较,当找到插入位置时,根据顺序表的插入我们知道:必须先将后面的元素全部后移,而后移又不能从前往后移,否则会造成元素覆盖,我们必须从后往前移动。折腾了半天又要从后往前遍历,那为什么不一开始就从后往前比较,在比较的同时一并挪动元素,何乐而不为呢?

        单趟插入动图:

        全过程动图


         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; //在合适位置插入
	}
}

         2.4 复杂度/稳定性分析

          复杂度

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

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

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

          稳定性

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

         

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

        3.1 基本思想

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

        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会出现死循环

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

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

         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;
			}

		}

	}
}

但是,上面的代码实际上还可以写得更加简洁

我们知道每个组的元素都相距gap,而组与组之间距离都为1。那么,我们实际上不用一组一组分开排,而是采用多组并排的方式,这样就可以少写一个for循环,代码如下

//写法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 复杂度/稳定性分析

          复杂度

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

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

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

1、《数据结构(C语言版)》--- 严蔚敏


 2、《数据结构-用面相对象方法与C++描述》--- 殷人昆

         因为我们上面的gap是按照Knuth提出的方式取值的,并且Knuth进行了大量的试验统计,时间复杂度我们就按照:O(n^{1.25})O(1.6n^{1.25})来进行取值。

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

          稳定性

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


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

制作不易,能否点个赞再走呢🙏

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/819783.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

提高电脑寿命的维护技巧与方法分享

在维护电脑运行方面&#xff0c;我有一些自己觉得非常有用的技巧和方法。下面我将分享一些我常用的维护技巧&#xff0c;并解释为什么我会选择这样做以及这样做的好处。 首先&#xff0c;我经常清理我的电脑内部的灰尘。电脑内部的灰尘会影响散热效果&#xff0c;导致电脑发热…

Nodejs 第五章(Npm run 原理)

npm run xxx 发生了什么 按照下面的例子npm run dev 举例过程中发生了什么 读取package json 的scripts 对应的脚本命令(dev:vite),vite是个可执行脚本&#xff0c;他的查找规则是&#xff1a; 先从当前项目的node_modules/.bin去查找可执行命令vite如果没找到就去全局的node…

C++模板初阶学习

目录 1.函数模板1.1函数模板概念1.2函数模板格式1.3函数模板的原理1.4函数模板实例化隐式实例化显示实例化 1.5模板参数适配原则 2.类模板2.1类模板的定义格式2.2类模板实例化 总结 1.函数模板 如何实现一个通用的交换函数呢&#xff1f; #include<iostream> using nam…

软件外包开发的JAVA开发框架

Java的开发框架有很多&#xff0c;以下是一些常见的Java开发框架及其特点&#xff0c;每个框架都有其特定的使用场景和优势&#xff0c;开发者可以根据项目的需求选择合适的框架。今天和大家介绍常见的框架及特点&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&…

Day50 算法记录| 动态规划 17(子序列)

这里写目录标题 647. 回文子串516.最长回文子序列总结 647. 回文子串 1.动态规划和2.中心扩展 这个视频是基于上面的视频的代码 方法1:动态规划 布尔类型的dp[i][j]&#xff1a;表示区间范围[i,j] &#xff08;注意是左闭右闭&#xff09;的子串是否是回文子串&#xff0c;如…

面向切面编程(SpringAOP)、通过注解实现AOP代码、AOP的工作流程

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaweb 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 SpringAOP 一、AOP简介1.1连接点&#xff08;JoinPoint&am…

IntelliJ IDEA 2023.1.3 主菜单不见了

通过以下操作&#xff0c;去掉了勾&#xff0c;把主菜单玩没了 然后主菜单找半天都不知道怎么回来&#xff0c;下面记录找回来的过程 双击shift&#xff0c;在弹出的菜单里面搜索 "main menu"&#xff0c;在下图高亮位置选项改为 on

火警智能感知定位 智慧消防大数据可视化监测系统

伴随着城市建设的快速发展&#xff0c;城市消防安全风险的不断上升&#xff0c;城市高层、超高层建筑和大型建筑日益增多&#xff0c;建筑消防安全问题越来越突出。 建设背景 市场背景 近年来&#xff0c;我国多数省级以上城市、90%左右地级以上城市均提出了智慧消防建设计划…

无涯教程-jQuery - width( )方法函数

width()方法获取第一个匹配元素的当前计算像素宽度。 width( ) - 语法 selector.width( ) 此方法能够找到窗口和文档的宽度&#xff0c;如下所示: $(window).width(); //返回浏览器窗口的宽度 $(document).width(); //返回 HTML 文档的宽度 width( ) - 示例 <html&g…

如何用C#实现上位机与下位机之间的Wi-Fi通信?

有IP协议支持的话用UDP报文或者TCP直接发IP地址和端口不行么&#xff1f;你说的WiFi难道是2.4GHz频率模块那种东东&#xff1f; 你既然用了wifi&#xff0c;那么只要上位机和下位机的对应wifi网卡都具有ip地址以及其协议支持&#xff0c;那么和网络编程没啥子明显区别的吧………

pycharm制作柱状图

Bar - Bar_rotate_xaxis_label 解决标签名字过长的问题 from pyecharts import options as opts from pyecharts.charts import Barc (Bar().add_xaxis(["高等数学1&#xff0c;2","C语言程序设计","python程序设计","大数据导论",…

MBA拓展有感-见好就收,还是挑战到底?MBA拓展有感-见好就收,还是挑战到底?

今天看到新闻提到某位坚持了14年高考的同学滑档&#xff0c;让人心生感叹&#xff1a;无论在日常工作还是生活中&#xff0c;选择都是非常重要的。不由想起前段时间我参加研究生新生拓展时的一些感悟&#xff0c;和大家分享一下。 事情的起因是拓展活动中的一个分队竞技类的活…

利用DIE、de4dot和dnspy进行反编译

1.数据准备 1.软件 知云翻译7.0这个版本好破解&#xff0c;该软件是C#写的程序 DIE&#xff08;Detect it Easy&#xff09;该软件是进行查看软件的壳及编译程序&#xff0c;详细了解EXE软件 de4dot是对PE进行脱壳处理&#xff0c;源码下载链接&#xff1a;GitHub - de4dot…

JAVA开发工具-maven的安装与配置(最新最详细教程)

引言 Maven项目对象模型(POM)&#xff0c;可以通过一小段描述信息来管理项目的构建&#xff0c;报告和文档的项目管理工具 软件。 Maven 除了以程序构建能力为特色之外&#xff0c;还提供高级项目管理工具。由于 Maven 的缺省构建规则有较 高的可重用性&#xff0c;所以常常用两…

lc1074.元素和为目标值的子矩阵数量

创建二维前缀和数组 两个for循环&#xff0c;外循环表示子矩阵的左上角&#xff08;x1,y1&#xff09;&#xff0c;内循环表示子矩阵的右下角&#xff08;x2,y2&#xff09; 两个for循环遍历&#xff0c;计算子矩阵的元素总和 四个变量&#xff0c;暴力破解的时间复杂度为O(…

点云处理——terrasolid教程

加载terrasolid软件模块 3、通过microstation的utilities->mdl applications加载terrasolid四个模块,加载成功后将显示tscan和tphoto的主窗口&#xff0c;以及四个模块的主工具箱。 浏览点云 4、显示点云坐标信息(类&#xff0c; 航带号&#xff0c;GPS信息&#xff0c;东…

小夜灯的体势红外传感器 > 红外知识学习

红外是电磁辐射谱中的一部分&#xff0c;它位于可见光谱的红色边缘之外&#xff0c;具有较长的波长。可见光谱是人眼能够感知的电磁辐射范围&#xff0c;而红外光的波长较长&#xff0c;人眼无法感知。 生命光的范围是6~14um 红外光的波长范围一般约为0.7um~1000um&#xff08;…

多模块Springboot项目maven单独打包子模块

背景介绍 最近接手一个项目代号XXL&#xff0c;是一个多模块的Springboot项目&#xff0c;在解决了线上的bug之后&#xff0c;想单独给子模块打包上线部署&#xff0c;问题来了。如果整个工程一起mvn -X -DskipTests clean package&#xff0c;打包出来的XXL-web.jar是可以正常…

打通数据治理全链路,火山引擎DataLeap数据治理平台公有云版本正式发布

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 近日&#xff0c;火山引擎DataLeap正式对外发布数据治理平台公有云版。DataLeap是火山引擎大数据研发治理套件&#xff0c;随着其子套件数据治理平台与CDH引擎底座成…

等待已久,新品上市 | RevPi Connect 4系列:基于树莓派CM4计算模块的全新工业树莓派

新品来袭 势不可挡 备受期待的虹科工业树莓派第四代产品—RevPi Connect 4终于来啦&#xff01;作为全球领先的工业自动化产品&#xff0c;RevPi Connect 4融合了工业树莓派多年技术积累与创新突破&#xff0c;以及现代物联网技术的结晶。无论您是行业领先者、工程师还是智能科…