数据结构之排序(上)

片头

嗨,小伙伴们,大家好!我们今天来学习数据结构之排序(上),今天我们先讲一讲3个排序,分别是直接插入排序、冒泡排序以及希尔排序。

1. 排序的概念及其应用

1.1 排序的概念

排序:什么是排序呢?排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i] = r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

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

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


一、插入排序

2.1.1 基本思想:

直接插入排序是一种简单的插入排序法,其基本思想是:

把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列。(简便记忆:将待排序的元素一个个插入到一个已经排好序的有序序列中,直到整个数列都有序为止)

emmm,听上去有点迷迷糊糊,我们来举个例子~

直接插入排序,大家平时都玩过,我们玩扑克牌时,最好让我们的牌按照从小到大的顺序排列,排好序之后,我们就方便出牌。

摸了一张牌后,怎么保证手里的牌是有序的?手里的牌本身是有序的,再摸一张牌,摸完后,插入到相应位置,继续保持手里的牌有序。

插入排序的思想:已经有一个有序序列,再摸一张牌起来,然后把它插入到合适的位置,保持它们继续有序。

数组的本质:最开始把第一个数当作是有序的,把后一个数(第二个数)往前插入;前2个数有序,第3个数插入;前3个数有序,第4个插入,依次类推......

往前怎么插入呢?如果插入的元素比前一个元素小,就将前一个元素往后挪动;如果插入的元素比前一个大,就放到前一个元素的后面。

当 前n-1个数有序,(第n个元素)最后一个元素插入,数组排序完成。

2.1.2 直接插入排序

当插入第 i (i>=1)个元素时,前面的array[0],array[1],....,array[i-1]已经排好序,此时用array[i]的排序码与array[i-1],array[i-2],...的排序吗顺序进行比较,找到插入位置即将array[i]插入,原来位置上的元素顺序后移。

动图演示:

那我们要怎么实现直接插入排序呢?

 排序这个部分,按2个步骤去完成:

①单趟 ②整体

单趟插入排序:

思想:把一个数往前面的有序区间插入,必须确保[0,end]区间是有序的。

比如,我在[0,end]这个区间是有序的,我要把end+1这个位置的值往[0,end]这个区间进行插入。

我们先假设arr数组里面已经存放了  "1" ,  "3" ,  "5" ,现在我们想要存放"7"

如果我们想要插入"2",该怎么做呢?

完整过程如下:

此时,"2"比"1"大,直接将"2"放到"1"的后面即可,换句话来说,将temp里面的值放入arr数组的end+1位置即可。

如果我们想要插入"0",该怎么做呢?

很简单,思路和刚才基本一致,我们一起来画一画图~

当执行到最后一次的时候,"0"比"1"小,因此将"1"往后挪动一位,同时end--,然后将"0"这个元素,也就是temp保存的值插入到 end+1 的位置,数组排序完成。

单趟插入排序的代码如下:

//直接插入排序
void InsertSort(int* a, int n) {//单趟int end;int temp = a[end + 1];        //将end+1位置的值保存到temp变量里面while (end >= 0) {    if (temp < a[end]) {      //如果temp的值比end位置的值小a[end + 1] = a[end];  //将end位置的值往后挪动end--;                //end继续找前一个元素}else {break;                //如果temp的值比end位置的值大,或者两者相等,跳出循环}}//跳出循环有2种情况://①temp的值比end位置的值大或者两者相同,直接将temp的值放到end位置的后面(end+1位置)//②temp的值比所有的值都小,循环结束,此时end为-1//那我还是要将temp放到end后面, -1 + 1 = 0, 放到下标为0的位置a[end + 1] = temp;
}

单趟的插入排序我们已经知道了,那么整体的插入排序怎么写呢? 

我们可以发现,无论是上面2种情况的哪一种,插入的元素都是放到end的后面,也就是end+1的位置。基于这样一个原因,我们可以:

① 定义一个变量temp,把end+1位置的值保存起来

② 最坏的情况下,end<0即循环结束(end == -1,已经超出了数组的范围),就是插入的值比所有的数都小

③跳出循环有2种情况:

  • temp的值比end位置的值大或者两者相等,直接将temp的值放到end位置后面(end+1位置)
  • temp的值比所有的值都小,循环结束,此时end为-1。那我还是要将temp的值放到end位置的后面(end+1位置), -1 + 1 = 0,将temp的值放到下标为0的位置。

 综上,  ①end起始位置是0,默认[0,0]区间的元素是有序的。此时,end == 0,下标为0的元素当作是有序的,后一个元素向前插入;end == 1,前2个元素有序了,再把第3个元素往前插入;end == 2,前3个元素有序了,再把第4个元素往前插入,以此类推....... 当 end == n-2, 前n-1个数已经有序,第n个数往前插入,整个数组有序。

因此,我们可以定义一个变量j,  j 的范围在[0,n-2] , j的最后一个位置的值为 n-2 ,也就是说 j < n-1

2.1.3 直接插入排序的代码:
//直接插入排序
void InsertSort(int* a, int n) {
//end起始位置是0,初始时,[0,0]区间的元素是有序的
//end = 0,下标为0的元素当做是有序的,后一个元素往前插入
//end = 1,前2个元素有序了,再把第3个元素往前插入
//end = 2,前3个元素有序了,再把第4个元素往前插入
//.....
//end = n-2,当前n-1个元素已经有序了,将第n个元素往前插入,整个数组有序//j的范围[0,n-2], j == n-2 --> j < n-1for (int j = 0; j < n - 1; j++) {int end = j;//定义一个变量temp,把temp+1位置的值保存起来int temp = a[end + 1];	//最坏的情况: end < 0即循环结束(end == -1,已经超出了数组的范围)//插入的值比数组中所有的值都小while (end >= 0) {//如果temp的值比end位置的值小,将end位置的值向后挪动if (temp < a[end]) {a[end + 1] = a[end];end--;}//如果temp的值比end位置的值大或者两者相同,跳出循环else {break;}}//跳出循环有2种情况://①temp的值比end位置的值大或者两者相同,直接将temp的值放到end位置的后面(end+1位置)//②temp的值比所有的值都小,循环结束,此时end为-1//那我还是要将temp放到end后面, -1 + 1 = 0, 放到下标为0的位置a[end + 1] = temp;}
}

二、冒泡排序

  2.2.1 基本思想

 冒泡排序是一种基本的排序算法,通过重复地比较相邻的两个元素,并且交换位置,将最大的元素逐渐"冒泡"到最后面。冒泡排序的思想是重复地遍历待排序的元素,每次遍历比较相邻的两个元素,如果他们的顺序错误就交换位置,直到没有需要交换的元素为止。

   具体实现时,首先从数组的第一个元素开始,与相邻的元素进行比较,如果顺序错误就交换位置,然后继续比较相邻的下一对元素,一直到数组的最后一个元素。这样一次遍历后,最大的元素就会"冒泡"到最后面。然后再从第一个元素开始,重复上述操作,直到整个数组都排好序。

动图展示:

单趟冒泡排序的思想: 把最大的数换到最后

如果i从1开始,i的最后一个位置为 n-1, 将前一个元素和当前元素进行比较,也就是将 a[i-1] 和 a[i] 进行比较,如果前一个元素比当前元素大,则交换。当 i == n,循环结束

如果i从0开始,i的最后一个位置为 n-2, 将当前元素和后一个元素进行比较,也就是将 a[i] 和 a[i+1] 进行比较, 如果当前元素比后一个元素大,则交换。当 i == n-1,循环结束

单趟冒泡排序的代码:

//2个数进行交换
void Swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}//冒泡排序
void BubbleSort(int* a, int n) {//单趟//如果i从1开始,i的结束位置在n-1//将前一个元素a[i-1]和当前元素a[i]进行比较,如果前一个比当前元素大,进行交换//如果i从0开始,i的结束位置在n-2//将当前元素a[i]和后一个元素a[i+1]进行比较,如果当前元素比后一个元素大,进行交换for (int i = 1; i < n; i++) {if (a[i - 1] > a[i]) {Swap(&a[i - 1], &a[i]);}}
}

我们已经知道了单趟的冒泡排序是如何实现的,那么整体的冒泡排序怎么做呢? 

第一次冒泡,冒泡完毕后,结束位置在下标为 n-1 的位置(i < n);第二次冒泡,结束位置在下标为 n-2 的位置(i < n-1);第三次冒泡,结束位置在下标为 n-3 的位置(i < n-2);第四次冒泡,结束位置在下标为 n-4 的位置 (i < n-3)....... 当 i == 1 ( i < n - (n-2) )时,冒泡结束。

 2.2.2 冒泡排序的代码:
//2个数进行交换
void Swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}//冒泡排序
void BubbleSort(int* a, int n) {//第一次冒泡,冒泡完毕后,结束位置在 n-1 --> i<n//第二次冒泡,结束位置在 n-2 --> i<n-1//第三次冒泡,结束位置在 n-3 --> i<n-2//第四次冒泡,结束位置在 n-4 --> i<n-3//....//当 i==1 时,冒泡结束。// i == 1 --> i == n-(n-1) --> i<2 --> i< n-(n-2)// j的范围:[0,n-2], j == n-2 --> j<n-1for (int j = 0; j < n - 1; j++) {for (int i = 1; i < n-j; i++) {if (a[i - 1] > a[i]) {Swap(&a[i - 1], &a[i]);}}}
}
算法优化:

如果遍历一遍数组后没有发生任何元素交换,说明每一个数,前一个都小于后一个,此时数组已经有序,排序就可以结束了。

优化过的代码如下:

//2个数进行交换
void Swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}//升级版冒泡排序
void BubbleSort1(int* a, int n) {//第一次冒泡,冒泡完毕后,结束位置在 n-1 --> i<n//第二次冒泡,结束位置在 n-2 --> i<n-1//第三次冒泡,结束位置在 n-3 --> i<n-2//第四次冒泡,结束位置在 n-4 --> i<n-3//....//当 i==1 时,冒泡结束。// i == 1 --> i == n-(n-1) --> i<2 --> i< n-(n-2)// j的范围:[0,n-2], j == n-2 --> j<n-1for (int j = 0; j < n - 1; j++) {//定义变量flag,假设此时数组是有序的int flag = 1;	for (int i = 1; i < n - j; i++) {if (a[i - 1] > a[i]) {Swap(&a[i - 1], &a[i]);//如果发生了交换,说明数组此时是无序的,flag为0flag = 0;}}//如果没有发生交换,说明数组已经有序//不需要进行比较了直接跳出循环if (flag == 1)break;}
}

三、希尔排序(最小增量排序)

希尔排序又称为缩小增量排序。它也是插入排序的一种,由希尔于1959年提出。

2.3.1 基本思想

希尔排序的基本思想是将待排序的元素分成几个子序列进行排序,通过逐步缩小子序列的间隔,最终使整个序列变为有序,具体步骤如下:

(1)首先确定一个增量gap,通常为数组的一半,然后将数组分成gap个子序列。

(2)分别对这些子序列进行插入排序,即对每个子序列进行直接插入排序,这样每个子序列都是部分有序的。

(3)再次选择一个较小的增量gap,重复步骤(2),直到gap为1。

(4)最后进行一次增量gap为1的插入排序,完成排序。

动图演示:

 希尔排序的思想:改革直接插入排序,有什么方法能让数组接近有序呢?

我直接再来一趟插入排序。把排序分成2个部分,第1个部分: 预排序。预排序的目标是:让数组接近有序;第2个部分:插入排序,目标是:让整个数组有序。

什么是预排序呢?

预排序是指:分组插入排序。目标:大的数更快换到后面的位置,小的数更快换到前面的位置

 gap给多少的问题:

①gap越大,数据跳得越快,大的数更快换到后面位置,小的数更快换到前面位置,但是越不接近有序

②gap越小,数据跳得越慢,但是越接近有序,当gap == 1时,插入元素后就是有序。

2.3.2 希尔排序的代码:
//希尔排序
void ShellSort(int* a, int n) {int gap = 3;for(int j = 0; j < gap; j++) {for (int i = j; i < n - gap; i += gap) {int end = i;int temp = a[end + gap];while (end >= 0) {if (temp < a[end]) {a[end + gap] = a[end];end = end - gap;}else {break;}}a[end + gap] = temp;}}
}

算法优化:

//希尔排序
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 temp = a[end + gap];while (end >= 0) {if (temp < a[end]) {a[end + gap] = a[end];end = end - gap;}else {break;}}a[end + gap] = temp;}}
}

希尔排序特性总结:

  1. 希尔排序是对直接插入排序的优化。
  2. 当gap>1时都是预排序,目的是让数组更接近有序。当gap==1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。

希尔排序的时间复杂度不好计算,因为gap取值的方法很多,导致很难去计算,因此在很多书中给出的希尔排序的时间复杂度都不固定。


片尾

今天我们学习了3个排序,分别是直接插入排序,冒泡排序以及希尔排序,希望能对看完文章的友友们有所帮助!!!

点赞收藏加关注!!!

谢谢大家!!!

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

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

相关文章

图书馆APP开发解决方案

uni-app框架&#xff1a;使用Vue.js开发跨平台应用的前端框架&#xff0c;编写一套代码&#xff0c;可编译到Android、小程序等平台。 框架支持:springboot/Ssm/thinkphp/django/flask/express均支持 前端开发:vue.js 可选语言&#xff1a;pythonjavanode.jsphp均支持 运行软件…

百度云防护如何开启CC攻击防护

百度云防护的最重要的功能是可以CC攻击防护&#xff0c;针对CC攻击&#xff0c;百度云防护有被动的CC攻击拦截规则&#xff0c;也有主动自定义访问策略拦截。 今天百度云来教大家如何开启百度云防护的CC攻击防御功能。 1.进入防护模板功能-创建模板 2.开启CC攻击防御功能&…

李飞飞首次创业!

B站&#xff1a;啥都会一点的研究生公众号&#xff1a;啥都会一点的研究生 最近AI又有啥进展&#xff1f;一起看看吧~ 中国独角兽企业已达369家&#xff0c;六成以上与AI、芯片等硬科技赛道有关 2024中关村论坛“全球独角兽企业大会”上发布全新《中国独角兽企业发展报告&am…

探索互联网医院系统源码:开发在线药房小程序实战教学

今天&#xff0c;笔者将与大家一同深入探讨互联网医院系统的源码结构&#xff0c;并通过开发在线药房小程序的实战教学&#xff0c;为读者提供一种学习和理解这一领域的途径。 一、互联网医院系统源码解析 1.技术选型 互联网医院系统的开发离不开合适的技术选型&#xff0c;…

类和对象-Python-第二部分

师从黑马程序员 多态 抽象类&#xff08;接口&#xff09; #演示抽象类 class AC:def cool_wind(self):"""制冷"""passdef hot_wind(self):"""制热"""def swing_l_r(self):"""左右摆风""…

Cloudflare国内IP地址使用教程

Cloudflare国内IP地址使用教程 加速网站&#xff1a; 首先我们添加一个 A 记录解析&#xff0c;解析 IP 就是我们服务器真实 IP&#xff1a; 然后侧边栏 SSL/TLS - 自定义主机名&#xff1a; 回退源这里填写你刚刚解析的域名&#xff0c;保存后回退源状态为有效再来接下的操作…

第十二篇:数据库系统导论 - 探索数据管理的基石

数据库系统导论 - 探索数据管理的基石 1 引言 数据的力量&#xff1a;揭秘数据库系统的核心 在信息时代&#xff0c;数据无处不在&#xff0c;它们成为了企业和社会运作的基础。我们如何储存、检索、更新和维护这些数据&#xff0c;决定了我们能否从这些数据中获得力量。数据…

网络应用层

叠甲&#xff1a;以下文章主要是依靠我的实际编码学习中总结出来的经验之谈&#xff0c;求逻辑自洽&#xff0c;不能百分百保证正确&#xff0c;有错误、未定义、不合适的内容请尽情指出&#xff01; 文章目录 1.使用协议和序列化1.1.自定义协议&#xff0c;自定义序列化1.2.自…

【网络】网络基础

目录 一、前言 1.计算机网络背景 2.认识协议 二、网络协议初识 1.OSI七层模型 2.TCP/IP五层(或四层)模型 3.网络传输基本流程 4.数据包封装和分用 5.网络中的地址管理 1.IP地址 2.MAC地址 一、前言 1.计算机网络背景 网络之前&#xff0c;我们所有在电脑上的操作都是…

Spring Boot数据映射利器:MapperStruct vs. BeanUtils.copyProperties 一较高下

Spring Boot数据映射利器&#xff1a;MapperStruct vs. BeanUtils.copyProperties 一较高下 在 Spring Boot 应用程序中&#xff0c;常常需要在不同的 Java 对象之间进行数据拷贝。这种拷贝操作在开发中非常常见&#xff0c;比如将 DTO&#xff08;Data Transfer Object&#x…

5. 简单说一说uniapp中的语法吧

前言 如果你 知道Vue3并且对Vue3的语法有一定了解&#xff0c;请跳过这一章&#xff0c;由于后续项目主要是基于Vue3TypeScript&#xff0c;因此提前简单概述一些Vue3的基础语法~ 本文的目的是 期望通过对本文的阅读后能对Vue3的每个语法有一个简单的印象&#xff0c;至少要知…

二叉搜索数使用,底层原理及代码实现

1:二叉搜索树的定义 二叉搜索树的底层是一个二叉链表 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树 &#xff0c;或者是具有以下性质的二叉树 : 若它的左子树不为空&#xff0c;则左子树上所有节点的值都小于根节点的值 若它的右子树不为空&#xff0c;则右子树上所…

Redis-配置文件详解

Redis配置文件详解 units单位 配置大小单位&#xff0c;开头定义基本度量单位&#xff0c;只支持bytes&#xff0c;大小写不敏感。 INCLUDES Redis只有一个配置文件&#xff0c;如果多个人进行开发维护&#xff0c;那么就需要多个这样的配置文件&#xff0c;这时候多个配置 文…

docker安装向量数据库milvus

Miluvs Milvus 向量数据库能够帮助用户轻松应对海量非结构化数据(图片 / 视频 / 语音 / 文本)检索。 单节点 Milvus 可以在秒内完成十亿级的向量搜索,分布式架构亦能满足用户的水平扩展需求。 Milvus 向量数据库的应用场景包括:互联网娱乐(图片搜索 / 视频搜索)、新零售…

MATLAB基础—系统环境

1.MATLAB操作界面的组成 (1)MATLAB主窗口&#xff08;红色&#xff09; MATLAB主窗口是MATLAB的程序窗口&#xff0c;他除了嵌入一功能窗口外&#xff0c;主要包括功能区(1)&#xff0c;快速访问工具栏(2)&#xff0c;和当前文件夹工具栏(3)。 在功能区提供了三个选项卡&#…

浅析vue3自定义指令

vue3中可以像下面这样使用自定义指令。 这里我们只是定义了一个vFoucs变量&#xff0c;vue怎么知道这是一个指令呢&#xff1f; 这是因为约定大于配置&#xff0c;vue3中有这样一个约定&#xff08;截图来自官方文档&#xff09;&#xff1a; 注意这里说的是驼峰命令&#x…

机器学习案例:加州房产价格(一)

参考链接&#xff1a;https://hands1ml.apachecn.org/2/ 假设你是被一家地产公司雇佣的数据科学家&#xff0c;现在需要做一些工作。 公司所给的数据集是StatLib 的加州房产价格数据集。这个数据集是基于 1990 年加州普查的数据。数据已经有点老&#xff0c;但它有许多优点&…

【三十一】springboot+easyExcel实现多文件导出压缩包

互相交流入口地址 整体目录&#xff1a; 【一】springboot整合swagger 【二】springboot整合自定义swagger 【三】springboot整合token 【四】springboot整合mybatis-plus 【五】springboot整合mybatis-plus 【六】springboot整合redis 【七】springboot整合AOP实现日志操作 【…

【数字IC设计】芯片设计中的RDC

RDC问题定义 在芯片设计中,RDC是reset domain crossing 的缩写,类似于CDC(clock domain crossing),由于现在SOC芯片是有很多ECUs组成,为了使整个系统能够快速从复位中恢复, 用户希望SOC里面每个ECU模块都可以有自己独立的异步复位信号,这样可以在出问题的时候只复位有错…

【计算机网络篇】数据链路层(8)共享式以太网的退避算法和信道利用率

文章目录 &#x1f6f8;共享式以太网的退避算法&#x1f95a;截断二进制指数算法 &#x1f354;共享式以太网的信道利用率 &#x1f6f8;共享式以太网的退避算法 在使用CSMA/CD协议的共享总线以太网中&#xff0c;正在发送帧的站点一边发送帧一边检测碰撞&#xff0c;当检测到…