深入理解并实现——快排【C语言版】

news2024/9/21 12:26:48

目录

一、快排介绍及其思想

二、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/2099074.html

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

相关文章

【数据结构】Map的使用与注意事项

文章目录 概念模型Map 的使用put() 和 get()getOrDefault()remove()keySet()entrySet() 注意事项 概念 Map 和 set 是一种专门用来进行搜索的容器或者数据结构&#xff0c;其搜索的效率与其具体的实例化子类有关。 以前常见的搜索方式有&#xff1a; 直接遍历&#xff0c;时间…

Spring AOP(下)原理

本文我们来学习 Spring AOP 的原理&#xff0c;也就是 Spring 是如何实现 AOP 的。Spring AOP 是基于动态代理来实现 AOP 的&#xff1b; 1. 代理模式 1.1 代理弄模式的定义 代理模式&#xff0c;也叫委托模式。 定义&#xff1a;为其他对象提供一种代理以控制这个对象的访问…

【第三版 系统集成项目管理工程师】第14章 收尾过程组

持续更新。。。。。。。。。。。。。。。 【第三版】第十四章 收尾过程组 14.1结束项目或阶段14.1.1主要输入1.项目章程-P5392.项目管理计划-P5393.项目文件-P5394.验收的可交付成果-P5405.协议-P5406.采购文档-P540 14.1.2主要输出1.最终产品、服务或成果-P5402.项目最终报告…

如何在IntelliJ IDEA中将Tab设置为4个空格

前言 IntelliJ IDEA是一个强大的开发工具&#xff0c;支持多种编程语言。为了保持代码整洁一致&#xff0c;开发者经常需要调整编辑器中的Tab和缩进设置。 步骤1: 打开设置 首先&#xff0c;启动IntelliJ IDEA。在主界面上方的菜单栏中找到 File&#xff08;文件&#xff09…

体育直播平台开发:初创公司突破资金与市场的双重挑战

在当今竞争激烈的数字娱乐行业中&#xff0c;体育直播平台的发展潜力巨大。然而&#xff0c;面对如此巨大的蓝海市场&#xff0c;对于初创公司或中小型平台而言&#xff0c;高昂的开发和运营成本可能成为进入该行业的主要障碍。本文将探讨一些可行的低成本开发策略&#xff0c;…

Matlab三维图的坐标轴标签 自动平行坐标/自动旋转

下载解压工具包&#xff1a; https://www.mathworks.com/matlabcentral/fileexchange/49542-phymhan-matlab-axis-label-alignment 添加至MATLAB路径: 在三维绘图后增加下列语句即可 ax struct(Axes, gca); align_axislabel([],ax) h3d rotate3d; set(h3d,ActionPreCa…

k8s项目的发布(金丝雀发布)

目录 三种发布方式 1.蓝绿发布 2.金丝雀发布&#xff08;灰度发布&#xff09; 实验&#xff1a;k8s实现金丝雀发布 3.滚动发布&#xff08;默认形式&#xff09; 因为应用升级以及新旧业务切换&#xff0c;所以在这个过程当中如何保证对外的服务正常是一个非常重要的问题…

/单元测试

承接上文 统一异常处理&#xff0c;封装结果-CSDN博客 ******************************************** 登录业务 Service public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {Resourceprivate JwtUtils j…

深入探索批处理中的变量与命令:从基础到高级

更多内容前往&#xff1a;孔乙己大叔 在Windows环境中&#xff0c;批处理&#xff08;Batch&#xff09;文件是一种非常有用的脚本工具&#xff0c;允许用户自动化重复性任务。通过编写批处理脚本&#xff0c;用户可以执行一系列命令&#xff0c;而无需手动输入每个命令。变量是…

怎么把视频压缩变小?快码住这篇视频压缩教程

众所周知&#xff0c;视频已经成为人们交流信息的重要方式之一。 然而&#xff0c;随着视频分辨率越来越高&#xff0c;文件大小也随之膨胀&#xff0c;这给存储空间带来了不小的压力&#xff0c;同时也影响了视频在网络上的传输速度。 因此&#xff0c;掌握视频压缩技巧&…

E1.S接口如何解决SSD过热问题?

针对SSD接口标准&#xff0c;目前业内有两大组织&#xff1a; PCI-SIG&#xff1a;这个就是定义pcie协议标准的那个组织&#xff0c;我们常见的传统接口M.2, U.2, 2.5英寸的接口都归这个组织定义规范。M.2&#xff0c;U.2起源与客户端&#xff0c;也是企业级当前最常用的接口形…

Day22_K8S

文章目录 3.资源管理方式通过命令管理通过配置文件管理4. 基本概念入门4.1 Namespace4.2 Pod4.3 Label4.4 Deployment4.5 Service5. Pod详解5.1 Pod介绍5.2 Pod配置5.3 Pod生命周期5.3.1 初始化容器5.3.2 钩子函数5.3.3 容器探测5.3.4 重启策略5.4 Pod调度5.4.1 定向调度5.4.2 …

【技巧】Excel检查单元格的值是否在另一列中

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 用到的excel函数 IF(ISNUMBER(MATCH(H2, I2:I10, 0)), H2, "") 注意改上面的“H2、I2、I10”&#xff01; 函数效果 函数解释 检查单元格 …

简述二叉树先序遍历、中序遍历和后序遍历的思想。

先序遍历&#xff08;根左右&#xff09;(DLR) 若二叉树为空&#xff0c;则退出&#xff0c;否则进行下面操作:访问根结点、先序遍历左子树、先序遍历右子树。 中序遍历&#xff08;左根右&#xff09;(LDR) 若二叉树为空&#xff0c;则退出&#xff0c;否则进行下面操作:中…

Nginx: 配置文件重载的原理和热部署

配置文件的重载原理 我们可以通过给Nginx的master进程发送 HUP 信号, 或者是使用Nginx的二进制程序 执行 reload命令来重新载入配置文件, 从而实现Nginx的一个平滑升级对Nginx的背后到底是发生了一个什么样的事情去保证新老配置的平滑过渡的 reload 重载配置文件的流程 第一步…

Amazon SPAPI Deloitte(德勤)审计问题流程

作为Amazon开发者审计的一部分&#xff0c;亚马逊会对使用其API的开发者进行严格的审核和评估。 这个过程旨在确保开发者遵守亚马逊的各项政策和安全要求&#xff0c;特别是在处理个人身份信息&#xff08;PII&#xff09;和其他敏感数据时。 下面是一些关于亚马逊开发者审计的…

【Python 千题 —— 算法篇】逆序字符串

Python 千题持续更新中 …… 脑图地址 👉:⭐https://twilight-fanyi.gitee.io/mind-map/Python千题.html⭐ 题目背景 字符串操作在编程中非常常见,无论是数据处理、文本分析还是算法设计,字符串的处理都是基础且关键的一部分。尤其是在数据处理和编程竞赛中,字符串逆序是…

常见错误

常见错误 报错&#xff1a;TypeError: setUpClass() missing 1 required positional argument: ‘cls’ 解决办法如上图 报错&#xff1a;TypeError: a bytes-like object is required, not ‘str’ ###报错&#xff1a;ERROR: usage: pytest [options][file_or_dir] [file_or_…

三(五)子棋实现

设计一个小游戏其实是对自己掌握一门编程语言的一个升华&#xff0c;几百行代码分项目进行这种很让人着迷的感觉哦&#xff01; 与五子棋游戏其实本质区别只不过是判输赢的条件不同&#xff0c;这里我打算写写三子棋小游戏。 代码的最后我将所有源代码整理了&#xff0c;大家急…

【C++ Primer Plus习题】8.2

问题: 解答: #include <iostream> using namespace std;typedef struct _CandyBar {string brand;float weight;int hot; }CandyBar;void fill(CandyBar& cb, const char* name"Millennium Munch", double w2.85, int h350) {cb.brand name;cb.weight w…