【数据结构与算法】快速排序:让数据排序变得飞快!

大家好,我是小卡皮巴拉

文章目录

目录

引言

一. 快速排序的基本思想

二. 快速排序实现主框架

三.寻找基准值的几种方法

hoare版本

挖坑法

前后指针版本

兄弟们共勉 !!! 


每篇前言

博客主页:小卡皮巴拉

咱的口号:🌹小比特,大梦想🌹

作者请求:由于博主水平有限,难免会有错误和不准之处,我也非常渴望知道这些错误,恳请大佬们批评斧正。

引言

在数据处理的魔法世界里,有一种算法如同一位技艺高超的魔术师,能在瞬间将杂乱无章的数据变得井井有条。它,就是快速排序(QuickSort)——一个既高效又充满魅力的排序算法。

想象一下,你面对着一堆毫无规律的数字或字母,如何在最短的时间内将它们排列得整整齐齐?快速排序就是这样一位神奇的助手,它运用分治法的智慧,通过巧妙地选择一个基准元素,将复杂的问题分解成更小的子问题,然后逐个击破。

快速排序不仅速度快,而且实现起来也相对简单。它的平均时间复杂度达到了令人惊叹的O(n log n),在处理大规模数据时更是游刃有余。尽管在最坏的情况下,它的性能会有所下降,但只要我们稍微动点心思,选择合适的基准元素,就能让它始终保持在最佳状态。

那么,这位数据排序的魔术师究竟是如何施展它的魔法的呢?接下来,就让我们一起揭开快速排序的神秘面纱,探索它背后的工作原理和奇妙之处吧!

一. 快速排序的基本思想

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

简单的来说,快速排序就是:

拿出一个元素当标杆,然后把其他元素分成两堆,一堆比标杆小,一堆比标杆大。接着,对这两堆再分别找出标杆,继续分堆。这样一直分下去,直到每堆都只有一个元素,最后按顺序把堆合并起来,就排好序了。

二. 快速排序实现主框架

我们在这里用函数的递归调用来实现快速排序的主框架:

void QuickSort(int* arr, int left, int right) {
    // 若区间元素小于等于1个,直接返回,已有序
    if (left >= right) {
        return;
    }

    // 找基准值并划分区间,获取基准值最终索引
    int keyi = _QuickSort1(arr, left, right);

    // 递归对基准值左边子区间排序
    QuickSort(arr, left, keyi - 1);

    // 递归对基准值右边子区间排序
    QuickSort(arr, keyi + 1, right);
}

这段快速排序主框架代码通过递归实现对给定数组指定区间的排序。首先,若区间元素小于等于 1 个(即left >= right),则该区间已有序,直接返回以终止当前递归。接着,调用_QuickSort1函数选取基准值并对区间进行划分,keyi记录基准值的最终位置。最后,分别对基准值左右子区间递归调用QuickSort函数,持续划分和排序子区间,直至所有子区间元素有序,从而实现整个区间的排序。

将区间中的元素进行划分的 _QuickSort 方法主要有以下几种实现方式:

三.寻找基准值的几种方法

hoare版本

算法思路

Hoare 版本找基准值,先选区间首元素为基准。设左指针于第二个元素,右指针于末尾。右指针左移找小于基准的,左指针右移找大于基准的,找到就交换。如此循环直至两指针相遇,再将基准与相遇处元素互换,这样基准就位,数组也分成左右两部分,左边小于等于基准,右边大于等于基准。

具体做法

选择基准元素

通常选择待排序区间的第一个元素作为基准值 key,先把它的值记录下来,方便后续比较操作。

设定双指针

  • 左指针 left:一开始指向待排序区间的第二个元素(因为第一个元素已选为基准值)。
  • 右指针 right:指向待排序区间的最右边元素。

开始移动指针并交换元素

  • 从右往左找小于基准值的元素
    右指针 right 先行动,从右往左扫描数组。只要 right 指向的元素大于等于 key,就继续往左移动 right 指针,直至找到一个小于 key 的元素。

  • 从左往右找大于基准值的元素
    当右指针 right 找到小于 key 的元素后,左指针 left 从其当前位置从左往右扫描数组,只要它指向的元素小于等于 key,就继续往右移动 left 指针,直到找到一个大于 key 的元素。

  • 交换元素
    当左指针 left 和右指针 right 都找到符合条件(左大右小)的元素后,交换这两个元素的位置。然后重复上述从右往左、从左往右找元素以及交换的过程。

确定基准值最终位置

该过程持续到左指针 left 和右指针 right 相遇或者交叉(即 left >= right)。此时它们相遇的位置就是基准值 key 的最终正确位置。最后把一开始记录的基准值 key 放到这个相遇的位置上,这样一趟下来,数组就被划分成了左边部分元素都小于等于 key,右边部分元素都大于等于 key 的状态了,也就成功找到了基准值的位置并且完成了初步的区间划分。

下面我们来思考在这个过程中存在的两个问题:

问题1:为什么跳出循环后right位置的值一定不大于key?

当left > right 时,即right走到left的左侧,而left扫描过的数据均不大于key,因此right此时指向的数据一定不大于key 

问题2:为什么left和right指定的数据和key值相等时也要交换?

相等的值参与交换确实有一些额外消耗。实际还有各种复杂的场景,假设数组中的数据大量重复时,无法进行有效的分割排序。

动图助解:

这里使用了博主 @五分钟学算法之快速排序 的图片,如有侵权,请联系我删除哈。

代码实现:

int _QuickSort(int* arr, int left, int right)
{
	// 选当前区间最左元素作基准值索引,先记录下来
	int keyi = left;
	// 让左指针跳过基准值位置
	++left;
	// 通过双指针从两端扫描调整元素实现区间划分
	while (left <= right)
	{
		// 从右往左找比基准值小的元素,符合条件则右移指针
		while (arr[right] > arr[keyi] && left <= right)
		{
			right--;
		}
		// 从左往右找比基准值大的元素,符合条件则左移指针
		while (arr[left] < arr[keyi] && left <= right)
		{
			left++;
		}
		// 若左右指针未交错,交换对应元素并移动指针
		if (left <= right)
		{
			Swap(&arr[left++], &arr[right--]);
		}
	}
	// 交换基准值与right指向元素,确定基准值最终位置
	Swap(&arr[keyi], &arr[right]);
	// 返回基准值最终索引,供后续确定子区间用
	return right;
}

挖坑法

算法思路

挖坑法找基准值,先把区间首个元素当作基准值并保存,此位置成 “坑”。设左指针在第二个元素,右指针在末尾。右指针左移找小于基准的元素,找到后填到 “坑” 里,该位置成新 “坑”;左指针右移找大于基准的元素,找到后填到新 “坑”,此位置又成新 “坑”。如此反复,直至两指针相遇,把保存的基准值填入相遇处,基准值归位,数组也划分成左右两块,左边小于等于基准,右边大于等于基准。

具体做法

选择基准元素

选取待排序区间首个元素作基准值,记为 key 并保存,原位置形成 “坑”。

设定双指针

  • 左指针 left:指向区间第二个元素。
  • 右指针 right:指向区间最右边元素。

开始移动指针并填坑、挖坑操作

  • 从右往左找小于基准值的元素
    right 从右往左扫描,若元素大于等于 key 则继续左移,找到小于 key 的元素后,将其填到 left 指向的 “坑”,right 处变为新 “坑”。

  • 从左往右找大于基准值的元素
    left 从左往右扫描,若元素小于等于 key 则继续右移,找到大于 key 的元素后,填到 right 指向的 “坑”,left 处变为新 “坑”。

  • 持续填坑、挖坑过程
    重复上述操作,左右指针交替工作,调整元素位置。

确定基准值最终位置

持续操作直至 left 与 right 相遇,将 key 填到相遇位置,此时数组按要求划分好,确定了基准值位置与区间划分。

动图助解:

代码实现:

int _QuickSort1(int* arr, int left, int right)
{
	int hole = left;
	int key = arr[hole];
	while (left < right)
	{
		//从右往左找出比基准小的数据
		while (left < right && arr[right] >= key)
		{
			right--;
		}
		//找到后立即放入左边坑中
		arr[hole] = arr[right];
		//当前位置变为新坑
		hole = right;
		//从左往右找出比基准值大的数据
		while (left < right && arr[left] <= key)
		{
			left++;
		}
		//找到后立即放入右边的坑中
		arr[hole] = arr[left];
		//当前位置变为新坑
		hole = left;
	}
	arr[hole] = key;
	return hole;
}

前后指针版本

算法思路

前后指针法找基准值,先取区间首元素作基准。前指针在第二元素,后指针在第三元素。后指针后移扫描,遇小于基准元素,前指针先移一位,再与后指针元素交换,后指针继续后移。直至后指针遍历完区间,把前指针元素与基准交换,该位置即基准正确位置,数组以此分界,左小于等于基准,右大于等于基准。

具体做法

选择基准元素

选取待排序区间的首元素作为基准值,将其记为 key 并保存。

设定指针

  • 前指针 prev:初始指向待排序区间的第二个元素。
  • 后指针 cur:初始指向待排序区间的第三个元素。

移动指针并调整元素

  • 后指针扫描数组
    后指针 cur 从初始位置开始向后扫描数组。每扫描到一个元素,就将其与基准值 key 进行比较。
  • 依据比较结果处理元素
    如果当前 cur 指向的元素小于基准值 key,则将前指针 prev 先向后移动一位(若 prev 与 cur 不重合),然后交换 prev 和 cur 所指向的元素,之后 cur 继续向后移动。这样做的目的是将小于基准值的元素不断交换到前面,使前指针之前的元素都小于基准值。
  • 持续移动与调整
    后指针 cur 持续向后移动并重复上述比较与交换操作,直至 cur 遍历完整个区间。

确定基准值最终位置

当后指针 cur 遍历完整个区间后,将前指针 prev 所指向的元素与基准值 key 进行交换。此时,前指针 prev 所在位置就是基准值的最终正确位置。

动图助解:

我们还需要注意以下的问题

为什么限制条件要加上cur != ++prev
因为交换前prev要先走向下一个位置,此时刚好和cur重合,此时交换是无意义的,应该让cur继续移动。
为什么要先让prev++,再与cur交换
因为prev所在位置是已经调整好的数据,所以要先让prev先移动到下一个位置。

兄弟们共勉 !!! 

码字不易,求个三连

抱拳了兄弟们!

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

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

相关文章

【贪心算法】贪心算法四

贪心算法四 1.最长回文串2.增减字符串匹配3.分发饼干4.最优除法 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f603; 1.最长回文串 题目链接&…

网络安全,文明上网(1)享科技,提素养

前言 在这个信息化飞速发展的时代&#xff0c;科技的快速进步极大地丰富了我们的生活&#xff0c;并为我们提供了无限的可能性。然而&#xff0c;随着网络世界的不断扩张&#xff0c;增强我们的网络素养成为了一个迫切需要解决的问题。 与科技同行&#xff0c;培育网络素养 技术…

Redis | 第3章 对象《Redis设计与实现》

前言 参考资料&#xff1a;《Redis设计与实现 第二版》&#xff1b; 本篇笔记按照书里的脉络&#xff0c;将知识点分为四个部分。其中第一部分数据结构与对象分为上中下篇&#xff0c;上篇包括&#xff1a;SDS、链表和字典&#xff1b;中篇包括跳跃表、整数集合和压缩列表&…

国标GB28181设备管理软件EasyGBS国标GB28181视频平台:RTMP和GB28181两种视频上云协议的区别

在当今信息化高速发展的社会中&#xff0c;视频监控技术已经成为各行各业不可或缺的一部分。无论是城市安全、交通管理&#xff0c;还是企业安全、智能家居&#xff0c;视频监控都发挥着至关重要的作用。然而&#xff0c;随着监控点数量的急剧增加&#xff0c;海量视频数据的存…

Elasticsearch:如何部署文本嵌入模型并将其用于语义搜索

你可以按照这些说明在 Elasticsearch 中部署文本嵌入模型&#xff0c;测试模型并将其添加到推理提取管道。它使你能够生成文本的向量表示并对生成的向量执行向量相似性搜索。示例中使用的模型在 HuggingFace上公开可用。 该示例使用来自 MS MARCO Passage Ranking Task 的公共…

MFC图形函数学习10——画颜色填充矩形函数

一、介绍绘制颜色填充矩形函数 前面介绍的几个绘图函数填充颜色都需要专门定义画刷&#xff0c;今天介绍的这个函数可以直接绘制出带有填充色的矩形。 原型1&#xff1a;void FillSolidRect(int x,int y,int cx,int cy,COLORREF color); 参数&#xff1a;&a…

【网络协议栈】网络层(中)IP地址的网段划分、CIDR划分以及网络层概念(内附手画分析图 简单易懂)

绪论​ “坚持的意义是&#xff0c;以后回想起来的时候&#xff0c;你会庆幸“真好&#xff0c;我撑过来了”&#xff0c;而不是后悔“要是当初再……就好了”。本章主要写道网络层中非常重要的概念&#xff0c;了解了网络中ip地址的由来&#xff0c;以及ip地址不够的如何的处理…

Ultiverse 和web3新玩法?AI和GameFi的结合是怎样

Gamef 和 AI 是我们这个周期十分看好两大赛道之一&#xff0c;(Gamef 拥有极强的破圈效应&#xff0c;引领 Web2 用户进军 Web3 最佳利器。AI是这个周期最热门赛道&#xff0c;无论 Web2的 OpenAl&#xff0c;还是 Web3&#xff0c;都成为话题热议焦点。那么结合 GamefiA1双叙事…

小米顾此失彼:汽车毛利大增,手机却跌至低谷

科技新知 原创作者丨依蔓 编辑丨蕨影 三年磨一剑的小米汽车毛利率大增&#xff0c;手机业务毛利率却出现下滑景象。 11月18日&#xff0c;小米集团发布 2024年第三季度财报&#xff0c;公司实现营收925.1亿元&#xff0c;同比增长30.5%&#xff0c;预估902.8亿元&#xff1b;…

Linux系列-僵尸状态

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 进程退出 进程退出之后&#xff0c;代码就不会执行了&#xff0c;而是由PCB维护起来&#xff0c;我们可以通过PCB来查看退出信息。 进程退出时首先可以立即释放的就是进程对应…

NLP论文速读(EMNLP 2023)|工具增强的思维链推理

论文速读|ChatCoT: Tool-Augmented Chain-of-Thought Reasoning on Chat-based Large Language Models 论文信息&#xff1a; 简介&#xff1a; 本文背景是关于大型语言模型&#xff08;LLMs&#xff09;在复杂推理任务中的表现。尽管LLMs在多种评估基准测试中取得了优异的成绩…

实现两个表格的数据传递(类似于穿梭框)

类似于element的 第一个表格信息以及按钮&#xff1a; <div style"height: 80%"><el-table :data"tableData1" border :cell-style"{text-align:center}" style"width: 100%;"ref"multipleTable1"selection-chang…

【学术论文投稿】JavaScript 前端开发:从入门到精通的奇幻之旅

【中文核刊&普刊投稿通道】2024年体育科技与运动表现分析国际学术会议(ICSTPA 2024)_艾思科蓝_学术一站式服务平台 更多学术会议论文投稿请看&#xff1a;https://ais.cn/u/nuyAF3 目录 一、引言 二、JavaScript 基础 &#xff08;一&#xff09;变量与数据类型 &am…

【Golang】——Gin 框架与数据库集成详解

文章目录 1. 引言2. 初始化项目2.1 创建 Gin 项目2.2 安装依赖 3. 数据库驱动安装与配置3.1 配置数据库3.2 连接数据库3.3 在主函数中初始化数据库 4. 定义数据模型4.1 创建用户模型4.2 自动迁移 5. 使用 GORM 进行 CRUD 操作5.1 创建用户5.2 获取用户列表5.3 更新用户信息5.4 …

uniapp页面样式和布局和nvue教程详解

uniapp页面样式和布局和nvue教程 尺寸单位 uni-app 支持的通用 css 单位包括 px、rpx px 即屏幕像素。rpx 即响应式px&#xff0c;一种根据屏幕宽度自适应的动态单位。以750宽的屏幕为基准&#xff0c;750rpx恰好为屏幕宽度。屏幕变宽&#xff0c;rpx 实际显示效果会等比放大…

用 Python 与 Turtle 创作属于你的“冰墩墩”!

用 Python 与 Turtle 创作属于你的“冰墩墩”&#xff01; &#x1f980; 前言 &#x1f980;&#x1f40b; 效果图 &#x1f40b;&#x1f409; 代码 &#x1f409; &#x1f980; 前言 &#x1f980; 冰墩墩是2022年北京冬季奥林匹克运动会的官方吉祥物。以熊猫为原型&#x…

用Python爬虫“偷窥”1688商品详情:一场数据的奇妙冒险

引言&#xff1a;数据的宝藏 在这个信息爆炸的时代&#xff0c;数据就像是一座座等待挖掘的宝藏。而对于我们这些电商界的探险家来说&#xff0c;1688上的商品详情就是那些闪闪发光的金子。今天&#xff0c;我们将化身为数据的海盗&#xff0c;用Python这把锋利的剑&#xff0…

力扣hot100-->二分查找

目录 二分查找 1. 33. 搜索旋转排序数组 2. 34. 在排序数组中查找元素的第一个和最后一个位置 3. 240. 搜索二维矩阵 II 3. 287. 寻找重复数 二分查找 1. 33. 搜索旋转排序数组 中等 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&am…

http自动发送请求工具(自动化测试http请求)

点击下载《http自动发送请求工具(自动化测试http请求)》 前言 在现代软件开发过程中&#xff0c;HTTP 请求的自动化测试是确保应用程序稳定性和可靠性的关键环节。为了满足这一需求&#xff0c;我开发了一款功能强大且易于使用的自动化 HTTP 请求发送工具。该工具基于 C# 开发…

蓝队技能-应急响应篇日志自动采集日志自动查看日志自动化分析Web安全内网攻防工具项目

知识点&#xff1a; 1、应急响应-系统日志收集-项目工具 2、应急响应-系统日志查看-项目工具 3、应急响应-日志自动分析-项目工具 演示案例-蓝队技能-工具项目-自动日志采集&自动日志查看&自动日志分析 系统日志自动采集-观星应急工具(Windows系统日志) SglabIr_Co…