【排序算法】快速排序(详解+各版本实现)

目录

一.交换排序

1.基本思想

2.冒泡排序

二.快速排序

1.hoare版本

2.挖坑法

3.前后指针版本

4.优化

优化①:三数取中

优化②:小区间优化

5.非递归版本

6.特性总结

①效率

②时间复杂度:O(N*logN)

③空间复杂度:O(logN)

④稳定性:不稳定


一.交换排序

1.基本思想

交换排序的核心思想就是根据序列中两个记录键值的比较结果来交换这两个记录在序列中的位置,将键值较大的向序列尾端移动,键值较小的记录向序列前端移动。

冒泡排序和快速排序都属于交换排序的类别。

2.冒泡排序

冒泡排序的核心思想是:从左到右相邻两个元素进行比较后判断是否交换。进行一轮比较都会找到序列中最大的一个,这个最大的数就会从序列右边冒出来。当然,进行一轮比较能使最大的数到最右端,进行第二轮比较就能使第二大的数到正确的位置,如此,进行多趟排序就能将序列变为有序。代码实现如下:

//冒泡排序
void Bubble(int* a, int n)
{
	//n个数进行n-1趟排序
	for (int i = 0; i < n - 1; i++)
	{
		//冒泡排序的优化:若一轮没有交换则序列已有序
		int flag = 1;

		//单趟排序,每次排序终止的地方都要-1
		for (int j = 0; j < n - 1 - i; j++)
		{
			if (a[j] > a[j + 1])
			{
				int tmp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = tmp;
				flag = 0;
			}
		}
		if (flag == 1)
		{
			break;
		}
	}
}

冒泡排序的特性总结:

1.时间复杂度:O(N^2)

2.空间复杂度:O(1)

3.稳定性:稳定

二.快速排序

1.hoare版本

hoare版本的快速排序的单趟过程如上动图所示,其核心思想是:将一个数(key)的位置找对,并把序列进行分割。 在上图单趟过程完成后,很明显能发现,6(key)最终位置上,左边都是比6小的数,同时右边都是比6大的数,这样就算6这个数的位置是排好了,同时将序列分为了在6左边的序列和在6右边的序列,那么要想把整个序列排为有序,只需要将左右序列都排为有序即可,这不就又遇到相似的问题了吗?只需要将左右序列各自再进行快速排序,继续将序列进行分割,直到拆分出的序列只剩1个值,此时就可以看作有序,整个序列也就有序了,下面的拆分图可以演示这个递归的过程(注意实际上并没有将序列拆分,只是逻辑上可以这样看)

代码实现: 根据上述原理可以想到用递归来实现快速排序,使用begin和end作为下标来进行左右查找,目的是不改变left和right的值,因为这两个还需要在下次的函数递归调用中使用(即拆分为left到keyi-1和keyi+1到right的两个新区间)

关于递归的终止条件可以如下图所示:

//快速排序
void Quick(int* a, int left, int right)
{
	//终止条件
	if (left >= right)
		return;

	//keyi是Key的下标
	int keyi = left;
	int begin = left;
	int end = right;
	while (begin < end)
	{
		//右找小,前一个判断保证不会出现相遇后错过的情况
		while (begin < end && a[end] >= a[keyi])
		{
			end--;
		}

		//左找大
		while (begin < end && a[begin] < a[keyi])
		{
			begin++;
		}

		Swap(&a[begin], &a[end]);
	}
	Swap(&a[keyi], &a[begin]);

	//[left,keyi-1]keyi[keyi+1,right]
	Quick(a, left, keyi - 1);
	Quick(a, keyi + 1, right);
}

Tips:关于为什么相遇位置一定比key的值小的证明:

2.挖坑法

挖坑法跟hoare版本其实性质是相同的,不过挖坑法更好理解一点,只需要考虑坑位的变化即可。思想就是最左边的key的值拿出,形成一个坑位,然后左边找小,找到的值填入坑,然后该位置形成新的坑,如此进行直到相遇,效率相比hoare是一样的,没有提升。 

//快速排序-挖坑法
void Quick_pit(int* a, int left, int right)
{
	if (left >= right)
		return;

	int pit = left;
	int key = a[left];
	int begin = left;
	int end = right;
	while (begin < end)
	{
        //相遇的情况下end就是到达了pit的位置,之后key会覆盖掉
		while (begin<end && a[end] > key)
		{
			end--;
		}
		a[pit] = a[end];
		pit = end;

		while (begin < end && a[begin] < key)
		{
			begin++;
		}
		a[pit] = a[begin];
		pit = begin;
	}
	a[pit] = key;
	Quick_pit(a, left, pit - 1);
	Quick_pit(a, pit + 1, right);
}

3.前后指针版本

前后指针法,分别定义前后指针prev和cur,cur向前找 比key值小的数,找到后prev++,交换cur和prev位置,若没找到则cur++继续找,直到cur找出数组,最后将prev和key位置的值交换,此时key就在prev位置上,分割成两个序列继续递归。

//快速排序-前后指针法
void Quick_pointer(int* a, int left, int right)
{
	if (left >= right)
		return;

	int keyi = left;
	int prev = left;
	int cur = prev + 1;

	while (cur <= right)
	{
		//若prev++后与cur位置相同,则没交换的必要
		if (a[cur] < a[keyi] && ++prev != cur)
		{
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	Swap(&a[keyi], &a[prev]);
	Quick_pointer(a, left, prev - 1);
	Quick_pointer(a, prev + 1, right);
}

4.优化

优化①:三数取中

快速排序在处理逆序的情况下效率很低,而且有栈溢出的风险。在逆序情况下,key就是最大的数,每次左找大都找不到直到相遇,这样的分割造成的递归层次是最多最深的,效率自然很差。

那么什么时候能让快速排序效率优化提升呢?可以发现,上述逆序情况是因为选key的问题,每次选得的key都是最大的数,那么这里就可以想到一个优化方法:三数取中,顾名思义:就是在left,right,mid(中间)值中选择排在中间的值为key,并将其移动到最左边即可(不改变快排逻辑),这样得到的key一定就不是最大,或最小的值了。

//三数取中
int Getmid(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[right] < a[left])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else//a[left]>a[mid]
	{
		if (a[right] > a[left])
		{
			return left;
		}
		else if (a[mid] > a[right])
		{
			return mid;
		}
		else
		{
			return right;
		}
	}

}

优化②:小区间优化

快速排序是递归展开的,在二叉树的学习中知道第h层节点数是2^(h-1)个,这快占了整个树总节点的二分之一,递归调用也是类似的,越往后展开递归的次数也越多,那么当序列中值的个数小于某个值的时候就不再使用快速排序来递归展开,而使用其他排序方法,这样就能使递归调用次数大大降低,能有效提升性能。

不过这里有个问题,使用哪种排序方法呢?针对区间较小的序列,没必要动用希尔排序等,直接使用插入排序即可。

5.非递归版本

尽管如今编译器对递归的优化十分显著,但递归始终会有一些缺陷,例如递归太深的情况下会有栈溢出的风险,因此这里考虑有非递归版本。

此处非递归版本使用栈(Stack)来实现,将left和right作为一组数据,代表一次排序,如下图所示,第一次是0到9,取得的key是5,那么将0,9出栈后,让0,4和6,9入栈,这就代表分割后的两个新序列的排序,如此继续出栈后带动入栈的操作,left>=right就不入栈,直到栈为空,就能实现非递归版本的快速排序。

在具体的实现,一组数据可以自定义结构体(int left ,int right)再插入栈,当然也可以不用这么麻烦,直接两个数据先后入栈,再两个数据先后出栈,也能实现相应的操作,代码实现如下: 

int QuickSort_1(int* arr, int left, int right)
{
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		if (arr[cur] < arr[left] && ++prev != cur)
		{
			Swap(&arr[prev], &arr[cur]);
		}

		cur++;
	}
	Swap(&arr[left], &arr[prev]);

	return prev;
}


//快速排序-非递归版本
void Quick_NorR(int* a, int left, int right)
{
	ST st;
	STInit(&st);
	//先入后出,先入的right等会先出栈的就是left
	STPush(&st, right);
	STPush(&st, left);

	while (!STEmpty(&st))
	{
		int begin = STTop(&st);
		STPop(&st);
		int end = STTop(&st);
		STPop(&st);

		//之前的无论哪种版本都能得到key
		int key = QuickSort_1(a, begin, end);
		
		//保证left<right才入栈
		if (key - 1 > begin)
		{
			STPush(&st, key - 1);
			STPush(&st, begin);
		}
		
		if (end > key + 1)
		{
			STPush(&st, end);
			STPush(&st, key + 1);
		}
	}
}

6.特性总结

①效率

快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

②时间复杂度:O(N*logN)

关于时间复杂度,可以这样简单解释:若递归调用的展开是二分的,就很类似于二叉树的结构,那么就存在logN层,每层遍历的时间复杂度为O(N),因此总的时间复杂度可以看为O(N*logN),当然这只是一种简单的解释,方便记忆。

③空间复杂度:O(logN)

④稳定性:不稳定

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

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

相关文章

【vue】JSON数据导出excel

前言 导出方式有很多种&#xff0c;但是若只需要数据导出成.xlsx文件并下载的话&#xff0c;只用xlsx一个插件就行 目标 1 实现数据导出excel 2 如何设置表格列宽 3 如何在文件中创建工作表 准备工作 1 安装 npm i xlsx -S 2 引入 npm i xlsx -S 二、导出excel 创建文件 con…

【IMU】 温度零偏标定

温度标定 IMU的零偏随着温度的变化而变化&#xff0c;在全温范围内形状各异&#xff0c;有些可能是单调的&#xff0c;有些可能出现拐点。 多项式误差温度标定 目的是对估计的参数进行温度补偿&#xff0c;获取不同温度时的参数值&#xff08;零偏、尺度、正交&#xff09;&…

C++笔试强训3

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、选择题1-5题6-10题 二、编程题题目一题目二 一、选择题 1-5题 如图所示&#xff0c;如图所示p-3指向的元素是6&#xff0c;printf里面的是%s&#xff0c;从6开…

LLM应用构建前的非结构化数据处理(一)标准化处理认识数据

1.学习内容 本节次学习内容来自于吴恩达老师的Preprocessing Unstructured Data for LLM Applications课程&#xff0c;因涉及到非结构化数据的相关处理&#xff0c;遂做学习整理。 2.相关环境准备 2.1 建议python版本在3.9版本以上 chromadb0.4.22 langchain0.1.5 langcha…

【电脑应用技巧】如何寻找电脑应用的安装包华为电脑、平板和手机资源交换

电脑的初学者可能会直接用【百度】搜索电脑应用程序的安装包&#xff0c;但是这样找到的电脑应用程序安装包经常会被加入木马或者强制捆绑一些不需要的应用装入电脑。 今天告诉大家一个得到干净电脑应用程序安装包的方法&#xff0c;就是用【联想的应用商店】。联想电脑我是一点…

数字化转型:企业法务管理的未来发展 ​​​

在数字化浪潮的推动下&#xff0c;企业法务管理正经历着前所未有的变革。传统的法务工作模式在数据处理、合同审查、风险评估等方面逐渐显得力不从心。面对这一挑战&#xff0c;企业法务管理的数字化转型成为提升效率、保障合规、优化法律服务的必然选择。 数字化转型涉及到法…

【Java算法】二分查找 下

&#x1f525;个人主页&#xff1a; 中草药 &#x1f525;专栏&#xff1a;【算法工作坊】算法实战揭秘 一.山脉数组的峰顶索引 题目链接&#xff1a;852.山脉数组的峰顶 ​ 算法原理 这段代码实现了一个查找山峰数组中峰值索引的算法。山峰数组是一个先递增后递减的数组&…

前端图表库G2快速上手

文档地址&#xff1a; https://g2-v3.antv.vision/zh/docs/manual/getting-started/ https://g2.antv.antgroup.com/ 安装&#xff1a; pnpm i antv/g2在vue3中使用&#xff1a; <script setup> import {Chart} from antv/g2; import {onMounted} from "vue"…

智慧运维管理平台建设方案(PPT原件)

1、智慧运维系统建设背景 2、智慧运维系统建设目标 3、智慧运维系统建设内容 4、智慧运维系统建设技术 5、智慧运维系统建设流程 6、智慧运维系统建设收益 软件全套资料获取及学习&#xff1a;本文末个人名片直接获取或者进主页。

咱迈出了模仿的第一大步!快进来看看~

微信公众号&#xff1a;牛奶Yoka的小屋 有任何问题。欢迎来撩~ 最近更新&#xff1a;2024/06/28 [大家好&#xff0c;我是牛奶。] 这是第一篇模仿文章。咱决定先模仿样式&#xff0c;从外至里&#xff0c;层层递进。于是找了几个大V的公众号&#xff0c;看来看去&#xff0c;发…

uniapp 微信小程序接入MQTT

MQTT安装 前期准备 由于微信小程序需要wss&#xff0c;所以要有域名SSL证书 新建目录/srv/mosquitto/config&#xff0c;/srv/mosquitto/config/cert 目录/srv/mosquitto/config中新建配置文件mosquitto.conf&#xff0c;文件内容 persistence true persistence_location /m…

Hospital Management Startup 1.0 SQL 注入漏洞(CVE-2022-23366)

前言 CVE-2022-23366是一个影响HMS v1.0的SQL注入漏洞。该漏洞存在于patientlogin.php文件中&#xff0c;允许攻击者通过特定的SQL注入来获取或修改数据库中的敏感信息。 具体来说&#xff0c;攻击者可以通过向patientlogin.php发送恶意构造的SQL语句来绕过身份验证&#xff…

基于 sftp 的 NAS (局域网文件存储服务器)

局域网 NAS (文件存储服务器) 的基本功能有: 能够存储文件, 同时能够通过多个设备访问 (上传/下载) 文件. 这些功能通过 sftp 可以实现. sftp 是基于 SSH 的文件传输协议, SSH 全程加密传输, 使用 公钥 认证 (不使用密码/口令), 能够提供很高的安全性. 上文说到, 在 LVM 和 bt…

大数据------JavaWeb------FilterListenerAJAXAxiosJSON

Filter Filter简介 定义&#xff1a;Filter表示过滤器&#xff0c;是JavaWeb三大组件&#xff08;Servlet、Filter、Listener&#xff09;之一。 作用&#xff1a;它可把对资源&#xff08;Servlet、JSP、Html&#xff09;的请求拦截下来从而实现一些特殊功能 过滤器一般完成…

绝区陆--大语言模型的幻觉问题是如何推动科学创新

介绍 大型语言模型 (LLM)&#xff08;例如 GPT-4、LLaMA-2、PaLM-2、Claude-2 等&#xff09;已展示出为各种应用生成类似人类文本的出色能力。然而&#xff0c;LLM 的一个鲜为人知的方面是它们倾向于“产生幻觉”或生成不正确或没有根据的事实陈述。我不认为这仅仅是一个限制…

苍穹外卖前后端搭建

文章目录 参考开发环境搭建前端环境搭建1、 前端工程基于 nginx2、启动nginx,访问测试后端环境搭建1、从资料中找到后端初始工程:2、用 IDEA 打开初始工程,了解项目的整体结构:数据库环境搭建前后端联调nginx反向代理和负载均衡1、nginx反向代理2、nginx 负载均衡完善登录功…

博客标题:C++中的继承:构建面向对象的基石

目录 ​编辑 引言 继承的基本形式 示例1&#xff1a;基本继承 继承的类型 示例2&#xff1a;不同类型的继承 多重继承 示例3&#xff1a;多重继承 继承与多态性 示例4&#xff1a;继承与多态 结论 结尾 引言 在面向对象编程&#xff08;OOP&#xff09;中&#xff…

飞跃边界,尽在掌握 —— Jump Desktop 8 for Mac,远程工作新体验!

Jump Desktop 8 for Mac 是一款强大的远程桌面控制软件&#xff0c;专为追求高效工作与生活平衡的用户设计。它允许您轻松地从Mac设备上远程访问和控制另一台电脑或服务器&#xff0c;无论是跨房间、跨城市还是跨国界&#xff0c;都能实现无缝连接&#xff0c;仿佛操作就在眼前…

TIA博途与威纶通触摸屏无实物仿真调试的具体方法示例

TIA博途与威纶通触摸屏无实物仿真调试的具体方法示例 准备条件: TIA PORTAL V16 S7-PLCSIM V16 EasyBuilderPro V6.9.1 NetToPLCsim V1.2.5 如有需要,可以在这个链接中下载 NetToPLCSim - Browse Files at SourceForge.net538 weekly downloads3 weekly downloads12 weekly d…

参数手册 : PXIe-1095

PXIe-1095 起售价 RMB 97,950.00 产品详细信息 PXI机箱类型: PXIe 机箱电源类型: 交流 混合插槽数量: 5 PXI Express插槽数量: 11 冗余硬件选项: 是 最大系统带宽: 24 GB/s 插槽数量: 18 PXI插槽数量: 0 系统定时插槽: 是 槽冷却能力: 82 瓦 简介 PXIe&#xff0c;18槽&am…