深入理解快排【C语言版】

news2024/12/25 10:12:22

目录

一、快排介绍及其思想

二、hoare版本 

三、前后指针版    

四、挖坑法 

五、优化版本

        5.1 三数取中

         5.2 小区间优化

六 、非递归实现快排

 七、三路划分

 八、introsort

小结 


一、快排介绍及其思想

        快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法。

        思想:

1.先从数列中取出一个数作为基准数。

2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。

3.再对左右区间重复第二步,直到各区间只有一个数。

二、hoare版本 

        hoare版本是最原始版本,其实现思想如下:

        从上面动图我们不难分析出单趟有以下特点:

        1. 以首元素为k值

        2. 先在右边找比k小的数

        3. 然后在左边找比k大的数

        4. 最后,k与左边进行交换

        我们很容易写出以下代码:

int k = left;
int begin = left;
int end = right;
while (a[k] < a[end])
{
	end--;
}
while (a[k] > a[begin])
{
	begin++;
}
swap(&a[begin], &a[end]);

         我们很容易发现这代码有问题,啥问题?是不是很容易出现越界访问?当没有数比a[end]大时,很容易出现失控,下面也是同理。那么?我们该如何进行处理?很简单,加上控制条件即可,如下:

int k = left;
int begin = left;
int end = right;
while (begin < end && a[k] < a[end])
{
	end--;
}
while (begin < end && a[k] > a[begin])
{
	begin++;
}
swap(&a[begin], &a[end]);

        这样便可进行控制,既然单趟已完成,那么多趟自然不在话下,这里我们用递归方式进行实现。

int PartSort1(int* a, int left,int right)
{
	int k = left;
	int begin = left;
	int end = right;
	while (begin < end)
	{
		while (begin < end && a[k] < a[end])		//这里等号可加可不加
		{
			end--;
		}
		while (begin < end && a[k] > a[begin])	
		{
			begin++;
		}
		swap(&a[begin], &a[end]);
	}
	swap(&a[begin], &a[k]);
	return begin;
}

void QuickSort(int* a, int left,int right)
{
	if (left >= right)		//递归结束条件判断
	{						//当左边与右边相等时,不用进行任何处理
		return;
	}
	int k = PartSort1(a, left, right);
	QuickSort(a, left, k - 1);
	QuickSort(a, k + 1, right);	//区间为:左闭右开
}

        那我们这时有个问题:为什么分要从右边开始,为何不能从左边开始?

        我们要明白一件事:咱们要确保每一趟的k值的左边一定要比k值小,右边一定要比k值大才行,我们来看下面这组例子:

        如果我们从左开始,左边找比k值大的,右边找比k值小的,其结果无外乎为:把6与5换一个位置,仅此而已,咱们的目的肯定会达不到。

        要是,我们先找大呢?会发现左边的1会不参与右边的排序,只需将剩下的进行排序,我们对其进行分析,符合我们的目的,所以,我们一定从右边先找小!!! 

三、前后指针版    

     

         前后指针方法,相比于hoare版,算法思路和实现过程有了较大的提升,是目前较为主流的写法。

        通过上面动图我们可得到以下结论:

        1. 以首元素为k值

        2. 设立两个指针:prev,cur

        3. cur位于begin+1的位置,prev位于begin位置,k先存放begin处的值。
        4. cur不断往前+1,直到cur >= end时停止循环。
        5. 如果cur处的值小于key处的值,并且prev+1 != cur,则与prev处的值进行交换。
        6. 当循环结束时,将prev处的值与k的值相交换,并将其置为新的keyi位置。

        代码实现:

int PartSort2(int* a, int left, int right)
{
	int k = left;
	int prev = left;
	int cur = prev + 1;
	while (cur <= right)
	{
		if (a[cur] < a[k] && ++prev != cur)			//这里要确保当cur遇到比k小的数时,prev要++
		{
			swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	swap(&a[k], &a[prev]);
	return prev;
}

         这里简单说明一下:

        当prev == cur时,因为相等无交换必要,但无论如何只要cur遇到比k小的数时,prev都要++。

四、挖坑法 

        我们可以看到挖坑法其实和hoare版类似,那为什么还要出该版本呢? 主要是有人搞不清到底先走左还是先走右,所以推出了该版本,该版本更容易理解。

        特点如下:

        1. 将begin处的值放到k中,将其置为坑位(piti),然后right开始行动找值补坑。
        2. right找到比k小的值后将值放入坑位,然后将此处置为新的坑。
        3. left也行动开始找值补坑,找到比k大的值将其放入坑位,置为新的坑。
        4. 当left与right相遇的时候,将k放入到坑位中。
        5. 然后进行[begin,piti-1],  piti,   [piti+1,end] 的递归分治。

        因为有以上基础,所以,我们不在进行赘述。代码实现如下:

int PartSort3(int* a, int left, int right)
{
	int k = a[left];		//此处要放入值
	int piti = left;
	int begin = left;
	int end = right;
	while (begin < end)
	{
		while (begin < end && a[end] > k)
		{
			end--;
		}
		a[piti] = a[end];
		piti = end;
		while (begin < end && a[begin] < k)
		{
			begin++;
		}
		a[piti] = a[begin];
		piti = begin;
	}
	a[piti] = k;
	return piti;
}

五、优化版本

        5.1 三数取中

                通过以上的版本使我们意识到了一个问题:制约快排效率主要的一个因素为:选k。这个k选得好与不好直接关系到快排的效率问题,所以,有人就提出了三数取中这个方法。

                方法为:即知道这组无序数列的首和尾后,我们只需要在首,中,尾这三个数据中,选择一个排在中间的数据作为基准值(keyi),进行快速排序,即可进一步提高快速排序的效率

int Getmid(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] > a[right])
	{
		if (a[right] > a[mid])
		{

			return right;
		}
		else if (a[mid] > a[left])
		{
			return left;
		}
		else
		{
			return mid;
		}
	}
	else
	{
		if (a[right] < a[mid])
		{
			return right;
		}
		else if (a[right] < a[left])
		{
			return left;
		}
			
		else
		{
			return mid;
		}
	}
}

                这时,我们把我们的k值替换为GetMid的返回值即可。 

         5.2 小区间优化

                当我们在进行排序时,如果剩下一个小区间我们仍用快排进行排序时,会降低其效率,那么,这时我们就可以考虑用其他排序来进行排序,从而提高效率。

                如:当我们数据量小于10时,我们就可以用插入排序来进行排序。

void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (tmp < a[end])
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}

void QuickSort(int* a, int left,int right)
{
	if (left >= right)		
	{						
		return;
	}
	if ((right - left + 1) <= 10)				//小区间优化
	{
		InsertSort(a, (right - left + 1));
	}
	int k = PartSort3(a, left, right);
	QuickSort(a, left, k - 1);
	QuickSort(a, k + 1, right);	
}

六 、非递归实现快排

        在用非递归实现快排时,我们要借助栈这个数据结构来辅助实现。

        实现思路:

  1. 入栈一定要保证先入左再入右。
  2. 取两次栈顶的元素,然后进行单趟排序。
  3. 划分为[left , k - 1] k [ k +  1 , right ] 进行右、左入栈。
  4. 循环2、3步骤直到栈为空。

        代码实现:

void QuickSortNonR(int* a, int left, int right)
{
	ST sk;
	STInit(&sk);
	STPush(&sk, right);
	STPush(&sk, left);

	while (!STempty(&sk))
	{
		int begin = STTop(&sk);
		STPop(&sk);
		int end = STTop(&sk);
		STPop(&sk);

		int k = PartSort1(a, begin, end);
		if (k + 1 < end)
		{
			STPush(&sk, end);
			STPush(&sk, k + 1);
		}

		if (begin < k - 1)
		{
			STPush(&sk, k - 1);
			STPush(&sk, begin);
		}
	}
	STDestory(&sk);
}

 七、三路划分

        为了提高快排效率,有人提出了三路划分,叫我们一起来了解一下吧!

        相信大家在快排中会遇到这种情况:一个数组中有多个数据连续情况,这时,我们采用以上版本的话,就会有效率问题。

        如若各位不信,可试试用快排做一下这道题目:. - 力扣(LeetCode)

        另外这道题目大家可发现这样一种现象:LeetCode官方的C++题解跑不过去。

        好了,话不多说,开始寻求解决办法吧!

        三路划分实现的大思路其实和前后指针大差不差,不过会有改动地方:把相同的数据放到中间

        这里,我们说一下三路划分思路:

        1. k默认取左边位置。

        2. left指向最左边,right指向最右边,cur取left下一个位置。

        3. cur遇到比left小的就和left交换位置,left++,cur++。

        4. cur遇到比left大的就无脑和right交换位置,right--。

        5. 遇到相同的值就cur++,直到结束。 

        代码实现:

void PartSort3Way(int* a, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int k = a[left];	//这里直接赋值,不然不好控制
	int cur = left + 1;
	int begin = left;
	int end = right;
	while (cur <= right)
	{
		if (a[cur] < k)
		{
			swap(&a[cur], &a[left]);
			cur++;
			left++;
		}
		else if (a[cur] > k)
		{
			swap(&a[cur], &a[right]);
			right--;
		}
		else
		{
			cur++;
		}
	}
	PartSort3Way(a, begin, left - 1);
	PartSort3Way(a, right + 1, end);
}

 八、introsort

        这里,我们再简单介绍一下introsort版本的,它目前是官方版的快排。

        introsort是introspective sort采⽤了缩写,他的名字其实表达了他的实现思路,他的思路就是进⾏⾃ 我侦测和反省,快排递归深度太深(sgi stl中使⽤的是深度为2倍排序元素数量的对数值)那就说明在 这种数据序列下,选key出现了问题,性能在快速退化,那么就不要再进⾏快排分割递归了,改换为堆 排序进⾏排序。

        它就是可以理解为将堆排,插入排序,快排揉在一起的缝合怪。其实现思想简单,这里就说明一下,主要体现在以下三方面,大家感兴趣可以自行去实现一下。

        实现思想:

        1. 小区间优化思想:数组⻓度⼩于16的⼩数组,换为插⼊排序,简单递归次数。

        2. 定义变量logN检查递归深度:当深度超过2*logN时改⽤堆排序。

        3. 选k方面:借助rand函数采用了随机选k的方法。

小结 

        本文对于快排的实现做了较为深入的讲解,内容有较大难度,大家看完之后,看完后难以理解,可借助画图帮助理解。写排序时,先控制单趟在控制多趟较为容易。好了,本文的内容到这里就结束了,如果觉得有帮助,还请一键三连多多支持一下吧!

完!

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

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

相关文章

掌握CompletableFuture,提升你的代码效率!

文章目录 1 CompletableFuture与线程池之间有什么关系&#xff1f;2 如何优化CompletableFuture的性能&#xff1f;3 实际项目中&#xff0c;以并行执行多个HTTP请求为例&#xff0c;你会如何优雅使用CompletableFuture 解决问题&#xff1f; 1 CompletableFuture与线程池之间有…

计算机毕业设计选题推荐-在线音乐网站-音乐专辑商城-Java/Python项目实战

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

埃隆·马斯克超级计算新里程碑:Cortex AI超级集群震撼亮相!

本周&#xff0c;科技界的超级明星埃隆马斯克再次引领潮流&#xff0c;他在超级计算领域的征途上迈出了令人瞩目的步伐。通过一段视频&#xff0c;他首次公开了最新命名的“Cortex”人工智能超级集群&#xff0c;这一壮举不仅标志着特斯拉“Giga Texas”工厂的又一次重大扩张&a…

LeetCode_sql_day17(1843.可疑银行账户)

描述&#xff1a; 表&#xff1a;Accounts ---------------------- | Column Name | Type | ---------------------- | account_id | int | | max_income | int | ---------------------- account_id 是这张表具有唯一值的列。 每行包含一个银行账户每月最大收入的…

提供开发资料 Hi3516CV610-00B/10B/20B/00S/20S/00G/20G 七个型号配置差异

根据功能不同&#xff0c; Hi3516CV610 分为七个不同型号版本: HI3516CV610-00B HI3516CV610-00B HI3516CV610-10B HI3516CV610-20B HI3516CV610-00S HI3516CV610-20S HI3516CV610-00G HI3516CV610-20G

【书生2.1】书生大模型全链路开源体系

0 引言 书生浦语官网 开源一周年总结及回顾 1 回顾 1.1 社区生态 2 总结 书生浦语大模型的开源开放体系&#xff0c;包括技术发展、性能提升、模型架构、开源生态等。 要点: &#x1f31f; 开源开放体系涵盖数据收集、标注、训练、微调、评测、部署等全链路。 &#x1f68…

【案例64】无法从套接字读取更多的数据

问题现象 系统突然间登录报如下错误&#xff1a;SELECT * FROM sm_user WHERE user_code_q? 无法从套接字读取更多的数据 问题分析 查看nc-log.log发现大量相关报错 $$callid1723104097968-1063 $$thread[http-bio-xxx-xxx-exec-xxx] $$hostxxx$$userid#UAP# $$tsxxx-08-08…

C++竞赛初阶L1-14-第六单元-数组(31~33课)542: T456472 数组逆序重存放

题目内容 将一个数组中的值按逆序重新存放。例如&#xff0c;原来的顺序为 8,6,5,4,1。要求改为 1,4,5,6,8。 输入格式 输入为两行&#xff1a;第一行数组中元素的个数 n&#xff08;1<n≤100)&#xff0c;第二行是 n 个整数&#xff0c;每两个整数之间用空格分隔。 输出…

Windows安装PostgreSQL数据库,保姆级教程

PostgreSQL 是客户端/服务器关系数据库管理系统 (RDMS)。PostgreSQL是一个功能非常强大的、源代码开放的客户/服务器关系型数据库管理系统&#xff08;RDBMS&#xff09;。PostgreSQL 也有自己的查询语言&#xff0c;称为 pgsql。 此外&#xff0c;PostgreSQL 还支持过程语言&a…

Cesium模型封装-Point

一、初始化地图 <template><div class"cesium_map"><div id"cesiumContainer"></div></div> </template><script setup> import { reactive, ref, onMounted } from "vue"; import { Point } from &…

基于yolov8的安全帽反光衣护目镜检测系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv8的安全帽、反光衣及护目镜检测系统是一款集成了前沿深度学习与计算机视觉技术的智能监控系统。该系统利用YOLOv8这一尖端的目标检测模型&#xff0c;结合云计算与自动化图像处理技术&#xff0c;实现对工地、化工厂、煤矿等高风险作业区域工作人员安全…

Java—方法引用

目录 初识方法引用 方法引用的分类 引用静态方法 引用成员方法 引用构造方法 其它调用方式 类名引用成员方法 引用数组的构造方法 总结 初识方法引用 方法引用就是拿现有的方法来当做函数式接口中抽象方法的方法体。 方法引用注意事项 1. 引用处必须是函数式接口&a…

初识JAVA(上)

&#x1f381;&#x1f381;创作不易&#xff0c;关注作者不迷路&#x1f380;&#x1f380; 初识JAVA 前言一、初识JAVA1.1.Java是什么1.2.Java语言的重要性1.3 Java语言发展简史1.4 Java语言特性 二、初识Java的main方法1 main方法示例 三、注释基本规则 四、数据类型1.常量2…

入门Java第一步—>IDEA的下载与安装与JDK的环境配置(day01)

1.JDK的下载与安装 jdk的安装链接分为不同操作系统如下,点击链接跳转下载页面&#xff1a; windows操作系统JDK下载链接(按住键盘ctrl键单击链接即可)&#xff1a; 链接7天有效&#xff0c;有需要的评论区找我哈 通过网盘分享的文件&#xff1a;jdk-8u271-windows-x64.exe 链…

建筑企业数字信息化转型的建议

在现代建筑企业的管理中&#xff0c;信息化转型已成为提升效率和竞争力的关键。然而&#xff0c;在选择信息化系统时&#xff0c;企业需要慎重考虑&#xff0c;以确保系统真正适合企业的现状和未来发展。 &#x1f50d; 要选合适的&#xff0c;而非“成熟”的 信息化系统的核心…

解决 启动模拟器出现 未开启Hyper-V 的问题

~~ 解决 启动模拟器出现 未开启Hyper-V 的问题 ~~ 如果在启动模拟器时出现 未开启Hyper-V 的问题 解决方案&#xff1a; 1.打开控制面板–>点击 程序和功能 2.点击左侧&#xff1a;启用或关闭Windows功能 3.找到虚拟机平台–> 打对勾√ -->确定 &#xff08;注意…

harbor私有仓库管理(twenty-nine day)

一、harbor私有仓库管理 是python的包管理工具&#xff0c;和yum对redhat的关系是一样的 yum -y install epel-release yum -y install python2-pip pip install --upgrade pip pip list pip 8x pip install --upgrade pip pip install --upgrade pip20.3 -i https://mirror…

ElasticSearch学习笔记(四)分页、高亮、RestClient查询文档

文章目录 前言7 搜索结果处理7.2 分页7.2.1 基本使用7.2.2 深度分页7.2.3 小结 7.3 高亮7.3.1 高亮原理7.3.2 实现高亮 8 RestClient查询文档8.1 match_all查询8.2 match查询与multi_match查询8.3 精确查询8.4 布尔查询8.5 排序、分页、高亮 9 项目实战9.1 酒店搜索和分页9.2 酒…

Linux 软件包管理器yum 自动化构建工具-make/makefile

Linux 工具 linux 软件包管理器 yum 把一些常用的软件提前编译好&#xff0c;做成软件包放在一个服务器上&#xff0c;通过包管理器可以很方便的获取到在这个编译好的软件包。直接进行安装。 软件包和软件包管理器就相当于 App 和应用商店这样的关系。 Linux 安装软件 源代码…

【QT】学习笔记:导出资源中静态文件

在 Qt C 中&#xff0c;可以通过将文件添加到资源文件中&#xff0c;并在程序运行时将其导出到磁盘上的指定目录。以下是具体的步骤和代码示例&#xff1a; 1. 将文件添加到资源文件中 首先&#xff0c;需要将文件添加到 Qt 的资源系统中。假设你已经创建了一个资源文件&…