数据结构与算法:插入排序希尔排序

数据结构与算法:插入排序&希尔排序

    • 插入排序
    • 希尔排序


插入排序

假设现在你有一个有序的数组,你要把一个数据插入到数组中,保证插入后依然有序,要怎么做?

对于人来说,这个问题就像是在整理扑克牌,瞄一眼就知道应该插入什么位置。但是对于程序来说,就需要一一对比,直到找到一个位置左边比它大,右边比它小,就算找到了一合适的位置插入。

而插入排序就是基于这样的一个过程完成的排序。

比如下面这个数组,其左边是有序的,右边是无序的。
在这里插入图片描述

我们只需要将第一个无序的元素进行插入,向右一一对比,就可以找到合适的位置。
请添加图片描述
那么如果一个数组完全无序,要如何利用这个插入的思想呢?
比如对于以下数组:
在这里插入图片描述
我们可以将其视为:第一个元素有序,插入第二个元素:
在这里插入图片描述
当插入第二个元素,前两个元素就是有序的,然后插入第三个元素;接着前三个元素就是有序的,插入第四个元素…以此类推
最后前n-1个元素是有序的,第n个元素是无序的,插入第n个元素,最后整个数组都是有序的了。

过程如下:请添加图片描述
代码实现:

// 插入排序 
void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		//设前[0, end]是一个有序数列,将end+1插入
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}

		a[end + 1] = tmp;
	}
}

具体解释如下:

  1. 函数定义了一个名为InsertSort的函数,接收两个参数:一个整型数组a和一个整型变量n,表示数组的元素个数。

  1. 使用一个循环遍历数组a,循环变量i从0开始,每次循环递增1,直到i大于或等于n-1

  1. 在每次循环中,将数组的第i个元素设为待插入的元素,即end+1处的元素。

  1. 定义一个整型变量end,初始值为i,用于表示有序数列的末尾位置

  1. end+1处的元素暂存到变量tmp中。

  1. 使用一个循环将end+1处的元素插入到有序数列中。循环条件是end大于等于0,也就是当end等于0时,说明已经遍历到了有序数列的开头

  1. 在循环内部,判断tmpa[end]的大小关系。如果tmp小于a[end],则将a[end]后移一位,end递减1。这样,tmp大的元素就会向右移动一位,为tmp腾出插入的位置

  1. 如果tmp大于或等于a[end],则说明tmp应该插入到end+1的位置。同时跳出循环。

  1. 在跳出循环后,将tmp插入到end+1的位置,即a[end+1] = tmp

  1. 重复步骤2~9,直到遍历完整个数组a,排序完成。

希尔排序

希尔排序(Shell Sort)是插入排序的一种,它是针对直接插入排序算法的改进。
希尔排序又称缩小增量排序,因 DL.Shell 于 1959 年提出而得名。

假设我们现在有一个完全逆序的数组,如果此时使用插入排序对其排序,那么第二个元素就要和第一个元素交换,第三个元素要和前两个全部交换一一次…第n个元素要和前n-1个元素交换一次,这不是把时间复杂度拉满了吗。

于是有人就开始考虑要如何处理这种逆序环境下插入排序要全部交换一次的问题。
插入排序在这种情况下排序效率低的原因在于,当一个比较小的元素处于后面时,其要和前面的所有比他大的元素对比一次,比如这样:
在这里插入图片描述
这个8想要到达正确位置,就要和前面所有的元素进行一次比大小,这会造成很多的计算量,想要优化这个问题,其实就是解决:如何让比较小的元素快速到达数组的前面?

于是希尔排序诞生了,我们可以将一个数组分为分为多组,对每一组单独进行一次插入排序,比如这样:
在这里插入图片描述
这是一个8个数字的数组,我们将其分为4份,每两个数字一组,上面每种颜色为一组,然后让他们进行一次插入排序。排序后,虽然数组依然无序,但是整体上来说,前面的数字比较小,后面的数字比较大,通过这种方法,可以将后面的小元素快速提到前面。

我们再利用一个较大的数据看一次:
在这里插入图片描述
我们现在对这个数组分区,分为两份,左边是一份,右边是一份。其中左边的第一个元素和右边的第一个元素是一组,左边的第二个元素和右边的第二个元素是一组…左边的最后一个元素和右边的最后一个元素是一组。
这样我们就把这个数组分为了两两一组,然后每两个元素进行一次插入排序:

请添加图片描述

这样一趟排序后,我们看看数组的状态:
在这里插入图片描述
整体上来说,左边的数据要小于右边的数据,这不就达成了我们想要的效果:让比较小的元素快速到达数组的前面。这个过程,每个数据都只进行了一次交换,就到达了比较前的位置。这个过程叫做预排序

希尔排序是基于插入排序的算法,通过分组对元素进行插入排序来提高效率。

上述过程的代码如下:

int gap = n / 2;

for (int i = 0; i < n - gap; i++)
{
	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,其值为数组的长度 n 除以 2。gap 的值决定了分组的大小,可以根据具体情况进行调整。

然后,使用一个循环遍历数组 a,从前往后依次处理每个元素。循环的结束条件是 i < n - gap,即遍历到倒数第 gap 个元素时结束。

在循环内部,首先定义一个变量 end,并将其赋值为当前遍历的元素下标 i。然后,定义一个变量 tmp,并将其赋值为 a[end + gap],即当前元素所在分组的下一个元素。

接下来,使用一个循环将当前元素插入到正确的位置。循环的条件是 end >= 0,即遍历到数组的起始位置时结束。在循环内部,做如下操作:

  • 如果 tmp 的值小于当前位置的元素 a[end],则将 a[end] 的值赋给 a[end + gap],并将 end 的值减去 gap
  • 如果 tmp 的值大于等于当前位置的元素 a[end],则退出循环。

最后,将 tmp 的值赋给最终位置上的元素,即 a[end + gap]

但是这样还不是一个完整的希尔排序,设想一下,如果我们的数据量很庞大,是不是要多进行几次预排序才比较好?这样小的数据才更可能再数组的前面。
所以我们的gap也是动态变化的,在希尔排序中,一开始我们两两一组进行了插入排序,我们可以再进行4个一组插入排序,接着8个一组插入排序…以此类推,直到n个一组,将整个数组作为一组是,也就是最后一趟插入排序,这时候数组已经十分接近有序,最后对整个数组进行一次插入排序,数组就有序了。

过程如下:
请添加图片描述
经过这一次每四个一组的排序,我们的数组状态如下:
在这里插入图片描述
可以看到,数组整体呈阶梯递增趋势了。

接着进行后续排序:
请添加图片描述
可以发现,数组越来越接近有序,最后一趟排序,几乎每个元素都没有移动几次就插入到了目标位置,这就是希尔排序的完整推导思路。

外层控制gap的代码如下:

// 希尔排序
void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 2;

		//插入排序
	}
}

在这段代码中,首先定义了一个变量gap,它表示初始的增量,也即是划分的分组的大小。初始化时,gap的值为待排序数组的大小n

然后,代码进入一个while循环,循环条件是gap > 1,也就是说当gap为1时,循环结束。在每一轮循环中,gap的值减半,并更新为新的增量。这样,希尔排序就是通过不断缩小增量的大小来进行排序。

在每一轮循环中,代码执行了一个"插入排序"的操作。

最终,当增量gap减小到1时,希尔排序的整个过程结束,待排序数组完成排序。

总代码如下:

// 希尔排序
void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 2;

		for (int i = 0; i < n - gap; i++)
		{
			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;
		}
	}
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第一个 OpenGL 程序:旋转的立方体(VS2022 / MFC)

文章目录 OpenGL API开发环境在 MFC 中使用 OpenGL初始化 OpenGL绘制图形重置视口大小 创建 MFC 对话框项目添加 OpenGL 头文件和库文件初始化 OpenGL画一个正方形OpenGL 坐标系改变默认颜色 重置视口大小绘制立方体使用箭头按键旋转立方体深度测试添加纹理应用纹理换一个纹理 …

0104 AJAX介绍

Ajax 的全称是 Asynchronous Javascript And XML &#xff08;异步 JavaScript 和 XML &#xff09;。 通俗的理解&#xff1a;在网页中利用 XMLHttpRequest 对象和服务器进行数据交互的方式&#xff0c;就是 Ajax Ajax 能让我们轻松实现网页与服务器之间的数据交互。 浏览器…

基础篇_数据持久化(实战-我的B站,MySQL数据库)

文章目录 一. 实战-我的B站1. 功能演示2. 设计数据类数据展示路径参数 3. 设计 Service 类静态资源映射读取文件的时机Stream API 改进 二. MySQL 数据库1. 数据库必要性2. MySQL 安装下载压缩包初始化数据库运行服务器运行客户端 3. 初步使用4. datagrip添加数据源导入数据用 …

【网络安全】【密码学】【北京航空航天大学】实验四、古典密码(上)【C语言实现】

实验四、古典密码&#xff08;上&#xff09; 一、实验目的 1、 通过本次实验&#xff0c;了解古典加密算法的主要思想&#xff0c;掌握常见的古典密码。 2、 学会应用古典密码&#xff0c;掌握针对部分古典密码的破译方法。 二、原理简介 古典密码的编码方法主要有两种&am…

【深度学习环境搭建】Windows搭建Anaconda3、已经Pytorch的GPU版本

目录 搭建Anaconda3搭建GPU版本的Pytorch你的pip也要换源&#xff0c;推荐阿里源打开conda的PowerShell验证 搭建Anaconda3 无脑下载安装包安装&#xff08;自行百度&#xff09; 注意点&#xff1a; 1、用户目录下的.condarc需要配置&#xff08;自定义环境的地址&#xff08…

【TC3xx芯片】TC3xx芯片电源管理系统PMS详解

目录 前言 正文 1.供电模式选择&#xff08;Supply Mode Selection&#xff09; 1.1 供电域 1.2 供电模式 1.3 供电阈值 1.4 供电上升和下降行为Supply Ramp-up and Ramp-down Behavior 1.5 EVRC产生供电 2. 电源监控 2.1 电源监控原理 2.2 Primary低电压监控 2.3 …

怎么把身份证压缩到200k以下?一分钟教你如图片压缩

在网络平台办理一些业务的时候&#xff0c;经常会需要上传我们的身份证照片&#xff0c;但是大多数平台为了用户体验&#xff0c;会限制上传的图片大小&#xff0c;比如图片不得超过200kb&#xff0c;当我们提交的身份证图片超出限制&#xff0c;就无法顺利提交&#xff1b;这时…

【Docker篇】使用Docker操作镜像

文章目录 &#x1f6f8;镜像&#x1f33a;基本操作⭐docker --help⭐docker pull [ 参数 ]⭐docker images⭐docker save -- 导出⭐docker rmi -- 删除⭐docker load -- 导入 &#x1f6f8;镜像 镜像是指在计算机领域中&#xff0c;通过复制和创建一个与原始对象相似的副本的过…

微信小程序快速入门02(含案例)

&#x1f3e1;浩泽学编程&#xff1a;个人主页 &#x1f525; 推荐专栏&#xff1a;《深入浅出SpringBoot》《java项目分享》 《RabbitMQ》《Spring》《SpringMVC》 &#x1f6f8;学无止境&#xff0c;不骄不躁&#xff0c;知行合一 文章目录 前言一、页面导航1.…

精确掌控并发:分布式环境下并发流量控制的设计与实现(一)

这是《百图解码支付系统设计与实现》专栏系列文章中的第&#xff08;10&#xff09;篇。 本篇主要讲清楚常用的并发流量控制方案&#xff0c;包括固定窗口、滑动窗口、漏桶、令牌桶、分布式消息中间件等&#xff0c;以及各种方案在支付系统不同场景下的应用。 在非支付场景&a…

若依实现前段后登录密码加密

若依虽然有加密解密功能&#xff0c;然后只有前端有&#xff0c;在用户点击保存密码的时候&#xff0c;会将密码保存到本地&#xff0c;但是为了防止密码泄露&#xff0c;所以在保存的时候&#xff0c;进行加密&#xff0c;在回显密码的时候进行解密显示&#xff0c;用户在登录…

上门回收小程序开发,让回收更加简单

资源回收一直是当下深受大众关注的话题&#xff0c;如何做到资源不浪费&#xff0c;成为了大众要考虑的问题。在人们环保意识的加深下&#xff0c;回收行业也是获得了大众的关注&#xff0c;逐渐形成了一个新的商业模式。 随着互联网技术的发展&#xff0c;回收行业也更加方便…

乱码问题汇总

写在前面 在工作中经常会碰到各种莫名其妙的乱码问题&#xff0c;但通过之前的学习&#xff1a;字符集&字符编码-CSDN博客 &#xff0c;可以知道乱码的根本原因就是使用和数据源编码不一样的编码解码导致。 如&#xff1a;BIG5解码GB2312编码内容&#xff0c;编解码不一致…

C++学习笔记(三十五):c++ 函数指针及lambda表达式

本节介绍c函数指针。在一些源码中经常能看到c函数指针&#xff0c;但之前一直觉着这一块比较复杂&#xff0c;就一直没去仔细研究&#xff0c;终于有时间去仔细研究这一块内容了。 c风格的函数指针 函数指针是指将一个函数赋值给一个变量的方法&#xff0c;可以将函数作为一个参…

Oracle篇—实例中和name相关参数的区别和作用

☘️博主介绍☘️&#xff1a; ✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ ✌✌️擅长Oracle、MySQL、SQLserver、Linux&#xff0c;也在积极的扩展IT方向的其他知识面✌✌️ ❣️❣️❣️大佬们都喜欢静静的看文章&#xff0c;并且也会默默的点赞收藏加关注❣…

可以在微信群里使用midjourney,gpt4,gemini,文心一言4.0,且免费

免费使用gpt4和midjourney 免费使用 参考链接&#xff1a; https://chat.xutongbao.top/

(核心变量)全国上市公司对外开放程度+dofile+参考文献(2000-2022年)

上市公司的对外开放程度数据反映了这些公司在国际市场上的活跃度和全球化程度。这包括了它们的国际贸易参与度、跨国投资和合作、国际市场的营销和品牌推广策略&#xff0c;以及在不同国家和地区的业务布局。此外&#xff0c;这段时间内不同行业和公司的对外开放程度可能有明显…

IDEA新建SpringBoot工程时java版本只有17和21

解决方法&#xff1a;替换源 参考博客&#xff1a;https://www.kuazhi.com/post/712799571.html

VirtualBox安装linuxmint-21.2虚拟机并配置网络

VirtualBox安装linuxmint-21.2虚拟机并配置网络 适用于在VirtualBox平台上安装linuxmint-21.2虚拟机。 1. 安装准备 1.1 安装平台 Windows 11 1.2. 软件信息 软件名称软件版本安装路径Oracle VM VirtualBoxVirtualBox-7.0.12-159484D:\softwareCentOS7CentOS-7.9.2009E:\…

4点优势,昂首资本使用浮动差价不使用固定差价的原因

在交易中&#xff0c;很多投资者和昂首资本一样&#xff0c;会使用浮动点差而不使用固定点差&#xff0c;那是因为投资者和昂首资本一样认为&#xff0c;使用浮动差价交易会比使用固定价差交易更有优势。 首先在大部分交易时段&#xff0c;价差缩小。正如投资者和昂首资本所知…