数据结构——快速排序的三种方法和非递归实现快速排序

数据结构——快速排序的三种方法和非递归实现快速排序(升序)

  • 快速排序的单趟排序
    • hoare法
    • 挖坑法
    • 前后指针法
  • 快速排序的实现
    • key基准值的选取
    • 快速排序代码
    • 快速排序的优化
  • 快速排序(非递归)

快速排序的单趟排序

hoare法

思路:从给定数组中选一个基准值key,然后定义两个下标L和R,分别从数组的两端开始向中间遍历,lL找大,R找小。R找到比key小的数就停下,L找到比key大的数就停下,然后交换两个数的值;接下来继续遍历,直到L和R相遇,按此规律你会发现相遇位置的值一定是小于等于key的,此时交换相遇位置和key的值。最后返回基准值的位置。
详细看下面的动图:
在这里插入图片描述
代码:

//单趟排序结束后将key对应得下标keyi返回,方便下一次递归得调用
int PartSort1(int* a, int left, int right)
{//三数取中选key,这里其实就是在数组中选取了一个较为中间的值作为key然后放在了数组得最左边//后面会讲为什么int keyi = GetMidNum(a, left, right);if (keyi != left){Swap(&a[keyi], &a[left]);keyi = left;}while (left < right){//key在左边右边先走   最后相遇的位置一定是比key小的或者等于key的//右边找比key小的while (left < right && a[right] >= a[keyi])//如果为顺序结构right会一直自减到-1,所以加left < right控制以一下{right--;}//左边找比key大的while (left < right && a[left] <= a[keyi]){left++;}Swap(&a[right], &a[left]);//找到后交换}Swap(&a[keyi], &a[left]);//此时left为相遇的位置,相遇的位置小于key然后交换key和相遇的位置的值//此时key的左边都是比key小的 右边都是比key大的return left;//此时left为keyi的位置
}

挖坑法

思路:选一个基准值放在最左边,然后将将坑也放在最左边,然后将基准值放在临时变量key中形成坑位a[hole],然后从左右两边向中间遍历,右边先遍历,找到比key小的就停下来,将该数据放在坑的位置,形成新的坑位;然后左边遍历,找到比key大的数就停下来,将该数据放在坑的位置,形成新的坑位。最后将坑的位置返回。
在这里插入图片描述
代码实现:

int PartSort2(int* a, int left, int right)//(挖坑法)
{int keyi = GetMidNum(a, left, right);//三数取中拿到基准值的下标int key = a[keyi];//将基准值放在key中,形成坑位//判断基准值是否在左边,若不在放在左边,也就是坑位放在最左边if (keyi != left){Swap(&a[keyi], &a[left]);//基准值放左边}int hole = left;//最初将坑放在左边while (left < right){while (left<right && a[right] >= key){right--;}a[hole] = a[right];hole = right;//坑的位置移到找到的比key小的那个位置while (left < right && a[left] <= key){left++;}a[hole] = a[left];hole = left;}a[hole] = key;return hole;
}

前后指针法

思路:从数组中选定一个基准值,放在数组的最左边。定义两个指针prev和cur,prev指向第一个数的位置,cur指向第二个数的位置,然后cur开始向后遍历,如果cur所指向的位置小于key那么,prev和cur同时++(自增1),也就是prev和cur同时向后遍历;如果cur所指向的位置大于key的话,cur向后遍历,prev不遍历,直到cur找到比key小的数,然后向后遍历一位,再交换此时cur和prev所指向位置的数,也就是保证prev在遍历的时候,遍历过的位置都是比key小的数,所以遍历结束后prev所指向的位置以及prev遍历过的位置(prev的左边)都是比key小的值,接下来交换key和prev所指向的数即可,并返回交换后key的下标。动图:
在这里插入图片描述
代码实现:

int PartSort3(int* a, int left, int right)//(前后指针法 下标法)
{int keyi = GetMidNum(a, left, right);if (keyi != left){Swap(&a[keyi], &a[left]);keyi = left;}int prev = left;int cur = prev + 1;while (cur <= right){if (a[cur] < a[keyi] && ++prev != cur)//cur找到比a[keyi]小的值的时候,prev++,若prev和cur在同一位置那么就不需要交换{Swap(&a[cur], &a[prev]);//上面判断条件中prev++过了}cur++;//两中情况cur都需要++}Swap(&a[prev], &a[keyi]);//key和prev的值交换return prev;
}

快速排序的实现

函数形参分别为待排序数组a,待排序数组的最左边数的下标left,待排序数组的最右边数的下标right。

void QuickSort(int* a, int left, int right);

不管是哪种方法都是用递归来实现的,就用hoare法来举例分析吧。
第一趟排序完后,key的左边全为比key小的数,右边全为比key大的数。接下来把数组分为key左(即下标区间为[left,key-1]的数组)和key右(下标区间为[key+1,right]的数组)两组分别进行单趟排序,然后不断递归,当递归到数组中只有一个值的时候,也就是left>=right的时候,这时候直接返回即可。
d在这里插入图片描述

key基准值的选取

一般我们会取最左边的数为基准值,这样容易理解但是,这样有时候会使递归深度很大,所以我们会选取较为中间的数来当基准值,用三数取中法来解决该问题。
递归深度对比图:
在这里插入图片描述
三数取中法:选取数组最左边最右边和中间这三个数,然后返回三数中间大小的下标。
代码实现:

int GetMidNum(int* a, int left, int right)//三个数取中间值  返回中间值的下标
{int midi = (left + right) / 2;if (a[left] < a[midi]){if (a[midi] < a[right]){return midi;//a[left[<a[midi]<a[right]}else if (a[right] < a[left]){return left;//a[right]<a[left]<a[midi]}else//a[right]>=a[left] && a[right]<=a[midi]{return right;//a[left]<a[right]<a[midi]}}else// a[left] >= a[midi]{if (a[midi] > a[right]){return midi;//a[left[<a[midi]<a[right]}else if (a[right] > a[left]){return left;//a[right]<a[left]<a[midi]}else//a[right]>=a[left] && a[right]<=a[midi]{return right;//a[left]<a[right]<a[midi]}}
}

快速排序代码

void QuickSort(int* a, int left, int right)//快速排序
{if (left >= right)return ;int begin = left;int end = right;//int keyi = PartSort1(a, begin, end);//hoare法//int keyi = PartSort2(a, begin, end);//挖坑法int keyi = PartSort3(a, begin, end);//前后指针法QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);
}

快速排序的优化

当快排递归到待排数组是一些短数组的时候,由于短数组的个数很多,再加上这些短数组都需要递归到数组中只有一个数的情况才可以返回,这时候会不断的创建函数栈帧,然后导致时间复杂度降低。因为这些待排的短数组都是一个接近于有序的的数组,用直接插入来优化更为合适。
代码实现:

void QuickSort(int* a, int left, int right)//快速排序
{if (left >= right)return ;int begin = left;int end = right;if (end - begin + 1 < 10){InsertSort(a, end - begin + 1);return;}//int keyi = PartSort1(a, begin, end);//hoare法int keyi = PartSort2(a, begin, end);//挖坑法//int keyi = PartSort3(a, begin, end);//前后指针法QuickSort(a, begin, keyi - 1);QuickSort(a, keyi + 1, end);

快速排序(非递归)

任何一个递归程序在执行的时候,都是操作系统底层开辟了一个栈,每执行一个函数调用,那么就将当前函数的栈帧压栈,如果当前函数内部存在递归调用,那么就继续压入新的该函数代表的栈帧,直到某个函数内部没有再进行递归调用,该函数计算完毕后,将其出栈,并将计算结果返回给下一层栈帧。所以我们可以尝试着自己手搓一个栈,来实现函数的非递归调用。

要点1:我们要明白函数栈桢中存放的是什么,存放的是数组区间
要点2:数组区间只有一个值或者不存在值得时候不入栈

思路:首先将数组得左右下标压入栈中,这里注意顺序,栈得性质得后入先出,所以先将right压入栈中,在将left压入栈中,接下来相当于创建了函数栈桢,然后开始调用函数,因为递归函数中是不断得调用同一个函数,但是函数栈桢中得数组区间不同,所以接下来要通过循环来反复调用单趟排序,并且在循环中重复定义数组区间得值,调用完单趟排序后将子区间压栈,要判断一下数组区间至少有两个元素。
代码实现:

void QuickSortNonR(int* a, int left, int right)//非递归
{ST st;STInit(&st);//初始化STPush(&st, right);//先插入right,栈是后入先出STPush(&st, left);while (!STEmpty(&st)){//每次循环重置数组区间int begin = STTop(&st);STPop(&st); int end = STTop(&st);STPop(&st);int keyi = PartSort3(a, begin, end);//先排序后半部分,栈是后入先出if (keyi + 1 < end)//数组区间有一个数或者没有数不压栈{//排序完该区间后将子区间压入栈当中去STPush(&st, end);STPush(&st, keyi+1);}if (begin < keyi - 1){STPush(&st, keyi-1);STPush(&st, begin);}}STDestory(&st);
}

大致图解:
这里没有考虑到三数取中选key,选取最左边的数为基准值进行的单趟排序
在这里插入图片描述

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

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

相关文章

AndroidStudio中一些实用插件

1.RainbowBrackets插件为圆括号、方括号和花括号内的代码添加了漂亮的彩虹色 2.CodeGlance类似于Sublime或Xcode&#xff0c;CodeGlance插件在编辑器中嵌入了代码迷你图。滚动条也有所增大。在CodeGlance预览文件的代码模式下&#xff0c;用户可以快速导航到目标处。 3.ADBWifi…

Quest 3 和 Vision Pro 的“杀手级应用” 想法分享

融合空间游戏&#xff1a;通过将现实生活空间与虚拟世界相融合&#xff0c;创造出多人互动的游戏体验&#xff0c;为玩家带来独特而沉浸的感受。在产品设计中&#xff0c;不同的卧室和客厅空间可能对游戏需求产生影响&#xff0c;例如空间大小、布局和家具摆设等因素都可能影响…

Spring boot 发送文本邮件 和 html模板邮件

Spring boot 发送文本邮件 和 html模板邮件 提示&#xff1a;这里使用 spring-boot-starter-mail 发送文本邮件 和 html模板邮件 文章目录 Spring boot 发送文本邮件 和 html模板邮件一、开启QQ邮箱里的POP3/SMTP服务①&#xff1a;开启步骤 二、简单配置①&#xff1a;引入依赖…

win11 环境配置 之 Jmeter

一、安装 JDK 1. 安装 jdk 截至当前最新时间&#xff1a; 2024.3.27 jdk最新的版本 是 官网下载地址&#xff1a; https://www.oracle.com/java/technologies/downloads/ 建议下载 jdk17 另存为到该电脑的 D 盘下&#xff0c;新建jdk文件夹 开始安装到 jdk 文件夹下 2. 配…

IRIS / Chronicles 定义 Item 中的 Add Type 属性

根据我们前面说的 Item 中的 Add Type 属性&#xff0c;这个主要用来标识输入的数据是不是随着时间的变化而变化&#xff0c;有下面 3 种选项。 No‐Add 这个就是当数据输入后&#xff0c;是不会再变化了&#xff0c;不会随着时间的变化而变化。 Response Each Time 这个就…

第115讲:Mycat核心配置文件各项参数的作用以及概念

文章目录 1.Mycat配置文件相关概念2.Schema配置文件3.Rule配置文件4.Server配置文件 1.Mycat配置文件相关概念 在Mycat中核心的配置文件有schema.xml和rule.xml以及server.xml三个&#xff0c;其中schema.xml是用来配置数据库、表、读写分离、分片节点、分片规则等信息&#x…

政安晨:【深度学习实践】【使用 TensorFlow 和 Keras 为结构化数据构建和训练神经网络】(六)—— 二元分类

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: TensorFlow与Keras机器学习实战演绎 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 这篇文章咱们将深度学习应用到另一个常见任务…

【吊打面试官系列】Redis篇 -怎么测试 Redis 的连通性?

大家好&#xff0c;我是锋哥。今天分享关于 【怎么测试 Redis 的连通性&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 怎么测试 Redis 的连通性&#xff1f; 使用 ping 命令。 要测试Redis的连通性&#xff0c;可以使用redis-cli命令行工具或编写代码。以下是…

干货:教你如何在JMeter中调用Python代码N种方法!

在性能测试领域&#xff0c;JMeter已经成为测试专业人士的首选工具&#xff0c;用于模拟用户行为、测量响应时间、评估系统性能。而现在大部分接口都会涉及到验签、签名、加密等操作&#xff0c;为了满足特定需求&#xff0c;我们需要更多的灵活性&#xff0c;比如引入Python来…

[CISCN2019 华北赛区 Day2 Web1]Hack World

本题首先考察的是sql注入 拿过滤字符集先跑一遍 发现以上字符集都被过滤了 尝试id1 id11 尝试id(1)(2) 这里就已经给出了个思路我们可以尝试盲注去打 id(select(ascii(mid(flag,1,1))102)from(flag)) 这里表跟列已经给了我们了&#xff0c;所以我们可以写脚本了 import reque…

13 Games101 - 笔记 - 光线追踪(Whitted-Style光线追踪原理详解及实现细节)

13 光线追踪&#xff08;Whitted-Style光线追踪原理详解及实现细节) 引入光线追踪的原因 光栅化的缺点&#xff1a;不能很好的处理全局光照。&#xff08;因为Blinn-Phong这种局部模型无法处理全局效果&#xff01;&#xff09; 光栅化&#xff1a;快 real-time 质量低光线追…

嵌入式和 Java 走哪条路?

最近看到一个物联网大三学生的疑问&#xff0c;原话如下&#xff1a; 本人普通本科物联网工程专业&#xff0c;开学大三&#xff0c;现在就很迷茫&#xff0c;不打算考研了&#xff0c;准备直接就业&#xff0c;平时一直在实验室参加飞思卡尔智能车比赛&#xff0c;本来是想走嵌…

【分享】Word文档的5个隐藏功能

编辑Word文档的过程中&#xff0c;有时候我们需要隐藏一些格式&#xff0c;或者重要信息&#xff0c;今天小编来分享4个Word文档的隐藏功能&#xff0c;记得收藏哦&#xff01; 功能1&#xff1a;隐藏文本内容 对于不想被他人看到的文本内容&#xff0c;可以设置隐藏起来。 首…

来了!小学生Python创意编程(视频教学版)

目录 写在前面 推荐图书 推荐理由 写在最后 写在前面 在最好的年纪&#xff0c;一起来学Python吧&#xff01;本期博主给大家推荐一本适合小学生阅读的书籍&#xff0c;一起来看看吧~ 推荐图书 小学生Python创意编程&#xff08;视频教学版&#xff09; 直达链接&#x…

【Linux】网络基础1

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;题目解析 目录 &#x1f449;&#x1f3fb;一些常见网络设备&#x1f449;&#x1f3fb;网络协议(栈)&#x1f449;&#x1f3fb;协议分层OSI参考模型每个层…

ABC346 A-G 题解

ABC346 A-G题解 A题目AC Code&#xff1a;时间复杂度 B题目时间复杂度AC Code&#xff1a; C题目时间复杂度AC Code&#xff1a; D题目时间复杂度AC Code&#xff1a; E题目时间复杂度AC Code&#xff1a; F题目时间复杂度AC Code&#xff1a; G题目时间复杂度AC Code&#xff…

31---JTAG电路设计

视频链接 JTAG电路设计&#xff08;JLINK&XILINX&ALTERA&#xff09;_哔哩哔哩_bilibili JTAG电路设计 1、JTAG简介 JTAG&#xff08;Joint Test Action Group&#xff09;&#xff1a;联合测试工作组&#xff0c;是在名为标准测试访问端口和边界扫描结构的IEEE的标…

Redis数据结构的基础插入操作

数据结构与内部编码 Redis常见的数据结构 数据结构和内部编码 数据结构的插入操作 在Redis中&#xff0c;数据结构的插入操作取决于你要插入的数据类型。以下是一些常见的数据结构和它们的插入操作&#xff1a; 字符串 (String)&#xff1a;使用 SET 命令来插入字符串。例…

剑指Offer题目笔记19(二分查找)

面试题68&#xff1a; 问题&#xff1a; ​ 输入一个排序的整形数组nums和一个目标值t&#xff0c;如果数组nums中包含t&#xff0c;则返回在数组中的下标&#xff0c;否则返回按照顺序插入到数组的下标。 解决方案&#xff1a; ​ 使用二分查找。每次二分查找都选取位于数组…

IntelliJ IDE 插件开发 | (七)PSI 入门及实战(实现 MyBatis 插件的跳转功能)

系列文章 IntelliJ IDE 插件开发 |&#xff08;一&#xff09;快速入门IntelliJ IDE 插件开发 |&#xff08;二&#xff09;UI 界面与数据持久化IntelliJ IDE 插件开发 |&#xff08;三&#xff09;消息通知与事件监听IntelliJ IDE 插件开发 |&#xff08;四&#xff09;来查收…