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

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

  • 快速排序的单趟排序
    • 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,选取最左边的数为基准值进行的单趟排序
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++第十三弹---内存管理(下)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 目录 1、operator new与operator delete函数 1.1、operator new与operator delete函数 2、new和delete的实现原理 2.1、内置类型 2.2、自定义类型 …

基于模糊控制算法的倒立摆控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 对倒立摆模型进行模糊控制器simulink建模&#xff0c;利用倒立摆的摆角角度与小车的位置来控制小车的推力&#xff0c;控制了倒立摆的摆角问题&#xff0c;使得小车最终停在稳…

Redis面试题-缓存雪崩、缓存穿透、缓存击穿问题

1 穿透: 两边都不存在&#xff08;皇帝的新装&#xff09; &#xff08;黑名单&#xff09; &#xff08;布隆过滤器&#xff09; 2 击穿&#xff1a;一个热点的key失效了&#xff0c;这时大量的并发请求直接到达数据库. &#xff08;提前预热&#xff09; 3 雪崩&#xff1a…

LeetCode 27 移除元素

给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 元素的顺序可以改变。你不需要考虑数组中超出新长度后面…

【Emgu CV教程】10.9、轮廓的纵横比、面积比、坚硬性、等效直径、方向、掩码及像素点、最值点和它的位置、平均颜色、极值点

文章目录 一、轮廓的纵横比(Aspect Ratio)二、轮廓的面积比(Extent)三、轮廓的坚硬性(Solidity)四、轮廓的等效直径(Equivalent Diameter)五、轮廓的方向(Orientation)六、轮廓的掩码以及像素点(Mask and Pixel Points)1.原始素材2.代码3.运行结果 七、轮廓的值点和它的位置八、…

Spring实战:采用Spring配置文件管理Bean

文章目录 一、Spring框架概述二、实战&#xff1a;采用Spring配置文件管理Bean&#xff08;一&#xff09;创建Jakarta EE项目&#xff08;二&#xff09;添加Spring依赖&#xff08;三&#xff09;创建杀龙任务类&#xff08;四&#xff09;创建勇敢骑士类&#xff08;五&…

基于springboot实现校园周边美食探索及分享平台系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现园周边美食探索及分享平台系统演示 摘要 美食一直是与人们日常生活息息相关的产业。传统的电话订餐或者到店消费已经不能适应市场发展的需求。随着网络的迅速崛起&#xff0c;互联网日益成为提供信息的最佳俱渠道和逐步走向传统的流通领域&#xff0c;传统的…

uni-app(使用阿里图标)

1.注册阿里矢量图标库 注册阿里图标库账号并登录&#xff0c;https://www.iconfont.cn/ 2.加入购物车 搜索适合自己的图标&#xff0c;加入购物车&#xff0c;如下图&#xff1a; 3.加入项目 我的->资源管理->我的项目->创建项目&#xff0c;然后返回购物车&#…

SpringCloud学习笔记二:服务间调用

微服务中&#xff0c;很多服务系统都在独立的进程中运行&#xff0c;通过各个服务系统之间的协作来实现一个大项目的所有业务功能。服务系统间 使用多种跨进程的方式进行通信协作&#xff0c;而RESTful风格的网络请求是最为常见的交互方式之一。 spring cloud提供的方式&#…

工厂数字化看板是什么?部署工厂数字化看板有什么作用?

随着工业4.0时代的来临&#xff0c;数字化转型已成为制造业发展的必然趋势。在这个背景下&#xff0c;工厂数字化看板作为一种高效的信息展示与管理工具&#xff0c;正逐渐受到越来越多企业的青睐。那么&#xff0c;什么是工厂数字化看板&#xff1f;部署工厂数字化看板又有哪些…

真没想到,SQL注入漏洞的这么大,竟然导致1400万名俄罗斯大学毕业生信息泄露

不知道各位面试时&#xff0c;有没有相关的面试官有没有问到这样的问题&#xff0c;什么是sql注入&#xff0c;sql注入的危害是什么&#xff0c;mybatis的#与$的区别是什么等等&#xff0c;我想很多人都知道使用mybatis的#去规避sql注入&#xff0c;但是很多人不知道其原理&…

备份SQLserver数据库到本地位置

怎么选择合适的数据库备份方案&#xff1f; 有人可能会说SSMS&#xff0c;确实&#xff0c;SSMS作为一个微软官方提供的SQLserver数据库管理工具&#xff0c;是可以帮助我们完成对数据库的备份还原任务的&#xff0c;但是它也有一些局限性&#xff0c;比如不能进行批量化的备份…

LLM应用:Prompt flow vs LangChain

背景 Prompt flow和LangChain都是LLM时代&#xff0c;为高效地构建LLM应用而生。 Prompt flow是Microsoft开源的&#xff0c;其诞生时&#xff0c;LangChain已经很有名气了。 所以作为后生的Prompt flow会为我们带来哪些新的东西呢&#xff1f; ​​​​​​​ Prompt flo…

JTW——01,简述、对比

简述、对比 一、jwt跟token的区别二、什么是jwt三、jwt能做什么四、传统的session认证五、Jwt认证 一、jwt跟token的区别 https://blog.csdn.net/wangxinxinsj/article/details/132746876 二、什么是jwt 三、jwt能做什么 四、传统的session认证 五、Jwt认证

Docker搭建LNMP环境实战(06):Docker及Docker-compose常用命令

Docker搭建LNMP环境实战&#xff08;06&#xff09;&#xff1a;Docker及Docker-compose常用命令 此处列举了docker及docker-compose的常用命令&#xff0c;一方面可以做个了解&#xff0c;另一方面可以在需要的时候进行查阅。不一定要强行记忆&#xff0c;用多了就熟悉了。 1、…

ETL工具-nifi干货系列 第五讲 处理器GenerateFlowFile

1、今天我们一起来学习处理器GenerateFlowFile。这个处理器创建带有随机数据或自定义内容的 FlowFiles。GenerateFlowFile 对于负载测试、配置和模拟非常有用。从工具栏拖动处理器到画布&#xff0c;然后选择GenerateFlowFile即可。 2、点击add按钮或者双击 GenerateFlowFile可…

大型矿业集团安全知识竞赛主持词

男&#xff1a;尊敬的各位领导&#xff0c;员工同志们&#xff1a; 合&#xff1a;大家好&#xff01; 男&#xff1b;首先让我们以热烈的掌声对公司领导亲临比赛现场指导观看表示欢迎&#xff01; 男&#xff1b;继成功开展了荣辱观专题讲座、好矿嫂女红艺术展、安全谜语竞猜…

CCF-CSP认证考试 202212-3 JPEG 解码 100分题解

更多 CSP 认证考试题目题解可以前往&#xff1a;CSP-CCF 认证考试真题题解 原题链接&#xff1a; 202212-3 JPEG 解码 时间限制&#xff1a; 1.0s 内存限制&#xff1a; 512.0MB 问题背景 四年一度的世界杯即将画上尾声。在本次的世界杯比赛中&#xff0c;视频助理裁判&…

MySQL为什么会选错索引

在平时不知道一有没有遇到过这种情况&#xff0c;我明明创建了索引&#xff0c;但是MySQL为何不用索引呢&#xff1f;为何要进行全索引扫描呢&#xff1f; 一、对索引进行函数操作 假设现在维护了一个交易系统&#xff0c;其中交易记录表 tradelog 包含交易流水号(tradeid)、交…

计算机组成原理 — 指令系统

指令系统 指令系统指令的概述指令的格式指令的字长取决于 操作数类型和操作种类操作数的类型数据在存储器中的存放方式操作类型 寻址方式指令寻址数据寻址立即寻址直接寻址隐含寻址间接寻址寄存器寻址寄存器间接寻址基址寻址变址寻址堆栈寻址 RISC 和 CISC 技术RISC 即精简指令…