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

news2025/1/8 5:49:28

目录

一.交换排序

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)

④稳定性:不稳定

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

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

相关文章

拓展神经网络八股(入门级)

自制数据集 minst等数据集是别人打包好的&#xff0c;如果是本领域的数据集。自制数据集。 替换 把图片路径和标签文件输入到函数里&#xff0c;并返回输入特征和标签 只需要把图片灰度值数据拼接到特征列表&#xff0c;标签添加到标签列表,提取操作函数如下&#xff1a; def…

STM32快速搭建项目框架

注&#xff1a;编写本博客的原因&#xff0c;学习期间基于复习之前知识点的需要&#xff0c;故撰写本教程&#xff0c;即是复习前面的知识点也是作为博客的补充 1.0 文件夹的创建 创建一个STM32项目为模版工程&#xff0c;问价夹下分别包含4个子文件夹&#xff0c;一个是Librar…

【初阶数据结构】1.算法复杂度

文章目录 1.数据结构前言1.1 数据结构1.2 算法1.3 如何学好数据结构和算法 2.算法效率2.1 复杂度的概念2.2 复杂度的重要性 3.时间复杂度3.1 大O的渐进表示法3.2 时间复杂度计算示例3.2.1 示例13.2.2 示例23.2.3 示例33.2.4 示例43.2.5 示例53.2.6 示例63.2.7 示例7 4.空间复杂…

阻尼振动的可视化 包括源码和推导

阻尼振动的可视化 包括源码和推导 flyfish 牛顿第二定律&#xff08;加速度定律&#xff09; 胡克定律&#xff08;Hooke‘s Law&#xff09; 阻尼振动是指在振动系统中&#xff0c;由于阻力或能量损耗导致振动幅度随时间减小的现象。 左边为无阻尼&#xff0c;右边为有阻尼…

【WEB前端2024】3D智体编程:乔布斯3D纪念馆-第57-agent机器人助理自动获取喵星人资讯

【WEB前端2024】3D智体编程&#xff1a;乔布斯3D纪念馆-第57-agent机器人助理自动获取喵星人资讯 使用dtns.network德塔世界&#xff08;开源的智体世界引擎&#xff09;&#xff0c;策划和设计《乔布斯超大型的开源3D纪念馆》的系列教程。dtns.network是一款主要由JavaScript…

FastReport 指定sql 和修改 数据库连接地址的 工具类 :FastReportHelper

FastReport 指定sql 和修改 数据库连接地址的 工具类 &#xff1a;FastReportHelper 介绍核心代码&#xff1a;完整代码&#xff1a; 介绍 在FastReport中&#xff0c;经常会遇到需要给 sql 加条件的情况&#xff0c;或者给数据库地址做更换。 &#xff08;废话不多说&#x…

Elasticsearch基础(四):Elasticsearch语法与案例介绍

文章目录 Elasticsearch语法与案例介绍 一、Restful API 二、查询语法 1、ES分词器 2、ES查询 2.1、match 2.2、match_phrase 2.3、multi_match 2.4、term 2.5、terms 2.6、fuzzy 2.7、range 2.8、bool Elasticsearch语法与案例介绍 一、Restful API Elastics…

Echarts实现github提交记录图

最近改个人博客&#xff0c;看了github的提交记录&#xff0c;是真觉得好看。可以移植到自己的博客上做文章统计 效果如下 代码如下 <!DOCTYPE html> <html lang"en" style"height: 100%"><head><meta charset"utf-8"> …

需求分析|泳道图 ProcessOn教学

文章目录 1.为什么使用泳道图2.具体例子一、如何绘制确定好泳道中枢的角色在中央基于事实来绘制过程不要纠结美观先画主干处理流程再画分支处理流程一个图表达不完&#xff0c;切分子流程过程数不超25 &#xff0c;A4纸的幅面处理过程过程用动词短语最后美化并加上序号酌情加上…

未羽研发测试管理平台

突然有一些觉悟&#xff0c;程序猿不能只会吭哧吭哧的低头做事&#xff0c;应该学会怎么去展示自己&#xff0c;怎么去宣传自己&#xff0c;怎么把自己想做的事表述清楚。 于是&#xff0c;这两天一直在整理自己的作品&#xff0c;也为接下来的找工作多做点准备。接下来…

2-29 基于matlab的CEEMD

基于matlab的CEEMD&#xff08;Complementary Ensemble Empirical Mode Decomposition&#xff0c;互补集合经验模态分解&#xff09;&#xff0c;先将数据精心ceemd分解&#xff0c;得到imf分量&#xff0c;然后通过相关系数帅选分量&#xff0c;在求出他们的样本熵的特征。用…

理解点对点协议:构建高效网络通信

在通信线路质量较差的年代&#xff0c;能够实现可靠传输的高级数据链路控制&#xff08;High-level Data Link Control, HDLC&#xff09;协议曾是比较流行的数据链路层协议。HDLC是一个较复杂的协议&#xff0c;实现了滑动窗口协议&#xff0c;并支持点对点和点对多点两种连接…

SpringBoot实现简单AI问答(百度千帆)

第一步&#xff1a;注册并登录百度智能云&#xff0c;创建应用并获取自己的APIKey与SecretKey&#xff0c;参考网址&#xff1a; 点击去百度智能云 第二步&#xff1a;引入千帆的pom依赖 <dependency><groupId>com.baidubce</groupId><artifactId>q…

我的FPGA

1.安装quartus 2.更新usb blaster驱动 3.新建工程 1.随便找一个文件夹&#xff0c;里面新建demo文件夹&#xff0c;表示一个个工程 在demo文件夹里面&#xff0c;新建src&#xff08;源码&#xff09;&#xff0c;prj&#xff08;项目&#xff09;&#xff0c;doc&#xff…

基于单片机的温控光控智能窗帘设计探讨

摘 要&#xff1a; 文章使用的核心原件是 AT89C52 单片机&#xff0c;以此为基础进行模块化的设计&#xff0c;在整个设计中通过加入光检测模块和温度检测模块&#xff0c;从而对室内的温度和光照强度进行检测&#xff0c;然后将检测得到的数据传输给单片机&#xff0c;单片机…

Mosh|内连接、外连接、左连接、右连接(未完)

下图取自菜鸟教程&#xff0c;侵权删&#xff5e; 一、内连接&#xff1a;Inner Joins 模版&#xff1a;SELECT * FROM A JOIN B ON 条件 含义&#xff1a;返回A与B的交集&#xff0c;列为AB列之和 练习&#xff1a;将order_items表和products表连接&#xff0c;返回产品id和…

成为编程大佬!!——数据结构与算法(1)——算法复杂度!!

前言&#xff1a;解决同一个程序问题可以通过多个算法解决&#xff0c;那么要怎样判断一个算法的优劣呢&#xff1f;&#x1f914; 算法复杂度 算法复杂度是对某个程序运行时的时空效率的粗略估算&#xff0c;常用来判断一个算法的好坏。 我们通过两个维度来看算法复杂度——…

记录docker部署好golang web项目后浏览器访问不到的问题

部署好项目&#xff0c;docker ps -a查看没有任何问题 端口映射成功&#xff0c;但是浏览器就是访问不到&#xff0c;排查后发现犯了个错&#xff0c;注意&#xff0c;项目配置文件中的端口&#xff1a; 其实也就是你项目中监听的端口&#xff1a; 必须和容器端口一致&#x…

Linux——多线程(四)

前言 这是之前基于阻塞队列的生产消费模型中Enqueue的代码 void Enqueue(const T &in) // 生产者用的接口{pthread_mutex_lock(&_mutex);while(IsFull())//判断队列是否已经满了{pthread_cond_wait(&_product_cond, &_mutex); //满的时候就在此情况下等待// 1.…

看影视学英语(假如第一季第一集)

in the hour也代表一小时吗&#xff1f;等同于in an hour&#xff1f;