排序算法:快速排序(三种排序方式、递归和非递归)

朋友们、伙计们,我们又见面了,本期来给大家解读一下有关排序算法的相关知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

C 语 言 专 栏:C语言:从入门到精通

数据结构专栏:数据结构

个  人  主  页 :stackY、

目录

前言:

1.快速排序

1.1递归版本

1.1.1hoare版本

代码演示:

1.1.2挖坑法

 代码演示:

1.1.3前后指针(下标)版本

 代码演示:

1.1.4时间复杂度 

1.1.5快速排序的优化

优化完整代码: 

 1.2非递归版本

代码演示: 


前言:

在前面的文章我们分别介绍了插入排序和选择排序,那么在本期的学习中我们来了解一下快速排序,以及快速排序的三种实现方式以及递归和非递归的实现,话不多说,正文开始:

快速排序和冒泡排序是属于交换排序这个范畴内的,冒泡排序在前面的文章中非常细致的讲解过,那么在这里就不做赘述,直接开始快速排序即可:

1.快速排序

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

1.1递归版本

首先我们使用递归的方式来实现快速排序,后面再使用非递归来完成,因为递归太深是会有风险的。在递归版本的快速排序下对于排序区间的划分有三种方法:1.hoare版本、2.挖坑法、3.前后指针法。那么我们来一一学习。

1.1.1hoare版本

我们要对一段数据进行排升序的处理:首先我们取一个关键值key(基准值),那么我们一般取的是最左边或者最右边的数据,然后设置两个下标:左下标和右下标,左下标,进行一趟排序:左下标从左向右开始找比key大的,右下标从右向左找比key小的,然后左右下标的值交换,然后再重复此过程,当左右下标重叠时,将key与重叠位置的数据进行交换。一趟排序之后得到的结果就是key左边的都比key小,右边的都比key大,但是并不一定是有序,然后使用左右下标重合的位置将数据分为两个区间,然后再重复上述步骤,将这两个区间变为有序,那么左右区间都有序,数据的整体就有序了。

那么在这就存在几个问题:

1.怎么保证重叠位置的值就一定比key小呢?

2.怎么让左右区间变为有序?

 1.怎么保证重叠位置的值就一定比key小呢?

我们可以先让右下标开始走,然后让左下标开始走,因为右下标找的是比key小的值,先让右下标找到比key小的值,当左下标找到比key大的值时,两个值交换,当左下标找不到比key大的值时,这时左下标肯定和右下标重合,这时重合的就是右下标找到的比key小的,这时交换即可达到重叠时的数据比key小。

 2.怎么让左右区间变为有序?

通过一次排序让数据变为两个区间,那么这两个区间再分别进行一次排序,再将它分为若干个小区间,这若干个小区间再进行排序,直到分出的区间只剩一个值或者分出的区间不存在,这时就达到了有序,那么就需要使用递归来进行左右区间的排序,递归截止的条件就是区间不存在或者区间里面只有一个数据。

代码演示:

void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}// 1. hoare版本
int PartSort1(int* a, int left, int right)
{//设置关键字int keyi = left;while (left < right){//右边先走//找比key小的while (left < right && a[keyi] <= a[right]) //先得判断下标是否合理{right--;}//找比key大的while (left < right && a[keyi] >= a[left]){left++;}//交换Swap(&a[left], &a[right]);}//交换重叠位置的数据和keySwap(&a[keyi], &a[right]);//将重叠位置返回return right;
}void QuickSort(int* a, int begin, int end)
{//递归截止条件if (begin >= end){return;}//区间的划分int keyi = PartSort1(a, begin, end);//将整个数据划分为[begin, keyi-1] keyi [keyi+1, end]//递归右区间QuickSort(a, begin, keyi - 1);//递归左区间QuickSort(a, keyi + 1, end);}void PrintArry(int* a, int n)
{for (int i = 0; i < n; i++){printf("%d ", a[i]);}printf("\n");
}void TestQuickSort()
{int a[] = { 3,2,5,10,6,8,9,7,1,4 };QuickSort(a, 0, sizeof(a) / sizeof(int) - 1);PrintArry(a, sizeof(a) / sizeof(int));
}int main()
{TestQuickSort();return 0;
}

 1.1.2挖坑法

由于hoare版本的方法需要注意左右下标出发的先后顺序,那么就针对这一问题,有了一种新的方法:挖坑法

同样的还是将最左边的数据作为关键数据,并且将它先保存,然后将这个位置设置为坑,然后设置左右两个下标,由于左边有坑,所以右下标先走,向前找比key小的值,找到了之后将这个值放在坑中,然后这个值的位置就形成了新的坑,然后左下标开始向后找比key大的,找到了之后将这个值放在新的坑中,此时又形成了一个坑,继续重复这个过程,当左右下标重叠时,再将刚开始保存的key放在这个坑中,即可完成一次排序,然后关键数据的这个位置又将数据分为两个区间,然后使用递归继续排序左右区间

 代码演示:

// 2. 挖坑法
int PartSort2(int* a, int left, int right)
{//保存关键值int key = a[left];//设置坑int hole = left;//一次排序while (left < right){//右边找小while (left < right && a[right] > key){right--;}//找到了将其放入坑中,然后更新坑的位置a[hole] = a[right];hole = right;//左边找大while (left < right && a[left] < key){left++;}a[hole] = a[left];hole = left;}//最后把关键数据再放到坑中a[hole] = key;key = right;return key;
}void QuickSort(int* a, int begin, int end)
{//递归截止条件if (begin >= end){return;}//区间的划分int keyi = PartSort2(a, begin, end);//将整个数据划分为[begin, keyi-1] keyi [keyi+1, end]//递归右区间QuickSort(a, begin, keyi - 1);//递归左区间QuickSort(a, keyi + 1, end);}

1.1.3前后指针(下标)版本

同样的还是取最左边作为关键数据,然后设置两个下标,一个指向起始位置(prev),一个指向起始位置的后一个位置(cur),然后比较cur指向的数据与key的大小,若cur小于key,则prev++,并且prev指向的数据和cur指向的数据进行交换(如果prev和cur指向的时同一个数据那么便不用交换),然后cur++,如果cur指向的数据大于key,则cur++,当cur越界时,将prev指向的数据和key交换,这时prev指向的位置就可以作为新的key将整个数据分为两个区间,这时就可使用递归继续来排序它的左右区间。

 代码演示:

// 3.前后指针版本
int PartSort3(int* a, int left, int right)
{//设置关键数据int keyi = left;//前后指针(下标)int prev = left;int cur = left + 1;//判断cur的合法性while (cur <= right){//如果cur指的数据小于关键数据且prev不等于cur即可完成交换if (a[cur] < a[keyi] && ++prev != cur){Swap(&a[cur], &a[prev]);}cur++;}//cur越界之后再次交换prev与keySwap(&a[keyi], &a[prev]);keyi = prev;//返回新的关键位置return keyi;
}void QuickSort(int* a, int begin, int end)
{//递归截止条件if (begin >= end){return;}//区间的划分int keyi = PartSort3(a, begin, end);//将整个数据划分为[begin, keyi-1] keyi [keyi+1, end]//递归右区间QuickSort(a, begin, keyi - 1);//递归左区间QuickSort(a, keyi + 1, end);}

1.1.4时间复杂度 

快速排序的递归版本与二叉树学习中的前序遍历逻辑相似,只需要注意区间的划分,那么根据二叉树的递归,一共需要递归\log N次,每一次进行遍历排序需要N次,那么快速排序的时间复杂度是:O(N* \log N) ,但是计算时间复杂度是按照最坏情况来考虑,当一组数据是有序的情况再来使用快速排序来排序这时它的时间复杂度就会变为O(N^2),这时递归就需要N次,每一次的遍历也需要N次,所以就是N^2,因为我们每一次取到的关键数据都是左边的数据,那么就需要对如何取关键数据进行改进。

1.1.5快速排序的优化

为了优化快速排序最坏情况下的时间复杂度,那么我们需要对快速排序如何选择key做出改进:

我们采用的是三数取中选key:

选出最左边的数(left)、最右边的数(right)、中间数据(mid)这三个数据中大小顺序位于中间的数据作为key,然后与最左边的数进行交换,这样就达到了优化。

优化完整代码: 

void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}//三数取中选key
int GetMidIndex(int* a, int left, int right)
{int mid = left + (right - left) / 2;if (a[left] < a[mid]){if (a[right] > a[mid]){return mid;}else if (a[right] < a[left]){return left;}elsereturn right;}else{if (a[right] < a[mid]){return mid;}else if (a[right] > a[left]){return left;}elsereturn right;}
}// 1. hoare版本
int PartSort1(int* a, int left, int right)
{//设置关键字int midi = GetMidIndex(a, left, right);Swap(&a[left], &a[midi]);int keyi = left;while (left < right){//右边先走//找比key小的while (left < right && a[keyi] <= a[right]) //先得判断下标是否合理{right--;}//找比key大的while (left < right && a[keyi] >= a[left]){left++;}//交换Swap(&a[left], &a[right]);}//交换重叠位置的数据和keySwap(&a[keyi], &a[right]);//将重叠位置返回return right;
}// 2. 挖坑法
int PartSort2(int* a, int left, int right)
{int midi = GetMidIndex(a, left, right);Swap(&a[left], &a[midi]);//保存关键值int key = a[left];//设置坑int hole = left;//一次排序while (left < right){//右边找小while (left < right && a[right] > key){right--;}//找到了将其放入坑中,然后更新坑的位置a[hole] = a[right];hole = right;//左边找大while (left < right && a[left] < key){left++;}a[hole] = a[left];hole = left;}//最后把关键数据再放到坑中a[hole] = key;key = right;return key;
}// 3.前后指针版本
int PartSort3(int* a, int left, int right)
{int midi = GetMidIndex(a, left, right);Swap(&a[left], &a[midi]);//设置关键数据int keyi = left;//前后指针(下标)int prev = left;int cur = left + 1;//判断cur的合法性while (cur <= right){//如果cur指的数据小于关键数据且prev不等于cur即可完成交换if (a[cur] < a[keyi] && ++prev != cur){Swap(&a[cur], &a[prev]);}cur++;}//cur越界之后再次交换prev与keySwap(&a[keyi], &a[prev]);keyi = prev;//返回新的关键位置return keyi;
}void QuickSort(int* a, int begin, int end)
{//递归截止条件if (begin >= end){return;}//区间的划分int keyi = PartSort3(a, begin, end);//将整个数据划分为[begin, keyi-1] keyi [keyi+1, end]//递归右区间QuickSort(a, begin, keyi - 1);//递归左区间QuickSort(a, keyi + 1, end);}

优化过后快速排序的时间复杂度最坏的情况下也是O(N*\log N

 1.2非递归版本

由于递归存在风险,递归太深会出现问题,所以我们需要写出快速排序的非递归版本,快速排序的递归版本跟二叉树的前序遍历逻辑相似,那么在非递归版本中我们需要借助一个数据结构来完成非递归版本的快速排序。

在之前的数据结构中我们学到了一个叫栈的数据结构,它的特点就是先进后出,因此我们可以借助于栈来实现快速排序的非递归版本。​​​​​​​

在递归的版本中我们通过第一次排序整个区间然后取得一个关键值的位置,通过这个关键值将数据再次分为两个区间,然后依次递归继续排序这两个区间,并且在这两个区间内会再次划分区间,直到全部排序完毕。根据这个特点我们也可以根据栈的特性来进行实现,首先我们创建一个栈,然后先将0存放在栈中,再将9存放在栈中,那么就可以通过0和9来访问数据了,如果栈不为空,我们就取栈顶的元素9作为右下标,然后将其移除,再取栈顶元素0作为左下标,然后经过一次排序,得到了新的关键值,那么这个关键值会将区间分为两个部分,前面的部分是[left,keyi-1],后面的部分是[keyi+1,right],这时就需要对区间的合法性做出判断,如果left小于keyi-1,那么区间合理,可以继续将left作为左下标,将keyi-1作为右下标继续排序,如果right>keyi+1,那么区间合理,可以继续将keyi+1作为左下标,将right作为右下标继续排序,如果两个条件都不满足那么表示排序完成。

代码演示: 

// 1. hoare版本
int PartSort1(int* a, int left, int right)
{//设置关键字int midi = GetMidIndex(a, left, right);Swap(&a[left], &a[midi]);int keyi = left;while (left < right){//右边先走//找比key小的while (left < right && a[keyi] <= a[right]) //先得判断下标是否合理{right--;}//找比key大的while (left < right && a[keyi] >= a[left]){left++;}//交换Swap(&a[left], &a[right]);}//交换重叠位置的数据和keySwap(&a[keyi], &a[right]);//将重叠位置返回return right;
}//快速排序的非递归版本
void QuickSortNonR(int* a, int begin, int end)
{//创建栈Stack st;StackInit(&st);//先将整个区间放入栈StackPush(&st, begin);StackPush(&st, end);//进行排序while (!StackEmpty(&st)){//取栈顶元素作为右下标int right = StackTop(&st);//删除栈顶元素StackPop(&st);//取栈顶元素作为左下标int left = StackTop(&st);//删除栈顶元素StackPop(&st);//进行一次排序int keyi = PartSort1(a, left, right);//对获得的keyi是否合理进行判断if (left < keyi - 1){//重新将新的区间放入栈中//注意:顺序不能交换StackPush(&st, left);StackPush(&st, keyi - 1);}if (right > keyi + 1){//重新将新的区间放入栈中//注意:顺序不能交换StackPush(&st, keyi + 1);StackPush(&st, right);}}StackDestroy(&st);
}

注意:这里的先放左区间和先放右区间跟后面的代码是要呼应的,顺序不能随便更改。

在这里我就不展示栈接口的代码了,大家可以去栈和队列 这一篇博客找源码。

快速排序的特性总结:

1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速
2. 时间复杂度:O(N*logN)
3. 空间复杂度:O(logN)
4. 稳定性:不稳定

 

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持! 

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

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

相关文章

GeoNet: Unsupervised Learning of Dense Depth, Optical Flow and Camera Pose 论文阅读

论文信息 题目&#xff1a;GeoNet: Unsupervised Learning of Dense Depth, Optical Flow and Camera Pose 作者&#xff1a;Zhichao Yin and Jianping Shi 来源&#xff1a;CVPR 时间&#xff1a;2018 Abstract 我们提出了 GeoNet&#xff0c;这是一种联合无监督学习框架&a…

vim常用操作

一、Esc键 & 命令模式 1.撤销&#xff1a;u 恢复撤销&#xff1a;Ctrl r 2.定位 行首&#xff1a;0 行尾&#xff1a;$ 第7行&#xff1a;7G 3.编辑 下行开始插入&#xff1a; o 删除行&#xff1a;dd 复制3行并粘贴&#xff1a;3yy ---> p 复制单词并粘贴&#…

蓝桥杯官网练习题(玩具蛇)

题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 小蓝有一条玩具蛇&#xff0c;一共有 16 节&#xff0c;上面标着数字 1 至 16。每一节都是一个正方形的形状。相邻的两节可以成直线或者成 90 度角。 小蓝还有一个…

时序预测 | MATLAB实现ELM极限学习机时间序列预测未来

时序预测 | MATLAB实现ELM极限学习机时间序列预测未来 目录 时序预测 | MATLAB实现ELM极限学习机时间序列预测未来预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.MATLAB实现ELM极限学习机时间序列预测未来&#xff1b; 2.运行环境Matlab2018及以上&#xff0c;data为数…

【漏洞复现】H3C路由器信息泄露任意用户登录

漏洞描述 通过访问特地址得到密码可进行登录。 免责声明 技术文章仅供参考&#xff0c;任何个人和组织使用网络应当遵守宪法法律&#xff0c;遵守公共秩序&#xff0c;尊重社会公德&#xff0c;不得利用网络从事危害国家安全、荣誉和利益&#xff0c;未经授权请勿利用文章中…

CASAIM与南京航空航天大学在自动化叶片曲面分析系统开展合作,推动航空航天发动机零部件自动化3D检测进程

近期&#xff0c;CASAIM与南京航空航天大学在自动化叶片曲面分析系统展开深入合作&#xff0c;充分发挥双方在航空航天和智能检测领域优势&#xff0c;共同推动航空航天发动机零部件自动化3D检测进程。 南京航空航天大学创建于1952年10月&#xff0c;是新中国自己创办的第一批…

pyCharm远程DEBUG

第一步&#xff0c;添加一个远程机器的解释器 ssh 远程机器解释器添加&#xff0c; 我本地ssh有配置目标机器。 如果没配置&#xff0c;那就选着new server configuration 新增一个。 interpreter 指定远程机器python&#xff0c; &#xff08;机器上有多个版本python里尤其要…

红队打靶:ConnectTheDots打靶思路详解(vulnhub)

目录 写在开头 第一步&#xff1a;主机发现和端口扫描 第二步&#xff1a;FTP和NFS渗透&#xff08;失败&#xff09; 第三步&#xff1a;web渗透 第四步&#xff1a;jsfuck解码 第五步&#xff1a;再次FTP渗透与莫尔斯电码解码 第六步&#xff1a;vim读取断电swp文件…

批量采集的时间管理与优化

在进行大规模数据采集时&#xff0c;如何合理安排和管理爬取任务的时间成为了每个专业程序员需要面对的挑战。本文将分享一些关于批量采集中时间管理和优化方面的实用技巧&#xff0c;帮助你提升爬虫工作效率。 1. 制定明确目标并设置合适频率 首先要明确自己所需获取数据的范…

Bazzite 发行版 1.0 发布,可让 Linux 游戏机实现 Steam Deck 桌面环境体验

导读近日消息&#xff0c;当下 Steam Deck 掌机的性能已经有所过时&#xff0c;不过许多玩家为了追求原生 SteamOS 体验依然选择购买该掌机&#xff0c;V社此前曾表示&#xff0c;“SteamOS 简化了在手持设备上玩 PC 游戏的过程”&#xff0c;玩家在用 Steam Deck 玩游戏时&…

BCSP-玄子Share-Java框基础_工厂模式/代理模式

三、设计模式 3.1 设计模式简介 软件设计中的三十六计是人们在长期的软件开发中的经验总结是对某些特定问题的经过实践检验的特定解决方法被广泛运用在 Java 框架技术中 3.1.1 设计模式的优点 设计模式是可复用的面向对象软件的基础可以更加简单方便地复用成功的设计和体系…

springcloud-Eureka

1.Eureka注册中心 1.1 简介与依赖导入 1.2 服务注册与发现 启动eureka模块 访问Eureka 将user-service,book-service,borrow-service作为eureka的客户端&#xff0c;先导包。三个导入方式一样。 配置文件&#xff0c;三个模块下都一样配置 然后分别启动三个模块 发现注册…

SpringMvc--CRUD

目录 一.什么是SpringMvc--CRUD 二.前期准备 公共页面跳转(专门用来处理页面跳转) 三.ssm之CRUD后端实现 配置pom.xml 双击mybatis-generator:generate自动生成mapper 编写generatorConfig.xml 项目结构 编写PagerAspect切面类 编写hpjyBiz接口类 编写hpjyBizImpl接…

JavaWeb_LeadNews_Day11-KafkaStream实现实时计算文章分数

JavaWeb_LeadNews_Day11-KafkaStream实现实时计算文章分数 KafkaStream概述案例-统计单词个数SpringBoot集成 实时计算文章分值来源Gitee KafkaStream 概述 Kafka Stream: 提供了对存储与Kafka内的数据进行流式处理和分析的功能特点: Kafka Stream提供了一个非常简单而轻量的…

Pytorch 多卡并行(1)—— 原理简介和 DDP 并行实践

近年来&#xff0c;深度学习模型的规模越来越大&#xff0c;需要处理的数据也越来越多&#xff0c;单卡训练的显存空间和计算效率都越来越难以满足需求。因此&#xff0c;多卡并行训练成为了一个必要的解决方案本文主要介绍使用 Pytorch 的 DistributedDataParallel&#xff08…

合宙Air724UG LuatOS-Air LVGL API控件-表格(Table)

表格&#xff08;Table&#xff09; 示例代码 --创建表格Table1 lvgl.table_create(lvgl.scr_act(),nil)--设置表格为4行5列lvgl.table_set_row_cnt(Table1,4)lvgl.table_set_col_cnt(Table1,5)--给每个单元格赋值lvgl.table_set_cell_value(Table1, 0, 0, "选手")l…

Android之RecyclerView仿ViewPage滑动

文章目录 前言一、效果图二、实现步骤1.xml主布局2.所有用到的drawable资源文件3.xml item布局4.adapter适配器5.javabean实体类6.activity使用 总结 前言 我们都知道ViewPageFragment滑动&#xff0c;但是的需求里面已经有了这玩意&#xff0c;但是在Fragment中还要有类似功能…

基于3D扫描和3D打印的产品逆向工程实战【数字仪表】

逆向工程是一种从物理零件创建数字设计的强大方法&#xff0c;并且可以与 3D 扫描和 3D 打印等技术一起成为原型设计工具包中的宝贵工具。 推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 3D 扫描仪可以非常快速地测量复杂的物体&#xff0c;并且在涉及现实生活参考时可以…

花生壳内网穿透+Windows系统,如何搭建网站?

1. 准备工作 在百度搜索“Win7下安装ApachePHPMySQL”&#xff0c;根据搜到的教程自行安装WAMP环境。 如果在网页上键入http://127.0.0.1/ 出现以下页面表示您的服务器已经建好&#xff0c;下一步就是关键&#xff0c;如何通过花生壳内网穿透&#xff0c;让外网的用户访问到您…

设计模式 - 责任链

一、前言 ​ 相信大家平时或多或少都间接接触过责任链设计模式&#xff0c;只是可能有些同学自己不知道此处用的是该设计模式&#xff0c;比如说 Java Web 中的 Filter 过滤器&#xff0c;就是非常经典的责任链设计模式的例子。 那么什么是责任链设计模式呢&#xff1f; ​ …