排序(二)——快速排序(QuickSort)

        欢迎来到繁星的CSDN,本期内容包括快速排序(QuickSort)的递归版本和非递归版本以及优化。

一、快速排序的来历

        快速排序又称Hoare排序,由霍尔 (Sir Charles Antony Richard Hoare) ,一位英国计算机科学家发明。霍尔本人是在发现冒泡排序不够快的前提下,发明了快速排序。不像希尔排序等用名字命名排序方法,霍尔直接将其命名为快速排序,但时至今日,快速排序仍然是使用最广泛最稳定的排序算法。

二、快速排序主代码

        快速排序是如何实现的呢?私以为思想和二叉树的前序遍历类似。

     (没有看过二叉树的看这里:一文带你入门二叉树!-CSDN博客)

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;

	int keyi = PartSort1(a, left, right);
	QuickSort(a, left, keyi - 1);
	QuickSort(a, keyi + 1, right);
}

        快速排序采用了分治的思想,之前在排序(一)提到过,如果将数组拆成一个个小块再排序,会比一整个数组集体排序来的快。快排也正是利用这一点,从零到整地排序整个数组,并确定好分拆端点的最终位置,使得每一组的排序结束都是最终位置,无需再调整。而由于不知道数组需要被拆分成几块,类似于二叉树的递归遍历,快排也需要递归来帮助拆分和子数组排序。

        下面阐述PartSort1这个函数是如何进行单趟排序的了。

三、快速排序(递归版单趟排序)

      Part1 Hoare法

        先上动图:

        

        可以注意到,PartSort的参数是数组a,数组最左端元素的下标left,最右端元素的下标right。

        key就是子数组(即left~right范围内)的第一个元素。

        整个流程如下:

        1、right向左遍历,直到找到一个小于key的值,停下。

        2、left向右遍历,直到找到一个大于key的值,停下。

        3、两者交换。

        4、重复上述流程,直到left与right相遇,交换key和最后相遇处的下标mid。

        5、随后返回mid,并利用mid继续将[left,right]区间分割为[left,mid-1]和[mid+1,right]区间,当不能分割的时候,该数组排序完毕。

        问题讲解

        如何确保key交换后所在的位置就是最终位置?

        首先,从一个升序数组来看,对于其中任何一个元素(除首尾两个),该元素左侧均更小,右侧均更大。换句话说,保证左侧比key元素更小,右侧比key元素更大,即得到了这一元素的位置。

        不难发现,被left遍历过的地址都是比key小的,被right遍历过的地址都是比key大的,因为如有left发现比key大,或者right发现比key小,都已进行过交换。

         唯一需要判定的,就是最后一个元素是否比key小。

        (1)left遇到right       

        当right停下,说明right所处位置的元素应该比key小。

        那么left撞到right的时候,一定是right所处的位置,保证了该位置比key小。

        (2)right遇到left

        当left停下,说明已经交换完毕,此时left所处位置的元素比key小。

   两种情况本质相同,都是保证了停下的那一方所处位置比key小。

int PartSort1(int* arr, int begin, int end)
{
	int left = begin, right = end;
	int key = begin;
	while (left < right)
	{
		//left<right防止越界
		//使用>=而不是>防止数据出现死循环
		while (left < right && arr[right] >= arr[key])
			//寻找比key小的值
		{
			right--;
		}
		while (left < right && arr[left] <= arr[key])
			//寻找比key大的值
		{
			left++;
		}
		swap(&arr[left], &arr[right]);
	}
	int mid = left;
	swap(&arr[key], &arr[mid]);
	return mid;
}

      Part2 挖坑法

        先上动图:        

        挖坑法和hoare法的差别不大,思路都是将key的位置安排在最终位置后,再分段快排。

        但挖坑法的确会比hoare法更加容易理解。

        坑的位置就代表相遇位置,每“交换”一次,就将坑位换到被交换的位置,最后坑落在哪里,就将key值填入。

        和hoare法不同的是,挖坑法需要多一个临时变量储存key值,在进行操作的时候只需要赋值,而不是swap交换。

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--;
		}
		swap(&a[right], &a[hole]);
		hole = right;
		while (left < right && a[left] <= key) {
			left++;
		}
		swap(&a[left], &a[hole]);
		hole = left;
	}
	int mid = hole;
	a[hole] = key;
	return mid;
}

       Part3 双指针法

        怎么能够让双指针缺席呢!

        

        本质和前面两种没有区别,只是遍历的方向不同,前两种都是左右两侧开始遍历,而双指针法从一端开始遍历。prev代表比key小的值,cur去遍历,并确保prev的下一个位置上比key大,将较大的元素集体向后驱赶。

int PartSort3(int* a, int left, int right) {
	int fast = left;
	int slow = left;
	int key = a[left];
	while (fast <= right) {
		if(a[fast] < key)
			swap(&a[++slow], &a[fast]);
		fast++;
	}
	swap(&a[slow], &a[left]);
	return slow;
}

      总结

        Part123三种办法没有高下之分,都可以作为QuickSort的一部分使用。

        可以说QuickSort的代码实现和理解复杂程度并不如之前说的堆排那么抽象,但是性能确实优秀。

四、快速排序(非递归版)

        我们知道,递归占据了大量的栈帧空间,尽管市面上大部分编译器已经增强了递归的性能,致使用递归的快排和不用递归的快排时间上差别不大,但递归空间上的占据却不可忽视。快排非递归版应运而生。

   这一部分需要用到栈的知识,如果还没看,可以查看:栈和队列的介绍与实现-CSDN博客

        非递归版本最重要的是,如何实现数组的分割,与进行多次排序。

        利用好之前学过的数据结构,我们可以想起栈与队列,我们用栈来演示。

void QuickSortno(int* a, int left, int right) {
	Stack ps;
	StackInit(&ps);
	StackPush(&ps, left);
	StackPush(&ps, right);
	while (!StackEmpty(&ps)) {
		int end = StackTop(&ps);
		StackPop(&ps);
		int begin = StackTop(&ps);
		StackPop(&ps);
		int key=PartSort1(a, begin, end);
		if (key + 1 < end) {
			StackPush(&ps, key+1);
			StackPush(&ps, end);
		}
		if (key - 1 > begin) {
			StackPush(&ps, begin);
			StackPush(&ps, key-1);
		}
	}
	StackDestroy(&ps);
}

       有点像层序遍历,我们将待排序的子数组的两端,放入栈中,每两个为一组弹出并进行单趟排序,排序完毕后,将新增的两个子数组的两端放入栈中。循环往复,便可以得到结果。

       本质思想还是一致的。

五、快速排序的优化

  快速排序的最坏情况是升序/降序的数组,复杂度都达到了O(n^2)

       经研究表明,问题出在基准值上,也就是每次排序时最左端的数据。如果将基准值改为数组内的随机值,平均情况将比最好情况只糟39%,比只取最左端数据为基准值要好很多。

       所以优化的第一个方向在于调整基准值。

     三数取中法

int GetMidi(int* a, int left, int right)
{
	int midi = (left + right) / 2;
	// left midi right
	if (a[left] < a[midi])
	{
		if (a[midi] < a[right])
		{
			return midi;
		}
		else if (a[left] < a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
	else // a[left] > a[midi]
	{
		if (a[midi] > a[right])
		{
			return midi;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

        代码很长,实际作用就是在left,(left+right)/2,right三个数中间取中间数,很好地做了分割。正如之前所述,越能将数组分割为两半,便越可以以更高的效率去排序。

      小区间优化

        优化的第二个方向在于快排的最后几层递归上。

        无论递归多少次,我们都要在最后几层递归上耗费大量的时间与空间(只需要看区间数量,便可以知道需排序的区间呈指数级增长)。

        递归是大招,但是更适合大场面,所以我们有以下优化:

if (left >= right)
		return;

                                                                          ↓

if (right-left<=10)
{
	InsertSort(a+left, right - left+1);
	return;
}

        没错,就如排序(一)中提到的,如果数组元素比较少,插入排序的复杂度体现不出来。

        这意味着我们可以在这个时候直接使用插入排序(当然希尔排序也是可以的,可为什么不全部都直接用希尔排序呢?)

        感受一下优化过的和未优化过的:

        优化前:

        优化后:

在10w量级的数据里,优化前后效率提升显著,相信量级更大的时候会更体现出优势。

        本篇内容到此结束,谢谢大家的观看!

        觉得写的还不错的可以点点关注,收藏和赞,一键三连。

        我们下期再见~

        往期栏目: 排序(一)——冒泡排序、直接插入排序、希尔排序(BubbleSOrt,InsertSort,ShellSort)-CSDN博客

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

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

相关文章

MQTT协议网关解决方案及实施简述-天拓四方

MQTT协议网关是一个中间件&#xff0c;负责接收来自不同MQTT客户端的消息&#xff0c;并将这些消息转发到MQTT服务器&#xff1b;同时&#xff0c;也能接收来自MQTT服务器的消息&#xff0c;并将其转发给相应的MQTT客户端。MQTT协议网关的主要功能包括协议转换、消息过滤、安全…

ImportError: DLL load failed while importing cv2解决方案

系统是 server 2012 r2 datacenter 背景&#xff1a;在window10系统上采用PyInstaller打包python310版本程序后&#xff0c;在server 2012 r2 datacenter运行报错ImportError: DLL load failed while importing cv2&#xff0c;最后解决方案参考了一篇文章下评论修改成功。 原…

Qt http网络编程

学习目标&#xff1a;Qt HTTP网络编程 学习内容 1、Http就是超文本传输协议(Hypertext Transfer Protocol)的缩写,它定义了浏览器和网页服务器之间的通信规范。是一个简单的请求一响应协议&#xff0c;它通常运行在 TCP 之上。 作用:规定 WWW 服务器与浏览器之间信息传递规范…

软件测试常见面试题汇总(2024版)

一、常见的面试题汇总 1、你做了几年的测试、自动化测试&#xff0c;说一下 selenium 的原理是什么&#xff1f; 我做了五年的测试&#xff0c;1年的自动化测试&#xff1b; selenium 它是用 http 协议来连接 webdriver &#xff0c;客户端可以使用 Java 或者 Python 各种编…

软件测试想转职有适合的岗位吗?

软件测试被有些人看做技术含量低&#xff0c;但是软件测试实际上是万金油行业&#xff0c;如果你不是在很大的公司做的软件测试&#xff0c;相比你做的工作是很杂的&#xff0c;比如软件测试找bug&#xff0c;你的主业&#xff0c;帮着产品经理整理需求&#xff0c;帮着项目经理…

微软开源项目GraphRAG——基于知识图谱的RAG简介

前言 在大型语言模型&#xff08;LLM&#xff09;的前沿研究中&#xff0c;一个核心挑战与机遇并存的领域是扩展它们的能力&#xff0c;以解决超出其训练数据范畴的问题。这不仅要求模型在面对全新数据时仍能保持卓越表现&#xff0c;还意味着开辟了全新的数据分析可能性&…

【C++】C++ 汽车租赁管理系统(源码+论文)【500+行代码】【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

CAN总线实战项目:使用STM32和PCAN-View实现数据采集与监控系统(附完整代码)

摘要: 本文深入浅出地介绍CAN&#xff08;Controller Area Network&#xff0c;控制器局域网络&#xff09;总线协议&#xff0c;涵盖其基础概念、报文帧格式、仲裁机制、错误处理等关键知识。同时&#xff0c;文章结合STM32平台&#xff0c;从硬件设计、软件开发到实战案例&am…

【益起童行】为“来自星星的孩子”点亮希望之光

在未来的日子里&#xff0c; 我期望每一个孩子都能得到优质的干预治疗&#xff0c;让他们在未来能够过上正常、快乐的生活。 我也期望每一个家庭都能战胜困境&#xff0c;迎来美好。 作为社会的一份子&#xff0c;我愿意为这繁华人世贡献出自己微不足道但却真挚的力量&#xff…

24暑假计划

暑假计划&#xff1a; 1.从明天起开始将C语言的部分补充完整&#xff0c;这部分的预计在7月24日前完成 2.由于之前的文章内容冗余&#xff0c;接下来进行C语言数据结构的重新编写和后面内容的补充预计8月10号前完成 3.后续开始C的初级学习

新加坡很火的slots游戏代投Facebook广告新流量趋势

新加坡很火的slots游戏代投Facebook广告新流量趋势 在新加坡这片充满活力的土地上&#xff0c;Slots游戏以其独特的魅力和吸引力&#xff0c;迅速成为了许多玩家的心头好。而Facebook&#xff0c;作为全球最大的社交媒体平台之一&#xff0c;为Slots游戏的推广提供了得天独厚的…

element-plus 按需导入问题 404等问题

场景 新开一个项目&#xff0c;需要用element-plus这个ui库&#xff0c;使用按需引入。 这是我项目的一些版本号 "element-plus": "^2.7.6","vue": "^3.2.13","vue-router": "^4.0.3",过程&#xff08;看解决方法…

【MySQL】常见的MySQL日志都有什么用?

MySQL日志的内容非常重要&#xff0c;面试中经常会被问到。同时&#xff0c;掌握日志相关的知识也有利于我们理解MySQL 底层原理&#xff0c;必要时帮助我们排查解决问题。 MySQL中常见的日志类型主要有下面几类(针对的是InnoDB 存储引擎): 错误日志(error log):对 MySQL 的启…

利用Python与uiautomator2实现【手机群控】

利用Python与uiautomator2实现多设备自动化测试 引言 在移动应用测试中&#xff0c;自动化测试是一种提高测试效率和覆盖率的有效手段。本文将介绍如何使用Python语言结合uiautomator2库来实现对多个设备的并行自动化测试。 老规矩先放实现的效果 环境准备 Python环境安装u…

评价妙笔生词智能写歌词软件:助力与局限并存

在音乐创作的领域&#xff0c;科技的发展催生了各种创新工具&#xff0c;妙笔生词智能写歌词软件便是其中引人注目的一项。对于这款软件&#xff0c;我们需要以客观和全面的视角来进行评估&#xff0c;因为它既带来了显著的助力&#xff0c;同时也存在不可忽视的局限。 妙笔生…

Iridient Developer:解锁Mac RAW图像处理的极致潜力,打造专业级色彩与细节

Iridient Developer for Mac是一款专为Mac用户设计的RAW图像调整软件&#xff0c;它以其卓越的性能和丰富的功能&#xff0c;赢得了众多摄影师的青睐。以下是对这款软件的详细介绍&#xff1a; 一、强大的RAW图像处理能力 Iridient Developer专为处理RAW图像而设计&#xff0…

JAVA毕业设计146—基于Java+Springboot+vue+uniapp的景区旅游购票小程序(源代码+数据库+9000字论文)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootvueuniapp的景区旅游购票小程序(源代码数据库9000字论文)146 一、系统介绍 本项目前后端分离&#xff0c;分为用户、管理员两种角色 1、用户&#xff1a; 注册…

PHP充电桩小程序系统源码

绿色出行新伴侣&#xff01;充电桩小程序&#xff0c;让充电不再烦恼✨ &#x1f50b; 开篇&#xff1a;告别电量焦虑&#xff0c;充电桩小程序来救场&#xff01; 在这个电动车日益普及的时代&#xff0c;电量不足成了不少车主的“心头大患”。但别担心&#xff0c;充电桩小…

神器!3个免费PPT成品网站推荐+3款AIPPT工具盘点!

熬夜加班做PPT却没有头绪&#xff1f;别再自己憋着想了&#xff01;现在凡事主打一个“抄作业”&#xff0c;想做ppt却没想法&#xff0c;可以去到ppt成品网站搜集PPT模板&#xff0c;或是使用时下流行的AI生成PPT工具&#xff0c;只需输入PPT主题&#xff0c;即可快速生成一份…

MongoDB教程(二):mongoDB引用shell

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言一、MongoD…