DS八大排序之直接插入排序和希尔排序

前言

我们前面几期介绍了线性和非线性的基本数据结构。例如顺序表、链表、栈和队列、二叉树等~!本期和接下来的几期我们来详解介绍各个排序的概念、实现以及性能分析!

本期内容

排序的概念以及其运用

常见的排序算法

直接插入排序

希尔排序

一、排序的概念及其运用

排序的概念

排序:按照一定的规则,把一组元素序列以递增或递减排列起来的操作!

稳定性:假设在待排序的一组序列中有多个相同的元素,若经过排序,这些元素的相对位置(相对次序)是不变的,则称为这种排序是稳定的。否则就是不稳定的!

内部排序:数据元素全部放在内存中的排序

外部排序:数据元素太多不能同时在内存中,根据排序的过程要求不能在内存和外存之间移动数据的排序!

关于哪些是内部排序、哪些是外部排序后面性能分析会在介绍!

排序的运用

排序在日常生活中是极其常见的:例如你每天看的京东、淘宝、拼夕夕等购物平台上的综合、销量、好评、价格、等一系列都是对商品的排序!

还有高校排名:

还有大家考试的时候的排名,这些都是排序~!排序在生活只能是很常见的!!!

常见的排序算法

二、直接插入排序及其实现

插入排序的基本思想

待排序的元素序列按照大小逐个插入到已经排序好的有序序列中,直到所有待排序的元素插入完为止, 而此时得到的新的序列就是有序的序列!

这就和我们小时候玩扑克牌摸牌整理的一样,一次与前面的排比较找到合适的位置插入!

直接插入排序

第i个元素插入时(i >= 1)前面的序列已经有序的, 此时只需要用array[i]的元素与前面的所有元素逐一比较前面的元素比当前的前面的元素插入到当前位置(升序),否则当前元素插入到前面元素的后面!

我们根据上述思路写单趟改造整体

	int end = 0;//一开始end置0位置
	int tmp = a[end + 1];//tmp是end 的下一个位置的元素
	while (end >= 0)
	{
		if (a[end] > tmp)//前面元素比当前的元素大
		{
			a[end + 1] = a[end];//前面元素的插入到前面元素的后一个位置
		}
		else//前面元素不比当前的元素大
		{
			a[end + 1] = tmp;//当前元素插入到前面元素的下一个位置
			break;//记得结束否则会又把排好的区间搞乱
		}

		--end;
	}

	if (end < 0)//所有的都比tmp大,此时end一直减会减到-1
	{
		a[0] = tmp;//此时把tmp(end的下一个位置的元素)插入到0下标位置
	}

整体:

其实单趟写出来,改造整体就会很容易,我们在外面在套一层循环即可!让end从i 开始,每个元素与前面的逐一比较走单趟,直至最后有序!

void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;//一开始end置0位置
		int tmp = a[end + 1];//tmp是end 的下一个位置的元素
		while (end >= 0)
		{
			if (a[end] > tmp)//前面元素比当前的元素大
			{
				a[end + 1] = a[end];//前面元素的插入到前面元素的后一个位置
			}
			else//前面元素不比当前的元素大
			{
				a[end + 1] = tmp;//当前元素插入到前面元素的下一个位置
				break;//记得结束否则会又把排好的区间搞乱
			}

			--end;
		}

		if (end < 0)//所有的都比tmp大,此时end一直减会减到-1
		{
			a[0] = tmp;//此时把tmp(end的下一个位置的元素)插入到0下标位置
		}
	}
}

但要注意的是:外面的for循环的判断条件,i < n - 1, 也就是说i最多走到n - 2的位置即倒数第二个元素!原因是:tmp才是每次要插入的元素,而tmp = a[end +1]是end(i)的下一个位置,如果让i 到最后一个元素的位置即n-1处,那tmp = a[end+1]就会越界!!!所以i 只能到倒数第二个元素的位置!

OK, 直接插入写完了,测试一下:

这样写是没问题,但感觉稍微有点挫~!我们在当前元素不大于前面元素的时候要判断一次,是否越界也要判断一次。

我们能不能想办法给优化一下呢?答案是肯定的!我们无论是判断当前的元素不比前面的大还是越界最后插入的都是end+1的位置,所以当在单趟的循环(while)内一旦不满足当前的比前面的大则立刻跳出当前循环。到了单趟循环外有可能是break结束的,也有可能是end < 0结束的,但我们根本不需要关心他,直接插入到end+1的位置即可~!

void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (a[end] > tmp)
			{
				a[end + 1] = a[end];
			}
			else
			{
				break;
			}

			--end;
		}

		a[end + 1] = tmp;
	}
}

这样写简洁了不少吗,也不容易出错了~!我以前学的时候老是把上面第一种的break给忘了,最后找半天(Q^Q)....所以建议大家一般写第二种~!

复杂度分析

时间复杂度:O(N^2)  ---> 单趟是O(N),最坏情况N个元素都要走一次单趟

空间复杂度:O(1)  ---> 额外使用空间的个数是常数个

当要排序的序列接近有序时性能最好O(N)~!

OK, 我们直接插入排序就介绍到这里,下面我们来介绍一下它的优化版 ----- 希尔排序!!

三、希尔排序及其实现

我们上面介绍过直接插入的时间复杂度是O(N^2),它的性能一般~!有一天一位叫D.L.Shell的大佬去学习了直接插入排序后,想既然你直接插入排序在接近有序的情况下性能很好(O(N)),那我能不能把一组无序的元素经过处理先让他变得接近有序然后再来一趟直接插入呢?其实他这种想法直接把直接插入排序优化到几乎能和快排平起平坐了~!就是下面这位大佬:

OK,我们来看看大佬的具体思路!

希尔排序的思路

1、进行多组预排序

2、最后再来一次直接插入

什么意思呢?我来解释一下:这里的多组预排是:

先选定一个整数增量gap(一开始gap = n / 2),将数组的元素分为gap 组,将每个距离为gap的分为一组,并对每个小组进行距离为gap的"直接插排",然后不断的缩小gap(gap /= 2),重复上述操作,直到gap == 1时就说明各个组的都已经排好,此时相较于一开始已经是非常有序的了~!最后我们再来一趟直接插入排序则该序列就是有序的了!

OK, 画个图理解一下:

这就将一个完整的数组分成了gap 组,此时的gap 是 5,我们走一下预排序的一个gap:

他这样走下去,gap最后一定会到gap == 1,当gap == 1那就是直接插排了:

上述栗子可以清楚的看到当gap == 1时,未开始最后一次直接插排前的数组已经是很有序了,当经过最后一趟直接插入排序后就是有序了~!

希尔排序的实现

还是先单趟,再整体~!

单趟

每一组的单趟

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];
		}
		else
		{
			break;
		}

		end -= gap;
	}

	a[end + gap] = tmp;
}

注意的是这里是n - gap 而不是n - gap - 1

这是一组的单趟,这里和直接插入排序几乎一样,当gap == 1就是直接插排~!此时有多少组,就走多少组这样的单趟,所以在外面在套一层循环才是所有组的单趟~!

所有组的单趟

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

			end -= gap;
		}

		a[end + gap] = tmp;
	}
}

这就是所有的预排序的单趟,那他到底走多少趟预排呢?具体不知道,当gap最后通过调整到(gap /= 2或gap = gap / 3 + 1) gap == 1即可!所以总体应该在外面套个循环:

void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap /= 2;
		for (int i = 0; i < gap; i++)
		{
			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];
					}
					else
					{
						break;
					}

					end -= gap;
				}

				a[end + gap] = tmp;
			}
		}
	}
}

gap 一开始是n,然后开始执行第一趟时时gap /= 2;然后每次调整都是/=2,最后一次进入循环一定是1(n, n / 2, n / 4, n / 8, n / 16, n /32 ..... 4, 2, 1),即直接插入排序了~!

OK,测试一下:

没问题!但这段代码被另一位大佬进行过优化,如下:

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];
				}
				else
				{
					break;
				}

				end -= gap;
			}

			a[end + gap] = tmp;
		}
	}
}

他这里把,单组逐一排改成了多组并排~~以前我们是一组跑完了再去排另一组,他改完后直接一遍就可以把多组排好~!但性能上没有差别,,。

这里他给的gap = gap / 3 + 1;这样写的效率其实比gap /= 2的要好一点(网上以前看到的),他这里+1是因为,gap / 3有时候会小于1(例如: 2 / 3),这样最后一趟就排不了了~!+1就可以解决这个问题!

复杂度分析

希尔排序的时间复杂度是极其不好计算的,因为它的gap 的取值方法太多,很难精确地去计算,在好多书中给的都不同:例如严蔚敏老师的书中是如下

殷人昆老师的书中如下:

由于这里我们的gap是按殷人昆老师提出的方式取的,而殷人昆老师也对其进行了大量的数据实验统计,我们可以暂时认为

希尔排序的时间复杂度为:O(N^1.25)或O(N^1.3)

希尔排序的空间复O(1)

OK,本期插入排序就先介绍到这里,好兄弟,我们下期选择排序见~!

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

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

相关文章

Flutter开发type ‘Future<int>‘ is not a subtype of type ‘int‘ in type cast错误

文章目录 问题描述错误源码 问题分析解决方法修改后的代码 问题描述 今天有个同事调试flutter程序时报错&#xff0c;问我怎么解决&#xff0c;程序运行时报如下错误&#xff1a; type ‘Future’ is not a subtype of type ‘int’ in type cast 错误源码 int order Databas…

聚观早报 |红魔9 Pro开卖;真我GT5 Pro定档

【聚观365】11月29日消息 红魔9 Pro开卖 真我GT5 Pro定档 一加12镜头细节公布 Redmi K70 Pro将搭载夜枭算法 苹果Vision Pro头显下月量产 红魔9 Pro开卖 红魔电竞旗舰最新力作——红魔9 Pro系列正式发布。作为一款全能电竞旗舰&#xff0c;该机搭载了第三代骁龙8移动平台…

【九】linux下部署frp客户端服务端实践

linux下部署frp客户端服务端实践 简介&#xff1a; 今天有一个这样的需求&#xff0c;部署在公司内部局域网虚拟机上的服务需要在外网能够访问到&#xff0c;这不就是内网穿透的需求吗&#xff0c;之前通过路由器实现过&#xff0c;现在公司这块路由器不具备这个功能了&#x…

Java后端开发——JDBC(万字详解)

Java后端开发——JDBC&#xff08;万字详解&#xff09; 今日目标 掌握JDBC的的CRUD理解JDBC中各个对象的作用掌握Druid的使用 1&#xff0c;JDBC概述 在开发中我们使用的是java语言&#xff0c;那么势必要通过java语言操作数据库中的数据。这就是接下来要学习的JDBC。 1.1 …

【PHP】MySQL简介与MySQLi函数(含PHP与MySQL交互)

文章目录 一、MySQL简介二、MySQLi函数1. 开启mysqli扩展&#xff1a;2. PHP MySQLi扩展的常用函数 三、PHP与MySQL交互0. 准备1. 创建连接&#xff08;mysqli_connect() &#xff09;连接mysql语法 2. 选择数据库&#xff08;mysqli_select_db()&#xff09;3. 在php中操作数据…

【burpsuite安全练兵场-客户端16】测试WebSockets安全漏洞-3个实验(全)

前言&#xff1a; 介绍&#xff1a; 博主&#xff1a;网络安全领域狂热爱好者&#xff08;承诺在CSDN永久无偿分享文章&#xff09;。 殊荣&#xff1a;CSDN网络安全领域优质创作者&#xff0c;2022年双十一业务安全保卫战-某厂第一名&#xff0c;某厂特邀数字业务安全研究员&…

微服务--03--OpenFeign 实现远程调用 (负载均衡组件SpringCloudLoadBalancer)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 OpenFeign其作用就是基于SpringMVC的常见注解&#xff0c;帮我们优雅的实现http请求的发送。 RestTemplate实现了服务的远程调用 OpenFeign快速入门负载均衡组件Spr…

深入redis过程-命令

目录 通用命令 get set keys exists del expire key seconds ttl type 常用数据结构 String类型 SET GET MSET MGET INCR INCRBY INCRBYFLOAT SETNX SETEX Hash类型 HSET key field value HGET key field HMSET HMGET HGETALL HKEYS HVALS HINCRB…

Redis队列stream,Redis多线程详解

Redis 目前最新版本为 Redis-6.2.6 &#xff0c;会以 CentOS7 下 Redis-6.2.4 版本进行讲解。 下载地址&#xff1a; https://redis.io/download 安装运行 Redis 很简单&#xff0c;在 Linux 下执行上面的 4 条命令即可 &#xff0c;同时前面的 课程已经有完整的视…

Ubuntu 上使能 SELinux

首发公号&#xff1a;Rand_cs 此文档说明如何在 ubuntu 上启用 SELinux&#xff0c;测试环境为虚拟机&#xff0c;开始前一定一定一定先来个快照&#xff0c;不要问我为什么有三个一定。 卸载 apparmor&#xff08;可选&#xff09; ubuntu 默认安装的安全组件为 apparmor&a…

红队攻防之hash登录RDP

没什么好害怕&#xff0c;孩子放心去飞吧&#xff0c;在你的身后有个等你的家 Restricted Admin Mode 受限管理模式是一项 Windows 功能&#xff0c;可防止将 RDP 用户的凭据存储在建立 RDP 连接的计算机的内存中。 这是用来防止用户&#xff08;管理员&#xff09;在 RDP 进…

使用Pytorch从零开始构建扩散模型-DDPM

知识回顾: [1] 生成式建模概述 [2] Transformer I&#xff0c;Transformer II [3] 变分自编码器 [4] 生成对抗网络&#xff0c;高级生成对抗网络 I&#xff0c;高级生成对抗网络 II [5] 自回归模型 [6] 归一化流模型 [7] 基于能量的模型 [8] 扩散模型 I, 扩散模型 II 引言 去噪…

Windows系统下搭建PXE Server

在给一台服务器初始安装OS时一般有以下几种方式&#xff1a; 1、通过BMC挂载iso镜像来安装&#xff1b; 2、通过U盘启动来安装&#xff1b; 3、通过网络启动来安装&#xff1b; 方式1和方式2只能一台一台地进行&#xff0c;且需要有键盘和显示器&#xff0c;效率低下&#xff…

Jmeter--如何监控服务器资源

在我们做项目的性能测试时&#xff0c;需要查看相关服务器的资源使用情况&#xff1b;本文以apache-Jmeter-5.5版本为例&#xff0c;使用PerfMon进行服务器资源监控的方案由两部分来实现&#xff1a;ServerAgent部署在被测服务器&#xff0c;负责资源耗用数据的采集&#xff0c…

运营商网络性能测试-Y.1564

前言 在网络部署之后和业务开展之前&#xff0c;运营商迫切希望了解当前网络的性能状态&#xff0c;以便为商业规划和业务推广提供必要的基础数据支持。因此&#xff0c;高可靠性和高精确度的性能测试方法对于运营商评判网络性能的优劣&#xff0c;显得尤为重要&#xff0c;而…

在虚拟机搭建nignx,和使用本地访问nginx的情况

下载nginx yum install nginx 查看nginx是否安装成功。 nginx -v nginx的配置文件的目录和资源的目录。 先到nginx.conf的目录下&#xff0c;在 /etc/nginx/nginx.conf&#xff0c;编辑它。 vi /etc/nginx/nginx.conf 可以看到默认的html的目录。在 /usr/share/nginx/html 下面…

算法 离散化

整数离散化 适用条件 适用于有序的整数序列该序列的值域很大&#xff0c;该序列的数的个数很少使用的是数的相对大小而非绝对大小 算法思路 原数组 a &#xff1a; 数组下标&#xff1a;0 1 2 3 4 数组元素&#xff1a;1 2 2 5 109 映射数组 &#xff1a; 数组下标&…

等保——密评技术要求

密评简介 密评定义&#xff1a;全称商用密码应用安全评估, 是指对采用商用密码技术、产品和服务集成建设的网络和信息系统密码应用的合规性、正确性、有效性进行评估。密评对象&#xff1a;重要信息系统、关键信息基础设施、网络安全等保三级及以上的系统。评测依据&#xff1…

算法通关村-----数据流的中位数

数据流的中位数 问题描述 中位数是有序整数列表中的中间值。如果列表的大小是偶数&#xff0c;则没有中间值&#xff0c;中位数是两个中间值的平均值。 例如 arr [2,3,4] 的中位数是 3 。 例如 arr [2,3] 的中位数是 (2 3) / 2 2.5 。 实现 MedianFinder 类: MedianFin…

Photoshop Elements 2023 v21.0(ps简化版)

Photoshop Elements 2023是一款ps简化版图像处理软件&#xff0c;它加入了一些新的功能和工具&#xff0c;以帮助用户更高效地处理图片。 新功能&#xff1a;软件加入了黑科技&#xff0c;采用Adobe Sensei AI技术&#xff0c;主打人工智能&#xff0c;一键P图&#xff0c;新增…