数据结构——排序(续集)


♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥

♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥

♥♥♥我们一起努力成为更好的自己~♥♥♥

♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥

♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥

✨✨✨✨✨✨ 个人主页✨✨✨✨✨✨

上一篇博客我们说到了四种排序算法数据结构——排序,这一篇博客我们继续在排序算法里面遨游~体会更多的排序算法的魅力~

目录

交换排序

冒泡排序

基本思想

代码

时间复杂度

快速排序

基本思想

Hoare版本找基准值

挖坑法找基准值

lomuto前后指针找基准值

快速排序特性总结

归并排序

基本思想

代码

时间复杂度


交换排序

交换排序基本思想:所谓交换,就是 根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置
交换排序的 特点 是:将 键值较大的记录向序列的尾部移动,键值小的记录向序列的前部移动

交换排序包括两种,一种是冒泡排序,一种是快速排序,我们一个个来看看~

冒泡排序

基本思想

冒泡排序是⼀种最基础的交换排序。因为每一个元素都可以像小气泡一样,根据自身大小一点一点向数组的一侧移动,所以叫做冒泡排序。
它的基本思想是根据元素个数来比较不同趟数, 每一趟里面元素两两比较 ,如果满足一定的条件就进行交换, 最终最大的(或者最小的)元素会到最后面 ~

这里举一个简单的例子:

现在我们想要排序【3,5,9,7,2】这个数组排成升序~


第一趟比较:

【3,5,9,7,2】——>【3,5,9,7,2】——>【3,5,7,9,2】——>【3,5,7,2,9】


第二趟比较:(最后一个元素已经是最大的了,排序剩下的元素)

【3,5,7,2,9】——>【3,5,7,2,9】——>【3,5,2,7,9】


第三趟比较:

【3,5,2,7,9】——>【3,2,5,7,9】


第四趟比较:

【2,3,5,7,9】


经过四趟的排序,我们的数组就已经成为了升序~

代码

通过前面的思路,我们可以写出下面的代码:

//冒泡排序
void BubbleSort(int* arr, int sz)
{//外层循环比较趟数for (int j = 0; j < sz - 1; j++){//内层循环元素两两比较for (int i = 0; i < sz - 1 - j; i++){//前面元素比后面大就交换if (arr[i] > arr[i + 1]){int tmp = arr[i];arr[i] = arr[i + 1];arr[i + 1] = tmp;}}}
}

排序成功~

时间复杂度

按照上面的代码,第一趟比较次数为n-1,第二趟比较次数为n-2……第n-2趟比较次数为2,第n-1趟比较次数为1,是一个等差数列(n-1+1)(n-1)/2,根据时间复杂度的规则也就是O(N^2),事实上,上面的代码我们也可以给它做出优化~如果数组有序,那么第一趟就不会进行交换,我们可以标记一下~后面就不需要继续比较了~

优化代码:


//优化的冒泡排序
void BubbleSort(int* arr, int sz)
{//外层循环比较趟数for (int j = 0; j < sz - 1; j++){int flag = 1;//标记当前数组是否有序//内层循环元素两两比较for (int i = 0; i < sz - 1 - j; i++){//前面元素比后面大就交换if (arr[i] > arr[i + 1]){//进行了交换,说明数组无序flag = 0;int tmp = arr[i];arr[i] = arr[i + 1];arr[i + 1] = tmp;}}if (flag == 1){break;//数组有序,不需要继续比较}}
}

排序成功~这样如果是一个有序的数组,时间复杂度就会降低,最好的情况是第一趟就发现有序,那么时间复杂度为O(N),如果本来就是无序的,时间复杂度依然是O(N^2)

现在我们来比较一下排序100000个数据的运行时间~

我们可以看到冒泡排序达到了二十几秒,所以我们排序中是不推荐使用的~

快速排序

基本思想

快速排序是Hoare于1962年提出的 一种二叉树结构的交换排序方法
基本思想为: 任取待排序元素序列中的某元素作为基准值 ,按照该排序码 将待排序集合分割成两子序列 左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值(这样就可以 把基准值放到它该放的位置上) ,然后 再分左右子序列重复该过程 ,直到所有元素都排列 在相应位置上为止。

Hoare版本找基准值

这里找基准值有很多的版本,首先来看看 Hoare版本,思路如下:
right 从右往左找比基准值要小的数据
left 从左往右找比基准值要大的数据
找到之后, left 和 right 交换
left > right ,基准值 key 和 right 交换
我们结合一个例子画图理解一下:

首先让基准值key就是left,left++往后面走一个,right指向最后一个元素


right 从右往左找比基准值要小的数据

left 从左往右找比基准值要大的数据


找到了就交换下标为left和right的数据


再次重复前面的步骤,直到left>right

right 从右往左找比基准值要小的数据

left 从左往右找比基准值要大的数据

left>right,基准值 key和 right 交换

我们可以看到基准值放到了它应该放的位置,前面的元素都比它小,后面的元素都比它大。

然后再把左右序列进行类似的操作~

这个排序的过程事实上就是不断二分的过程,不断地分成左右子序列,接下来看看代码

//Hoare版本找基准值
int _QuickSort(int* arr, int left, int right)
{int keyi = left;//记录基准值下标left++;while (left <= right){//每一个循环都写left <= right,确保不越界// right 从右往左 找比基准值要小的数据while (left <= right && arr[right] > arr[keyi]){right--;}// left 从左往右 找比基准值要大的数据while (left <= right && arr[left] < arr[keyi]){left++;}//找到了,交换if (left <= right){Swap(&arr[left], &arr[right]);//交换后继续找直到left>rightleft++;right--;}}Swap(&arr[keyi], &arr[right]);return right;
}//快速排序
void QuickSort(int* arr, int left, int right)
{//找基准值int key = _QuickSort(arr, left, right);//左右子序列重复操作//[left,key-1]   [key+1,right]if (left < key - 1){//需要判断,避免越界!!!QuickSort(arr, left, key - 1);}if (key + 1 < right){//需要判断,避免越界!!!QuickSort(arr, key + 1, right);}
}

注意点:

我们是right 从右往左找比基准值要小的数据,left 从左往右找比基准值要大的数据,但是在我们的代码实现中,找到与基准值相等的也算进去进行交换了,这样可以更好地实现二分的目的,提高代码效率~

最后递归要判断下标是否有效

以上面代码的例子为例:

如果我们的key就是最大的,left走到最后面,那么right也就是key,是数组的最大下标,那么key+1=10就会越界【10,9】这就不是有效的

时间复杂度:

我们知道递归的时间复杂度=递归的次数*一次递归的时间复杂度

因为不断地进行二分,我们认为递归的次数为logN(如果数组所有元素相等或者已经有序,那么递归的次数为N,这种情况很少~),一次递归时间复杂度O(N)——这里虽然看起来是两层循环,但是里面的一层循环,只是用来判断,并没有完全的遍历数组~

所以时间复杂度为O(N*logN)

比较时间:
我们可以看到快速排序效率还是很高的~

挖坑法找基准值

这里快速排序也是使用递归来实现,但是找基准值方法不一样,我们一起来看看~

创建左右指针left、right,首先 right 从右向左找出比基准值小的数据找到后立即放入左边坑中当前位置变为新的"坑"

然后 left 从左向右找出比基准值大的数据找到后立即放入右边坑中当前位置变为新的"坑"

结束循环后将最开始存储的分界值放入当前的"坑"中返回当前"坑"下标(即分界值下标)

我们依然画图理解~

3比基准值小,把它拿到原来的坑位,right成为新的坑位

left找比基准值大的7,找到了,7去填坑

现在的left成为新坑

right继续找,如果相遇就停下来~

arr【hole】=key;返回坑hole就是基准值下标

代码:

//挖坑法找基准值
int _QuickSort2(int* arr, int left, int right)
{int hole = left;int key = arr[hole];//保存最开始坑位值,也就是基准值while (left < right){// right 从右向左找出比基准值小的数据// 找到后立即放入左边坑中,当前位置变为新的"坑"//这里相等就继续遍历while (left<right && arr[right] >= key){right--;}arr[hole] = arr[right];hole = right;// left 从左向右找出比基准值大的数据// 找到后立即放入右边坑中,当前位置变为新的"坑"while (left<right && arr[left] <= key){left++;}arr[hole] = arr[left];hole = left;}//相遇或者left>right跳出循环arr[hole] = key;return hole;//返回坑位
}

排序成功~

注意点:

这里相等也继续找

例:(一方面,如果里面元素相同,那么不断的交换也就降低了效率~)

另外一方面,同时以代码里面的数组为例

{ 9,1,2,5,7,4,8,6,3,5 }

第一轮:

left最开始就是hole的位置,arr【left】=key,那么就会不停的与自己进行交换,成为新坑位,不会往后面走,这就出现问题了~死循环了~

有的人可能会说left++不就可以了吗?

我们依然使用上面的数组验证~

 

这个时候我们就发现坑位一直在俩个位置变化,没有改变~因为依然有相同的元素,所以left++也不能解决这个问题~

接下来我们看看left能不能++呢?

这就是另外一个注意点:left不能++

我们依然使用上面的数组验证~

这就分左右序列了,我们先来看看左边的序列

我们继续来看看它的左边的序列

我们发现left和right已经相遇了,那么就不会去填坑了~所以left不能++

所以写代码的时候还是需要多多注意这些问题~

时间复杂度依然为O(N*logN)

lomuto前后指针找基准值

基本思想: 创建前后指针,从左往右找比基准值小的进行交换,使得小于基准值的都排在基准值的左边

思路:

我们依然画图理解
1比6小,++prev,prev与cur数据交换(这里++prev 等于 cur可以不进行交换),++cur
2比6小,++prev,prev与cur数据交换(这里++prev 等于 cur可以不进行交换),++cur
7比6大,位置不变,++cur
9比6大,位置不变,++cur
3比6小,++prev,prev与cur数据交换,++cur
cur已经越界,交换key和prev位置数据,返回prev就是基准值下标,这样 小于基准值6的都排在基准值6的左边
有了前面的画图,相信这里的代码就不是什么大问题了~

代码:


//lomuto前后指针找基准值
int _QuickSort3(int* arr, int left, int right)
{int key = left;//当前基准值下标int prev = left, cur = left + 1;//cur探路while (cur <= right)//确保下标不越界{//比基准值小,++prev如果不等于cur,交换prev和cur位置数据,cur++if (arr[cur] < arr[key] && ++prev != cur){Swap(&arr[prev], &arr[cur]);cur++;}//比基准值大else{cur++;}}//cur已经越界,交换key和prev位置数据,返回prev就是基准值下标Swap(&arr[key], &arr[prev]);return prev;
}

排序成功~

快速排序特性总结

1. 时间复杂度: O(N * logN)
2. 空间复杂度: O(logN)

归并排序

基本思想

归并排序(MERGE-SORT)是 建立在归并操作上的⼀种有效的排序算法 ,该算法是采用分治法(Divide and Conquer)的⼀个典型应用。
将已有序的子序列合并,得到完全有序的序列 ;即 先使每个子序列有序,再使子序列段间有序 ,若将两个有序表合并成⼀个有序表,称为二路归并。
比如下面的例子:

不断地二分最终得到每个子序列只有一个元素(只有一个元素肯定是有序的),然后合并序列成为有序的序列~这里毫无疑问就需要使用到递归了!我们来写写代码~

代码


//归并排序
//合并序列成有序的序列,需要一个临时数组来保存
void _MergeSort(int* arr, int left, int right, int* tmp)
{//相等说明只有一个元素,直接返回if (left >= right){return;}//分成子序列int mid = left + (right - left) / 2;//这种写法好处是避免数据过大引起存储不了//【left,mid】  【mid+1,right】_MergeSort(arr, left, mid, tmp);_MergeSort(arr, mid + 1, right, tmp);//合并左右有序子序列int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;int index = begin1;//保存数组的下标//合并while (begin1 <= end1 && begin2 <= end2){//前面序列元素小,就放到tmp数组中if (arr[begin1] < arr[begin2]){tmp[index++] = arr[begin1++];//别忘记下标往后面走}else{tmp[index++] = arr[begin2++];//别忘记下标往后面走}}//已经跳出循环,说明越界了// 处理还有剩余元素的情况while (begin1 <= end1){tmp[index++] = arr[begin1++];}while (begin2 <= end2){tmp[index++] = arr[begin2++];}//将排序好的tmp给arrfor (int i = left; i <= right; i++){arr[i] = tmp[i];}
}//归并排序
void MergeSort(int* arr,int sz)
{//开辟一块空间存排序的数组int* tmp = (int*)malloc(sizeof(int) * sz);if (tmp == NULL){perror("malloc fail");exit(1);}//调用排序方法_MergeSort(arr, 0, sz - 1, tmp);//动态申请的空间一定要释放,并且及时置为空free(tmp);tmp = NULL;
}

排序成功~

时间复杂度

递归的时间复杂度=递归的次数*一次递归的时间复杂度

这与我们前面的快速排序时间复杂度推导方法是一样的,也是一个二分递归的过程~我们认为递归的次数为logN一次递归时间复杂度O(N)时间复杂度也为(N*logN),这也是一个比较快速的排序算法

比较时间

到目前为止,我们已经知道了解了七种排序算法~这些排序算法都是比较排序的方法~想看更多的内容~请看下一篇详解~


♥♥♥本篇博客内容结束,期待与各位优秀程序员交流,有什么问题请私信♥♥♥

♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥

✨✨✨✨✨✨个人主页✨✨✨✨✨✨


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

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

相关文章

MySQL主从复制

主节点 server id 1. 更改server id 指定二进制日志文件目录 [rootmaster ~]#vim /etc/my.cnf.d/mariadb-server.cnf [mysqld] server-id8 log-bin 2. 新建目录并赋予权限 mkdir -p /data/mysql/logbin/chowm -R mysql.mysql /data/mysql/ 3. 重新启动 systemctl enabl…

酥皮点心,味蕾上的享受

甘肃酥皮点心承载着悠久的历史与深厚的文化底蕴。它起源于古老的丝绸之路&#xff0c;在岁月的长河中&#xff0c;经过一代又一代甘肃人的传承与创新&#xff0c;成为了如今令人陶醉的美食。每一块酥皮点心都仿佛在诉说着过去的故事&#xff0c;见证着甘肃大地的变迁与发展。食…

SpringCloud核心组件(三)

文章目录 Nacos 注册中心1. 简介功能1.服务发现和服务健康监测2.动态配置服务3. 动态 DNS 服务4. 服务及其元数据管理 优势设计理念易于使用面向标准高可用方便扩展 部署模式单机模式集群模式 Nacos 生态&#xff1a; 2. 安装 Nacos第一步&#xff1a;拉取镜像第二步&#xff1…

反射、枚举以及lambda表达式

反射、枚举以及lambda表达式 反射定义用途反射基本信息反射相关的类Class类(反射机制的起源)Class类中的相关方法 反射示例获得Class对象的三种方式反射的使用 反射优点和缺点重点总结 枚举的使用背景及定义使用枚举优点缺点枚举和反射总结单例模式 Lambda表达式背景Lambda表达…

Java学习Day60:回家!(ElasticStatic)

1.what is ElasticStatic The Elastic Stack, 包括 Elasticsearch、 Kibana、 Beats 和 Logstash&#xff08;也称为 ELK Stack&#xff09;。能够安全可靠地获取任何来源、任何格式的数据&#xff0c;然后实时地对数据进行搜索、分析和可视化。 Elaticsearch&#xff0c;简称…

java八股-jvm入门-程序计数器,堆,元空间,虚拟机栈,本地方法栈,类加载器,双亲委派,类加载执行过程

文章目录 PC Register堆虚拟机栈方法区(Metaspace元空间双亲委派机制类加载器 类装载的执行过程 PC Register 程序计数器&#xff08;Program Counter Register&#xff09;是 Java 虚拟机&#xff08;JVM&#xff09;中的一个组件&#xff0c;它在 JVM 的内存模型中扮演着非常…

Docker 篇-Docker 详细安装、了解和使用 Docker 核心功能(数据卷、自定义镜像 Dockerfile、网络)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 Docker 概述 1.1 Docker 主要组成部分 1.2 Docker 安装 2.0 Docker 常见命令 2.1 常见的命令介绍 2.2 常见的命令演示 3.0 数据卷 3.1 数据卷常见的命令 3.2 常见…

恶意PDF文档分析记录

0x1 PDF是什么 PDF&#xff08;便携式文件格式&#xff0c;Portable Document Format&#xff09;是由Adobe Systems在1993年用於文件交换所发展出的文件格式。 因为PDF的文件格式性质广泛用于商业办公&#xff0c;引起众多攻击者对其开展技术研究&#xff0c;在一些APT&#…

SpringBoot集成itext导出PDF

添加依赖 <!-- PDF导出 --><dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.11</version></dependency><dependency><groupId>com.itextpdf</groupId>&l…

不想后悔,混动车这样买

文 | AUTO芯球 作者 | 雷慢 不买一辆混动车&#xff0c; 你永远不知道自己有多抠&#xff01; 我有个跑滴滴的小伙伴&#xff0c; 他说近10年来最后悔的事&#xff0c; 就是没买个纯电续航长点的混动车&#xff0c; 怎么回事呢&#xff0c; 这个小伙伴今年买了辆纯电续航…

第一个C语言程序,带领我们进入C语言的大门!

第一个C语言程序&#xff0c;带领我们进入C语言的大门&#xff01; 我们有两种方式从计算机获得信息&#xff1a;一是看屏幕上的文字、图片、视频等&#xff0c;二是听从喇叭发出来的声音。让喇叭发出声音目前还比较麻烦&#xff0c;我们先来看看如何在屏幕上显示一些文字吧。p…

大模型到底是什么?小白也能看懂的科普贴,让你从大模型入门到大模型精通

&#xff08;图源网络&#xff09; 从去年到今年&#xff0c;大模型、chatGPT等概念和技术越来越火&#xff0c;但是像笔者一样的技术小白一直对大模型是一种似懂非懂的状态。鉴于最近在做基于大模型和Agent的上层AI应用&#xff0c;如若不了解底层概念&#xff0c;始终还是会…

qt QStandardPaths 详解

1、概述 QStandardPaths是Qt框架中的一个类&#xff0c;它提供了一种跨平台的方式来访问标准的位置&#xff0c;如应用程序的数据目录、配置目录、缓存目录、临时文件目录等。这些位置通常是用户特定的&#xff0c;并且遵循操作系统的标准和惯例。通过使用QStandardPaths&…

对node工程进行压力测试与性能分析

在系统上线前&#xff0c;为了看下系统能承受多大的并发和并发下的负载情况&#xff0c;进行了一轮压测。在压测过程中&#xff0c;发现服务器的cpu飚的的非常高&#xff0c;而tps&#xff0c;接口耗时、服务可用等都是正常的&#xff0c;卧槽&#xff0c;这就奇了怪了&#xf…

昆明华厦眼科医院在大观小学开展近视科普教育讲座

为响应全社会对青少年近视防控的号召&#xff0c;昆明华厦眼科医院组织了一场近视科普教育讲座&#xff0c;活动走进大观小学&#xff0c;旨在通过专业的眼科知识普及&#xff0c;提升小学生们对眼健康的认知&#xff0c;培养他们爱眼护眼的意识。讲座结束后还特地为教师群体进…

MPLS基本原理

Multiprotocol Label Switching 多标签交换 前言 MPLS位于TCP/IP协议栈中的链路层和网络层之间,用于向IP层提供连接服务,同时又从链路层达到服务.MPLS以标签交换代替IP转发. MPLS并不是一种业务或者应用,它实际上是一种隧道技术.这种技术不仅支持多种高层协议与业务,而且在一…

《MarsCode:编程领域的智能新势力》

《MarsCode&#xff1a;编程领域的智能新势力》 一、MarsCode 的诞生与发展&#xff08;一&#xff09;逐步崛起的历程&#xff08;二&#xff09;与各方的合作与影响 二、MarsCode 的独特魅力&#xff08;一&#xff09;强大的功能特点&#xff08;二&#xff09;多语言支持与…

PyInstaller未包含预编译引导程序

1 现象 在使用 PyInstaller 打包 Python 应用时&#xff0c;遇到了一个错误&#xff0c;错误信息如下&#xff1a; Fatal error: PyInstaller does not include a pre-compiled bootloader for your platform. For more details and instructions how to build the bootloade…

华为HCIP-openEuler考试内容大纲:备考必看!

华为HCIP-openEuler认证考试作为ICT领域的一项重要技术认证&#xff0c;已经成为越来越多IT从业者追求的目标。无论你是想提升自己的技术能力&#xff0c;还是为了未来的职业发展&#xff0c;HCIP-openEuler都是一个极具价值的认证。那么&#xff0c;如何高效备考&#xff0c;顺…

编程之路,从0开始:知识补充篇

Hello大家好&#xff0c;很高兴我们又见面了&#xff01; 给生活添点passion&#xff0c;开始今天的编程之路&#xff01; 这一篇我们来补充一下在之前篇目没讲到的知识&#xff0c;并结合一些码友的私信提问和我在编程中遇到的问题&#xff0c;做一些易错点或易混点的讲解。 …