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


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

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

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

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

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

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

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

目录

交换排序

冒泡排序

基本思想

代码

时间复杂度

快速排序

基本思想

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.mzph.cn/bicheng/60679.shtml

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

相关文章

MongoDB在现代Web开发中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 MongoDB在现代Web开发中的应用 MongoDB在现代Web开发中的应用 MongoDB在现代Web开发中的应用 引言 MongoDB 概述 定义与原理 发展…

CSS的综合应用例子(网页制作)

这是html的一些最基本内容的代码&#xff1a; <!DOCTYPE html> <html lang"zh"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <t…

【设计模式】行为型模式(二):策略模式、命令模式

行为型模式&#xff08;二&#xff09;&#xff1a;策略模式、命令模式 3.策略模式&#xff08;Strategy&#xff09;3.1 示例3.1.1 定义策略接口3.1.2 实现具体策略3.1.3 定义上下文类3.1.4 客户端代码3.1.5 输出结果 3.2 总结3.2.1 优点3.2.2 缺点 4.命令模式&#xff08;Com…

rocketmq——docker-compose安装

rocketmq安装 创建文件夹&#xff0c;这里我们分别部署namesrv和broker 1、namesrv.conf listenPort98762、broker.conf # 所属集群名字 brokerClusterNameDefaultCluster # broker 名字&#xff0c;注意此处不同的配置文件填写的不一样&#xff0c;如果在 broker-a.propert…

OpenGL ES 文字渲染方式有几种?

在音视频或 OpenGL 开发中,文字渲染是一个高频使用的功能,比如制作一些酷炫的字幕、为视频添加水印、设置特殊字体等等。 实际上 OpenGL 并没有定义渲染文字的方式,所以我们最能想到的办法是:将带有文字的图像上传到纹理,然后进行纹理贴图。 本文分别介绍下在应用层和 C+…

飞牛云fnOS本地部署WordPress个人网站并一键发布公网远程访问

文章目录 前言1. Docker下载源设置2. Docker下载WordPress3. Docker部署Mysql数据库4. WordPress 参数设置5. 飞牛云安装Cpolar工具6. 固定Cpolar公网地址7. 修改WordPress配置文件8. 公网域名访问WordPress 前言 本文旨在详细介绍如何在飞牛云NAS上利用Docker部署WordPress&a…

字符串处理指南:Air780E软件的全新视角

今天我会把 Air780E软件的字符串处理详细解析&#xff0c;指南如下&#xff1a; 1、Lua字符串介绍 关于字符串&#xff0c;Lua提供了一些灵活且强大的功能&#xff0c;一些入门知识如下&#xff1a; 1.1 字符串定义 在Lua中&#xff0c;字符串可以用单引号或双引号"来定…

C/C++语言 多项式加法和乘法

多项式加法和乘法 多项式的加法题目描述输入输出样例步骤代码段全局变量设定新建结点合并链表 完整代码 多项式乘法题目描述输入输出样例代码段计算两多项式结果输入 完整代码 多项式的加法 题目描述 输入输出 样例 步骤 总体思想是用链表来做 ① 我们发现输入样例中&#xf…

第十四章 Spring之假如让你来写AOP——雏形篇

Spring源码阅读目录 第一部分——IOC篇 第一章 Spring之最熟悉的陌生人——IOC 第二章 Spring之假如让你来写IOC容器——加载资源篇 第三章 Spring之假如让你来写IOC容器——解析配置文件篇 第四章 Spring之假如让你来写IOC容器——XML配置文件篇 第五章 Spring之假如让你来写…

微信小程序——实现二维码扫描功能(含代码)

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

linux rocky 9.4部署和管理docker harbor私有源

文章目录 Harbor简介安装Harbor技术细节1.安装系统(略),设置主机名和IP2.安装docker3.安装docker-compose4.安装Harbor私有源仓库5 测试登录1.本机登录2.客户端登录Harbor服务器配置docker源1. 下载镜像2.把镜像上传到Harbor私有仓库源3.客户端下载镜像,并且启动容器linux …

【Elasticsearch入门到落地】1、初识Elasticsearch

一、什么是Elasticsearch Elasticsearch&#xff08;简称ES&#xff09;是一款非常强大的开源搜索引擎&#xff0c;可以帮助我们从海量数据中快速找到需要的内容。它使用Java编写&#xff0c;基于Apache Lucene来构建索引和提供搜索功能&#xff0c;是一个分布式、可扩展、近实…

【算法一周目】双指针(2)

目录 有效三角形的个数 解题思路 C代码实现 和为s的两个数字 解题思路 C代码实现 三数之和 解题思路 C代码实现 四数之和 解题思路 C代码实现 有效三角形的个数 题目链接&#xff1a;611. 有效三角形的个数题目描述&#xff1a;给定一个包含非负整数的数组nums&…

单体架构 IM 系统之 Server 节点状态化分析

基于 http 短轮询模式的单体架构的 IM 系统见下图&#xff0c;即客户端通过 http 周期性地轮询访问 server 实现消息的即时通讯&#xff0c;也就是我们前面提到的 “信箱模型”。“信箱模型” 虽然实现非常容易&#xff0c;但是消息的实时性不高。 我们在上一篇文章&#xff08…

让AI为你发声!Windows电脑快速部署ChatTTS文本转语音神器

文章目录 前言1. 下载运行ChatTTS模型2. 安装Cpolar工具3. 实现公网访问4. 配置ChatTTS固定公网地址 前言 嘿&#xff0c;朋友们&#xff01;今天我们来聊聊如何在Windows系统上快速搭建ChatTTS&#xff0c;一个超酷的开源文本转语音项目。更棒的是&#xff0c;我们还可以用Cp…

RSTP的配置

RSTP相对于STP在端口角色、端口状态、配置BPDU格式、配置BPDU的处理方式、快速收敛机制、拓扑变更机制和4种保护特性方面的详细改进说明&#xff1a; 端口角色&#xff1a; STP中定义了三种端口角色&#xff1a;根端口&#xff08;Root Port&#xff09;、指定端口&#xff0…

elementui el-table中给表头 el-table-column 加一个鼠标移入提示说明

前言 在使用el-table 表格中有些表格的表头需要加入一些提示&#xff0c;鼠标移入则出现提示&#xff0c;非常实用&#xff0c;我是通过el-table中的el-tooltip实现的&#xff0c;以下的效果预览 代码实现 <el-table ref"multipleTable" :data"data"…

ubuntu18.04 安装与卸载NCCL conda环境安装PaddlePaddle

cuda版本11.2 说明PaddlePaddle需要安装NCCL 1、Log in | NVIDIA Developer 登录官网 找到对应版本 官方提供了多种安装方式&#xff0c;本文使用Local installers (x86)本地安装 点击对应的版本下载如&#xff1a; nccl-local-repo-ubuntu1804-2.8.4-cuda11.2_1.0-1_amd6…

机器学习—决定下一步做什么

现在已经看到了很多不同的学习算法&#xff0c;包括线性回归、逻辑回归甚至深度学习或神经网络。 关于如何构建机器学习系统的一些建议 假设你已经实现了正则化线性回归来预测房价&#xff0c;所以你有通常的学习算法的成本函数平方误差加上这个正则化项&#xff0c;但是如果…

【Rust中的项目管理】

Rust中的项目管理 前言Package&#xff0c;Crate&#xff0c;Module &use &#xff0c;Path通过代码示例解释 Crate&#xff0c;Module &#xff0c;use&#xff0c;Path创建一个package&#xff1a;代码组织化skin.rs 中的代码struct & enum 相对路径和绝对路径引用同…