史上最细快速排序讲解(hoare,挖坑,双指针, 非递归)

news2024/9/29 15:16:43

文章目录

  • 前言
  • 一、递归方法快排
    • 1. 递归主要思想
    • 2. 递归代码实现
  • 二、hoare方法实现找基准值
    • 1. hoare思想
    • 2. hoare代码实现
  • 三、挖坑方法实现找基准值
    • 1. 挖坑思想
    • 2. 挖坑代码实现
  • 四、双指针方法实现找基准值
    • 1. lomuto前后指针法思想
    • 2. lomuto前后指针法代码实现
  • 五、非递归方法快排
    • 1. 非递归主要思想
    • 2. 非递归代码实现
  • 总结


前言

之前J桑写过排序大全,里面有详细所有排序的算法思想,包括部分快排,写的很详细,感兴趣的观众老爷可以去看看~
史上最牛排序集合,带你认清所有排序算法!(必看系列)~

那么这里J桑再把一些东西拿过来一起学习~
本期内容主要写快速排序的所有思想~

在这里插入图片描述


一、递归方法快排

1. 递归主要思想

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

快速排序的思想如下:
首先一个数组,我们给他找一个基准值,这个基准值就使得基准值左侧的所有数据全都小于基准值,基准值右侧的所有数据全都大于基准值。
在这里插入图片描述

然后,所有基准值左侧的部分再划分一个基准值,分成小于和大于基准值的两侧。
原本基准值的右侧也同理
在这里插入图片描述
然后我们再细分,直到划分到只有一个或没有元素
在这里插入图片描述
我们可以把它看成二叉树的结构,对于每一个结点,排列好的数组就相当于它左右子树通过基准值排列好的样子叠加起来。

对于我们初始的数组来说,最主要的任务就是如何找基准值,以及如何遍历我们的数组。

2. 递归代码实现

这是我们初始的数组
在这里插入图片描述
假设有一个函数int _QuickSort(int* arr, int left, int right)

int _QuickSort(int* arr, int left, int right)的功能是实现返回数组arr中左边界left和右边界right的基准值。

那么我们就可以用这个数组模拟前序遍历的方式遍历这个数组,
假设基准值为keyi,那么它的左子树就是[ left, keyi-1 ],右子树就是[ keyi+1, right]
如下图所示:
在这里插入图片描述
而递归的结束条件是 left >= right 这个后面就会理解

因此我们可以写出代码:

//快速排序
void QuickSort(int* arr, int left, int right)
{
	//问题1:有没有等于呢?
	if (left >= right)
	{
		return;
	}

	int keyi = _QuickSort(arr, left, right);
	//遍历它的左子树
	QuickSort(arr, left, keyi - 1);
	//遍历它的右子树
	QuickSort(arr, keyi + 1, right);

}

二、hoare方法实现找基准值

1. hoare思想

hoare方法如何找基准值呢?
还是我们的数组,我们将第一个元素定位基准值,left为第二个元素,right为最后一个元素
如图:
在这里插入图片描述

  • 首先,right从右向左找小于基准值的位置,left从左向右找大于基准值的位置
    在这里插入图片描述
  • 然后交换arr[left]arr[right]
    在这里插入图片描述
  • 交换过后left ++,right - -
    此时left与right走到相同位置
    在这里插入图片描述
  • 注意:在left 与 right 相等时还需要继续循环,为了二分左右子树
  • 我们接着right从右向左找小于基准值的位置,left从左向右找大于基准值的位置
    在这里插入图片描述
  • 但是现在left <= right因此不交换,结束循环
  • keyi位置的值与right位置的值进行交换,此时right的位置就是基准值的位置
    在这里插入图片描述
  • 特殊情况
    如果rihgt或者left找到的值刚好等于基准值呢?我们来看一个特殊的数组
    在这里插入图片描述
    如果rihgt或者left找到的值刚好等于基准值还能循环的话最后就会变成这样
    在这里插入图片描述
    right就会来到如图所示的位置,那么最后我们return right相当于左子树为空,其余元素全在右子树,我们快排就是要一直二分数据。因此如果rihgt或者left找到的值刚好等于基准值不能循环

2. hoare代码实现

//找基准值
int _QuickSort(int* arr, int left, int right)
{
	int keyi = left;
	left++;
	//对于传来的left与right,left从左往右找大,right从右往左找小
	//问题1:有没有等于?  答:有,为了平衡左右子树达成二分的作用为了让right在往前走一格
	while (left <= right)
	{
		//问题2:有没有等于?    答:没有,假设数组元素全部都是基准值,那么每次递归之分出去一个数据
		//问题3: 为什么要加left <= right,因为left<=right就可以结束了不用循环了
		while (left <= right && arr[right] > arr[keyi])
		{
			right--;
		}
		while (left <= right && arr[left] < arr[keyi])
		{
			left++;
		}
		

		//出了这两个循环之后,就代表 left 与 right 都找到了各自的值,如果没找到也就越界了
		if (left <= right)
		{
			Swap(&arr[left++], &arr[right--]);
		}
	}
	//出了大的while循环就代表left已经超过right了,那么就需要交换right位置的值和保存的基准值
	Swap(&arr[keyi], &arr[right]);

	//right位置就是我们的基准值下标
	return right;
}

//快速排序
void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
	{
		return;
	}

	int keyi = _QuickSort(arr, left, right);
	//遍历它的左子树
	QuickSort(arr, left, keyi - 1);
	//遍历它的右子树
	QuickSort(arr, keyi + 1, right);

}

三、挖坑方法实现找基准值

1. 挖坑思想

挖坑方法如何找基准值呢?

我们先来看一张动图:
在这里插入图片描述
思路:

创建左右指针。⾸先从右向左找出⽐基准的数据,找到后⽴即放⼊左边坑中,当前位置变为新的"坑",然后从左向右找出⽐基准的数据,找到后⽴即放⼊右边坑中,当前位置变为新的"坑",结束循环后将最开始存储的分界值放⼊当前的"坑"中,返回当前"坑"下标(即分界值下标).

看下图是我们初始的数组:
我们定义一个坑(hole),让他等于left.
假设key保存数组最左边的数据,记作基准值.

在这里插入图片描述

接下来让right从右往左找小,如果小于基准值,就用这个值来填,再让right位置变为新的.

在这里插入图片描述
接下来让left从左往右找大,如果大于基准值,就用这个值来填,再让left位置变为新的.
在这里插入图片描述
重复上述步骤直到left不在小于right
此时,left 与 right 重合
在这里插入图片描述
此时,让我们最开始保存的 key 填坑,返回 hole 就是基准值.
在这里插入图片描述


2. 挖坑代码实现

//挖坑法
int _QuickSort2(int* arr, int left, int right)
{
	int key = arr[left];
	int hole = left;
	while (left < right)
	{
		while (left < right && arr[right] > key)
		{
			right--;
		}
		if (left < right)
		{
			arr[hole] = arr[right];
			hole = right;
		}

		while (left < right && arr[left] < key)
		{
			left++;
		}
		if (left < right)
		{
			arr[hole] = arr[left];
			hole = left;
		}

	}
	arr[hole] = key;

	return hole;
}

四、双指针方法实现找基准值

1. lomuto前后指针法思想

思想:

创建前后指针,从左往右找⽐基准值⼩的进⾏交换,使得⼩的都排在基准值的左边。

在这里插入图片描述
我们来看一张动图:
在这里插入图片描述

还是那个数组我们来具体看一看每一步是怎么实现的~
先定义keyi保存基准值下标,定义prev = left,cur = left + 1.
在这里插入图片描述
'cur从左往右找小,如果找到比基准值小的数组,就让prev ++,并且交换prevcur中的数据.

注意:如果此时++prev后与cur重合就不交换了
在这里插入图片描述
cur继续++执行前两项任务
在这里插入图片描述

直到cur > right结束循环,此时交换key与prev中元素,prev的值就是基准值
在这里插入图片描述


2. lomuto前后指针法代码实现

//lomuto前后指针法
int _QuickSort3(int* arr, int left, int right)
{
	int keyi = left;
	int prev = left, cur = left + 1;

	while (cur <= right)
	{
		if (arr[cur] < arr[keyi] && ++prev != cur)
		{
			Swap(&arr[cur], &arr[prev]);
		}
		cur++;
	}

	Swap(&arr[keyi], &arr[prev]);
	return prev;
}

五、非递归方法快排

1. 非递归主要思想

非递归版本的快速排序需要借助数据结构:栈

非递归快速排序使用栈来模拟递归过程,避免函数的递归调用栈过深的风险。这里,我们通过图文详细讲解非递归版的快速排序。

非递归快速排序的基本步骤如下:

  1. 选择基准元素(pivot):从数组中选择一个基准元素,通常是第一个或最后一个元素。
  2. 分区(partitioning):将数组分成两部分,左边的元素都小于基准元素,右边的元素都大于基准元素。
  3. 栈的操作:使用栈来存储未处理的数组边界(左右索引)。在每次分区后,将左、右子数组的边界推入栈中,继续处理子数组。
  4. 重复分区:重复上述过程,直到栈为空为止。

详解步骤:

初始数组:
我们以数组 {5, 3, 9, 6, 2, 4, 7, 1, 8} 为例,并且以第一个元素 5 为基准元素进行排序。

原始数组:539624718

第一步:选择基准元素

选取数组的第一个元素 5 作为基准元素。接下来,我们需要将小于 5 的元素移动到它的左边,大于 5 的元素移动到右边。

第二步:分区操作

使用双指针法(prevpcur)遍历数组。初始状态下:

  • prev 指向基准元素 5
  • pcur5 的下一个元素开始。

我们遍历数组,将比 5 小的元素交换到 prev 所在的位置,最后将 5prev 指针指向的元素交换。

位置539624718
初始539624718
交换5 ↔ 359624718
交换5 ↔ 235964718
交换5 ↔ 132596478

现在数组的左边元素都小于 5,并将 5 归位:

| 新数组: | 1 | 3 | 2 | 4 | 5 | 6 | 9 | 7 | 8 |

基准元素 5 的最终位置为索引 4

第三步:处理子数组

基准元素确定之后,我们将左右子数组的范围压入栈中:

  • 左子数组范围:[0, 3],对应元素 {1, 3, 2, 4}
  • 右子数组范围:[5, 8],对应元素 {6, 9, 7, 8}

我们从栈中取出右子数组 [5, 8],选择新的基准元素 6,进行同样的分区操作。

对右子数组 [6, 9, 7, 8] 的处理:

原数组:6978
基准元素:6978
交换后6978

6 的位置已经确定,无需交换。继续对剩下的子数组 [9, 7, 8] 进行分区。

对子数组 [9, 7, 8] 的处理:

原数组:978
基准元素:978
交换后789

9 归位,右子数组完成排序。

左子数组 [1, 3, 2, 4] 的处理:

我们从栈中取出左子数组 [1, 3, 2, 4],选择 1 作为基准元素。由于所有元素都大于 1,无需交换,继续处理子数组。

  • 子数组 [3, 2, 4] 选择 3 作为基准元素,进行分区。
原数组:324
基准元素:324
交换后234

至此,所有子数组都已处理完毕,数组完全有序。

  1. 完成排序的结果:

最终,数组经过多次分区操作,排序结果如下:

| 排序后数组: | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |


2. 非递归代码实现

//使用遍历的方法
void QuickSortNonR(int* arr, int left, int right)
{
	ST st;
	STInit(&st);
	StackPush(&st, right);
	StackPush(&st, left);

	while (!StackEmpty(&st))
	{
		int begin = StackTop(&st);
		StackPop(&st);
		int end = StackTop(&st);
		StackPop(&st);

		int keyi = begin;
		int prev = begin;
		int cur = begin + 1;
		
		while (cur <= end)
		{
			if (arr[cur] < arr[keyi] && ++prev != cur)
			{
				Swap(&arr[prev], &arr[cur]);
			}
			cur++;
		}
		Swap(&arr[keyi], &arr[prev]);

		keyi = prev;

		//根据基准值划分左右区间
		//左区间:[begin,keyi-1]
		//右区间:[keyi+1,end]
		if (keyi + 1 < end)
		{
			StackPush(&st, end);
			StackPush(&st, keyi + 1);
		}

		if (keyi - 1 > begin)
		{
			StackPush(&st, keyi - 1);
			StackPush(&st, begin);
		}
	}


	STDestroy(&st);
}

总结

到这里,我们快速排序就写完啦!

要记住快速排序时间复杂度通常为** O(n log n)**,
空间复杂度为 O(log n)~

谢谢大家~

在这里插入图片描述

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

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

相关文章

第十四周:机器学习笔记

第十四周周报 摘要Abstract一、机器学习——Transformer&#xff08;下&#xff09;1. Transformer decoder1.1 autoregressive decoder&#xff08;自回归解码器&#xff09;1.2 Transformer decoder结构图1.3 non-autoregressive decoder&#xff08;非自回归解码器&#xff…

24年最新 idea 插件开发教程,面试鸭插件技术实现!

大家好&#xff0c;我是松柏。今天给大家分享下这个JetBrains插件开发教程。 学习过程中可以参考开源项目&#xff1a;https://github.com/yuyuanweb/mianshiya-plugin 首先贴一下官方文档&#xff1a;https://plugins.jetbrains.com/docs/intellij/welcome.html 虽然这个文档…

【最新发布】Win10 22H2 19045.4957 正式版免费下载!

今日系统之家小编给大家分享2024年9月第二次更新的Windows10 22H2正式版系统&#xff0c;该版本系统基于最新Windows10 22H2 19045.4957 64位专业版进行离线制作&#xff0c;安全无毒&#xff0c;修复了使用某些环绕声技术时某些媒体的播放可能会停止等多项问题&#xff0c;系统…

基于C#开发的(CS界面)图书管理系统

图书管理系统项目开发说明书 项目简介 项目背景&#xff08;选择这个项目的原因、前景&#xff0c;面向的用户&#xff0c;优势&#xff09;&#xff1b; 根据温州理工学院需要希望能够充分利用现代科技来提高图书管理的效率&#xff0c;在原有的办公系统基础上进行扩展&…

1. 如何在服务器上租GPU跑实验 (以AutoDL为例) - 深度学习·科研实践·从0到1

目录 前言 1. 在AutoDL上注册账号 2. 在算力市场选择GPU 3. 创建实例 4. 控制台-容器实例界面&#xff08;核心&#xff09; 4.1 无卡模式&#xff08;常用&#xff09; 5. 帮助文档 前言 好记性不如烂笔头&#xff0c;本专栏将详细记录下本人学习深度学习工程实践&…

Python通过Sqlalchemy框架实现增删改查

目录 简介 什么是SQLAlchemy&#xff1f; SQLAlchemy可以分为两个部分&#xff1a;Core和ORM。 一、首先安装sqlalchemy 二、在配置文件中添加数据库连接信息&#xff0c;我这里是Mysql 三、 创建数据库连接类&#xff0c;我这里是动态读取数据库的表字段&#xff0c;自动…

神器!GPT让大学生也能轻松实现架构师级的系统设计图

文章目录 零、前言一、实现架构师级的系统设计图操作指导系统背景功能细化 画用例图画系统架构设计图划分html页面画实体类图画服务层类图画时序图画数据库ER图 二、感受 零、前言 粉丝做毕业设计时&#xff0c;不会画架构图&#xff0c;问虚竹哥会不会画&#xff5e; 虽然这…

基于微信小程序的空巢老人健康管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

STM32引脚PB3、PB4、PA15作为输入输出的特殊配置

一、问题描述 简单描述&#xff1a; 最近做的一个项目中&#xff0c;PB3端口配置为输入&#xff0c;不管外部输入是高电平还是低电平&#xff0c;一直读取到的是低电平。 调试过程&#xff1a;在撰写代码过程中&#xff0c;又发现新的问题&#xff0c;Enter按键无法控制屏幕数…

电脑显示缺失msvcp140_1.dll怎样修复,5种快速修复方法让你快速修复

1. msvcp140_1.dll 定义 1.1 Microsoft Visual C 2015 Redistributable组件 msvcp140_1.dll 是 Microsoft Visual C 2015 Redistributable 的关键组件之一&#xff0c;扮演着至关重要的角色。以下是对 msvcp140_1.dll 的详细分析&#xff1a; 组件功能&#xff1a;msvcp140_…

《中国电子报》报道: 安宝特AR为产线作业者的“秘密武器

近日&#xff0c;中国电子报在其文章《下一代工业智能终端重新定义制造业》中对安宝特的增强现实&#xff08;AR&#xff09;解决方案给予了高度评价&#xff0c;称其为产线作业者的“秘密武器”。这一创新技术改变了传统制造业的作业方式&#xff0c;使得操作人员能够在生产过…

Ubuntu中交叉编译armadillo库

网上关于交叉编译armadillo库比较少&#xff0c;借鉴了一些但是在前几天编译时总是磕磕绊绊&#xff0c;于是写一个详细的编译过程。 交叉编译armadillo库包含两个步骤&#xff1a;交叉编译依赖库和交叉编译armadillo。armadillo官网介绍依赖库如下图所示&#xff1a; 需要注意…

【高性能内存池】page cache 5

page cache 1 page cache的框架2 central cache从page cache申请n页span的过程3 page cache 的结构3.1 page cache类框架3.2 central cache向page cache申请span3.3 获取k页的span page cache的结构和central cache是一样的&#xff0c;都是哈希桶的结构&#xff0c;并且挂载的…

JAVA全球互联同城速达国际版同城跑腿快递代取帮买帮送一体化服务系统源码

全球互联&#xff0c;便捷生活新篇章&#xff01; &#x1f31f; 开篇&#xff1a;跨越国界的即时服务革命 在这个快节奏的时代&#xff0c;你是否也曾为忙碌的生活而烦恼&#xff1f;购物、取件、送物……这些日常琐事似乎总在不经意间占据了我们宝贵的时间。但现在&#xf…

003集—— CAD批量划线和text文字(CAD—C#二次开发入门)

本例通过for循环创建255条线&#xff0c;颜色不同&#xff0c;并在线的右端点处注记文字。 效果如下: 本文有个事务的封装函数&#xff0c;如下&#xff1a; private ObjectId AppendEntity(Entity entity) { ObjectId objectId; Database db HostApplication…

❤Node实现接口增删改查(文章为例)

❤Node实现接口增删改查&#xff08;文章为例&#xff09; 1、文章表的创建​ 接下来我们新建一个文章数据表article&#xff0c;实现对于文章部分的管理功能接口 根据文章我们创建一个对应的 SQL 数据表 javascript CREATE TABLE articles (id INT AUTO_INCREMENT PRIMAR…

亚马逊卖家如何利用自养号测评策略低成本提升销量?

在跨境电商的平台上&#xff0c;随着市场逐渐成熟与竞争的白热化&#xff0c;众多卖家正面临流量增长乏力与转化率提升困难的双重挑战。为了在这日益激烈的竞争环境中脱颖而出&#xff0c;卖家们纷纷加大投入&#xff0c;探索多样化的推广策略&#xff0c;但往往因策略不够精准…

探索Kombo:AI与API的完美结合

文章目录 探索Kombo&#xff1a;AI与API的完美结合背景介绍库的定义安装指南简单函数使用场景应用常见问题及解决方案总结 探索Kombo&#xff1a;AI与API的完美结合 背景介绍 在当今快速发展的人工智能领域&#xff0c;Kombo库以其独特的优势脱颖而出。Kombo是一个专注于AI的…

2024年7月大众点评全国爱车前百名城市分析

在做一些城市分析、学术研究分析、商业选址、商业布局分析等数据分析挖掘时&#xff0c;大众点评的数据参考价值非常大&#xff0c;截至2024年7月&#xff0c;大众点评美食店铺剔除了暂停营业、停止营业后的最新数据情况分析如下。 分析研究的字段维度包括大众点评数字id、字母…