【数据结构】排序:插入排序与希尔排序详解

news2024/11/16 1:30:58

本章开始就要分享一些常用的排序方法,我们的日常生活中很多地方都要使用排序,比如电商平台可以按照你的需求进行排序,或者是你想了解大学的综合排名时

 

 我们之前也学到过一些简单的排序比如冒泡排序,虽然他在时间复杂度上可以说是依托答辩,但是作为排序算法来讲还是非常有教学意义的,让更多的人可以了解到排序。

那首先分享两种排序算法,简单了解一下排序,以下是本章目录;

目录

1.直接插入排序

2.希尔排序

2.1对gap的解释


1.直接插入排序

玩儿斗地主时,我们需要将我们的牌排成有序的,例如对子、顺子,以便于我们出牌。

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

用上面这副图说就是我们手里已经有了2、4、5、10,当我们拿到7的时候我们需要对他的前后进行对比并且找到合适的位置进行插入。

为了便于理解以下先是单趟的插入排序

void InserSort(int* a, int n)
{
	//[0,end]有序,插入tmp后依然有序
	int tmp;
	int end;
	while (end >= 0)
	{
		if (a[end] > tmp)
		{
			a[end + 1] = a[end];
			--end;
		}
		else
		{
			break;
		}
	}
	a[end + 1] = tmp;
}

以上代码中tmp可以理解为我们插入的数据,大致意思就是当我们想要插入的数比最后一个数小的时候,就放在最后一个数的前面,并且将数组中的数字整体向后移动;但是当其他情况的时就可以直接插入。

下图是当我们插入1的时候要和数组内容进行比较,最后将1插入到了最前面,图示方便大家理解。

注意将a[end+1]=tmp;写在括号外是为了避免数组为空的问题;

以上是单趟排序的理解,我们加上for语句来规定他的循环次数即可完成多趟排序

void InserSort(int* a, int n)
{
	//[0,end]有序,插入tmp后依然有序
	for (int i = 1; i <= n; i++)
	{
		int tmp = a[i];
		int end = i - 1;
		while (end >= 0)
		{
			if (a[end] > tmp)
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
} 

所所以不难看出他最坏的情况下的时间复杂度为O(N^2) 

那不就和我们之前所学的冒泡排序的时间复杂度一样了吗?并不是这样。

冒泡排序的思想是相邻两个元素进行比较在进行交换,而且一趟排序只能确定一个数的位置,所以无论是否有序,他的时间复杂度都是O(n^2);

而插入排序在最好的情况下,也就是数组为升序的情况下时间复杂度为O(n),省去了两两交换的过程,当然前提是数组为升序

以上就是插入排序的内容

2.希尔排序
 

希尔排序其实是对插入排序的一种优化,大体思想就是在插入排序之前加上了与排序的步骤,所以他的思路就是1.预排序,使这个数组接近有序(注意是接近有序而不是有序)2.插入排序。

这个预排序的意思就是先将数组分组,固定一个gap,使数组间隔为gap分成一组,总共有gap组。

我们用一段数组来举例

我们假设gap为3

 如上图就分成了一组,接下来继续分组

 

 

 

可以看到gap为三时,红色线标注的9,6,3,1为一组数;蓝色线标注的8,5,3,0为一组数,最后用绿色线标注的7,4,2为一组数;

接下来就要对gap组的数据分别插入排序(升序);

首先是对第一组的数据插入排序成升序,如下图;后面几组数据以此类推

红色组的数据

蓝色组的数据

 

 

绿色组的数据 

 

 到这里我们会发现这样预排序之后让数组接近有序,这样就达到了预排序的效果;

最后我们再对预排序后的数据进行插入排序,这样效率就会提高很多。

回头再看这组数据,如果想让他排成升序,但是这组数据确实降序,无疑在时间复杂度上一定是最坏的结果,所以时间复杂的度为O(n^2);

但是使用了希尔排序的预处理之后,让数组接近升序,这样就会在时间复杂度上回优化不少;

为了方便理解,我们先排列第一组数据(以下代码为错误代码示范);

void ShellSort(int* a, int n)
{
	int gap = 3;
	for (int i = 0; i < n; i += gap)
	{
		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;
	}
	
}

以上就是排完一个gap组的数据 

看完以上代码不知到是否想到了插入排序的思想

 是的,预处理只是将数组之间比较的间隔拉大了一些,插入排序是后一个数向前插入,和自己的前一个数进行比较;而希尔排序的预处理阶段也是后一个数向前插入,只不过是和自己的前gap个数进行比较;简单来说就是插入排序只走一步,而希尔排序走的是规定的gap步。

但是观察上面的代码我们会发现当i走到end的位置时,继续往下走就会越界

 所以我们还需要将for循环中的循环条件改为i<n-gap;

void ShellSort(int* a, int n)
{
	int gap = 3;
	for (int i = 0; i < n-gap; i += gap)
	{
		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;
	}
	
}

这样才算是真正的将第一组的数据,也就是红色的线连接的9,6,3,1排成有序的1,3,6,9;

排完一组数据我们还需要将第二组蓝色线的数据和第三组绿色线数据都排成有序;

上面的思想我们也讲过只需控制下标end就可以达到排序不同组数据的效果,所以我们不妨再套一个for循环来解决问题;

void ShellSort(int* a, int n)
{
	int gap = 3;
	for (int j = 0; j < gap; j++)//循环插入每组
	{
		for (int i = j; i < n; i += gap)//每组数据排序(升序)
		{
			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;
		}
	}
	
	
}

这样以来我们就解决了要排列多组数据的问题;

ok说了那么多我们写个测试样例验证一下我们的结果

 可以看到我们的降序数组在希尔的预处理中已经变得近似有序

接下来要说是到现在位置已经有了三层循环,所以我们可以对其进行优化,以下是优化的代码

void ShellSort(int* a, int n)
{
		int gap = 3;
		for (int i = 0; i < n - 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;
		}


}

首先我们对比两种写法

 

 其实对比两种写法,第一种会更利于理解一些,因为第一种的思路是一组一排,就是第一组数据排完排第二组,第二组排完派第三组以此类推,按照图示来说就是先排红色在排蓝色在排绿色的数据。

 

而第二种写法的思路是多组并排,也就是控制通过控制  i 来控制end, 也就是分别将每一组的第一个数据排好再去排每组的第二个数据,再去排每组的第三个数据以此类推,照图示来说就是先排1,再排0,再排2,在排3,3,4,6,5…………

 其实两者的时间复杂度是一样的,效率上并没有提升,只是在理解上更巧一点。

2.1对gap的解释

在上面的代码中为了测试方便一些于是自己写了一个简单的测试用例,并规定了gap为3;

那gap一定为3吗?其实根据上面的希尔排序对比插入排序中,希尔排序跳的更快了,当后一个数比前一个数小的时候,他不是像插入排序一样向前一位交换数据,而是向前gap位交换数据;

他的目的是在预处理中,将小的数更快的放到数组的前面,大的数更快的放到数组的后面而使数组尽量有序。

当然gap也并非越大越好,因为gap越大,越不接近有序

当我们将gap改成5时,我们会发现没有gap=3时有序;

 我们再将gap换成1

 我们就会发现数组就会排成升序了,这就和之前的插入排序一样了

所以我们总结出

gap越大,大的数可以更快到后面,小的数可以更快到前面,越不接近有序;

gap越小,大的数和小的数移动越慢,但是会更接近有序;

gap==1时,就会直接变成插入排序;

 所以我们规定在使用希尔排序时将他的gap设置为  gap/3+1 ,因为要保证gap在数据很少的情况下也是int类型。

以下是规定gap后的代码

void ShellSort(int* a, int n)
{
		int gap = n;
		while (gap > 1)
		{
			gap = gap / 3 + 1;

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

 接下来在对他进行一次插入排序就可以使数组有序了。

以上就是有关插入排序和希尔排序内容的分享,如果对你有所帮助还请三连支持,感谢您的阅读。

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

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

相关文章

归并排序(思路+代码)

变量&#xff1a; left、right、privot、temp[]、leftIndex、k 思路&#xff1a; 代码&#xff1a; import java.util.Arrays;public class Queue8 {public static void main(String[] args) {int[] arr {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};sort(arr,0,arr.length-1);System.ou…

AST-抽象语法树

js加密解混淆首先想到的是AST语法树&#xff0c;那么什么是AST呢&#xff0c;学习AST过程的一些笔记 1.AST是JS执行的第一步是读取 js 文件中的字符流&#xff0c;然后通过词法分析生成令牌流Tokens&#xff0c;之后再通过语法分析生成 AST&#xff08;Abstract Syntax Tree&a…

3D 旋转木马

在工作中我们常用到3D装换和3D位移 主要知识点 3D位移&#xff1a;transale3d(x,y,z)3D旋转&#xff1a;rotate3d(x,y,z)透视&#xff1a;perspective3D呈现 transfrom-style 1、 transale3d translform: translform:translateX(100px):仅仅是在x轴上移动translform:transl…

[NOI2014] 随机数生成器(模拟+贪心)

题面 [NOI2014] 随机数生成器 - 洛谷 题解 缝合题 第一部分&#xff0c;直接模拟题目操作生成二维数组即可&#xff0c;复杂度O(n*mQ) 第二部分&#xff0c;是一个比较经典的字典序贪心 首先肯定需要将最小的数放到路径上&#xff0c;这样可选的剩下的数就被限制在了最小数…

Redis 管道

问题由来&#xff1a;如何优化频繁命令往返造成的性能瓶颈&#xff1f; Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。 一个请求会遵循以下步骤&#xff1a; 1、客户端向服务端发送命令分四步(发送命令→命令排队→命令执行→返回结果)&#xff0c;并监听S…

Codeforces Round 883 (Div. 3) A~G

比赛链接&#xff1a;Dashboard - Codeforces Round 883 (Div. 3) - Codeforces 目录 A. Rudolph and Cut the Rope B. Rudolph and Tic-Tac-Toe C. Rudolf and the Another Competition D. Rudolph and Christmas Tree E. Rudolf and Snowflakes F. Rudolph and Mimic…

JavaWeb项目(包含SSM项目)部署到Linux云服务器

目录 一、云服务器环境部署 1、安装JDK 查看JDK的命令为&#xff1a; 安装JDK命令&#xff1a; 2、安装Tomcat 2.1 安装步骤 2.2 验证Tomcat是否启动成功 3、安装MySQL 二、部署 Web 项目到 Linux 2.1 在云服务器中数据库建库建表 2.2 修改部署项目连接数据库密码 …

Qt(Day2)

实现登录框中&#xff0c;当登录成功时&#xff0c;关闭登录界面&#xff0c;并跳转到其他界面&#xff1a;

Go实现在线词典翻译(三种翻译接口,结合sync)

火山翻译 首先介绍用火山翻译英译汉。 package mainimport ("bufio""bytes""encoding/json""fmt""io""log""net/http""os""strings""unicode" )type DictRequestHS st…

第四章:角色和菜单管理功能【基于Servlet+JSP的图书管理系统】

角色和菜单功能 一、角色功能 接下来我们可以完成角色管理的增删改查操作 1. Bean对象 创建sys_role对应的实体对象SysRole Data public class SysRole {private Integer id;private String name;private String notes;private Date createTime; }2. Dao层 现在我们就可以在D…

JVM(Java虚拟机)详解

目录 一、JVM内存区域划分 1. 什么是内存区域划分以及为啥要进行区域划分 2. JVM内存区域划分详解 3. 堆区详解&#xff1a; 4. 给一段代码&#xff0c;问某个变量是在那个区域上&#xff1f; 二、JVM类加载机制 1.类加载的过程 2. 类加载的时机 3. 双亲委派模型&#xff08…

下班前几分钟,我彻底玩懂了tmux

目录 1. tmux简介2. Session3. Window4. Pane5. 自定义tmux配置6. 在shell脚本中操纵tmuxReferences 1. tmux简介 tmux&#xff08;terminal multiplexer&#xff09;是一个非常强大的工具&#xff0c;主要有以下几点功能&#xff1a; 终端复用&#xff1a; tmux 使你能够在一…

Linux分布式应用 Zabbix监控配置[添加主机 自定义监控内容 邮件报警 自动发现/注册 代理服务器 高可用集群]

-------------------- 添加 zabbix 客户端主机 -------------------- 关闭防火墙 systemctl disable --now firewalld setenforce 0 hostnamectl set-hostname zbx-agent01 服务端和客户端都配置时间同步 yum install -y ntpdate ntpdate -u ntp.aliyun.com 服务端和客户端都设…

基于simulink跟踪火车站对象检测遗弃物体(附源码)

一、前言 此示例演示如何跟踪火车站的对象并确定哪些对象保持静止。公共场所的遗弃物品会引起当局的关注&#xff0c;因为它们可能会构成安全风险。算法&#xff08;例如本例中使用的算法&#xff09;可用于通过将他们的注意力引导到潜在的感兴趣区域来协助监控实时监控视频的…

二十五、传输层协议(上)

文章目录 一、再谈端口号&#xff08;一&#xff09;端口号定义&#xff08;二&#xff09;端口号范围划分1.一共有 2^16 个端口2.认识知名端口号(Well-Know Port Number)3.端口号和进程就是K-V关系4.netstat&#xff08;1&#xff09;示例1&#xff1a; n 拒绝显示别名&#x…

CentOS Linux上安装JDK11、MySQL8.0、Minio等软件(rpm脚本模式)

本地环境&#xff1a;Windows 10家庭版 16G内存 512G硬盘 软件&#xff1a;VMWare WorkStation 16.0 FinalShell 4.0.1 一、下载必要软件包 下载软件均选择x86架构64位&#xff01;&#xff01;&#xff01;&#xff08;可根据自己的电脑配置选择&#xff09; CentOS Linu…

基础算法-前缀和

1 算法笔记 2.代码示例 3.代码解析 #include<iostream> using namespace std; const int maxn 1010000; int a[maxn],s[maxn];//a数组是用来存放数组的&#xff0c;s是用来存放前n项数组的和 int m,n;int main(){scanf("%d%d",&n,&m);for(int i1;i&l…

【react】插件react-tsparticles和tsparticles实现粒子特效:

文章目录 一、效果图:二、实现思路:三、实现代码:【1】安装依赖【2】 一、效果图: 二、实现思路: particles&#xff08;npm i react-particles-js&#xff09;目前已被弃用&#xff1b;取代它的是tsparticles&#xff08;npm i react-tsparticles 和npm install tsparticles&a…

docker 安装应用

前文介绍&#xff1a;我们再阿里云领取的ECS服务器&#xff0c;服务器选择应用模板&#xff0c;他会自动帮我们的服务器安装docker的一些相关插件。如果没有&#xff0c;则需要自己安装docker docker 安装Jenkins 1.下载Jenkins镜像 推荐版本 jenkins/jenkins:lts 命令&…