排序——手撕快排

news2024/9/23 3:26:30

本节复习快速排序, 快排我们要讲三个版本:一种是霍尔大佬的原版版本, 也就是快速排序的原版。 一种挖坑法。还有一种前后指针法。 

首先我们应该知道,三个版本针对的是单趟进行排序的方法不同。 而多趟使用的是递归或者非递归模拟二叉树。 是的, 快排分为递归版本和非递归版本。 两种版本的实现方法本节都会详细实现。 由于单趟是一样的, 我们先来分析单趟的方法。

目录

单趟排序

霍尔版本

 挖坑法

前后指针法

多趟排序

递归

 非递归


单趟排序

霍尔版本

快排的三个版本的单趟的函数中, 都要传送进行排序的区间。 不用传送排序的数据个数。就像这样:

void partSort1(int* arr, int left, int right)//arr是要排序的数组, left是左闭区间, right是右闭区间
{
    //……内容
}

然后, 我们来看一下具体的实现过程:、

假设有这么一串数组:a[10] = { 6, 0, 7, 5, 4, 3, 1, 2, 8, 9 }。要对这个数组进行排序, 那么单趟过程如下:

具体过程是这样的:left就代表着红指针, right就代表着绿指针。

然后还有一个keyi指针指向left的初始位置, 也就是最左边(这里不是必须指向最左边。 也可以指向最右边。 不过指向的初始位置决定着过程的走向, 这里以最左边演示)。

然后让left红指针从左向右找大的数, 让right绿指针从右向左找小的数。只要两个指针都找到了, 那么就让两个指针指向的位置交换数据。 (注意:这里要绿指针先找, 红指针后找,因为keyi指向最左边, 最左边最后放置的应该是比目前keyi所指向的小的数。现在还说不清。 继续向后看)直到两个指针相遇。或者者left红指针在right绿指针右边。然后这个时候最左边keyi指向的数据和两个指针相遇位置的数据交换。 这个时候单趟就结束了。

此时两指针所指向位置就是排好的一个位置, 左边的值一定比两指针指向位置的值小, 右边的值一定比两指针指向位置的值大。

这个过程中keyi和两指针指向的位置交换数据是最后一步, 交换后这一趟结束, 并且交换后两指针指向位置的数据就是原keyi的数据, 并且这个位置的值已经排好。这说明两个值交换之前keyi的值大于两指针指向的数据。造成这个现象的原因就是因为我们先让右指针找, 再让做指针找。 因为右指针找的过程中只有两种情况, 一种是找到了比keyi小的数据,一种是和左指针相遇。而左指针在上一步找到比keyi大的数据后又和一个比keyi小的数据交换了。所以这个时候左指针也是指向的小数据。 所以让右指针先找的话最后两指针相遇的位置一定是一个比keyi小的值。 

而如果让keyi指向最左边的话就要让右指针先动。 原因同上自行分析。

现在来写一下代码:


//霍尔版本
int partSort1(int* a, int left, int right) 
{
	int get = GetMidIndex(a, left, right);
	Swap(&a[get], &a[left]);
	int keyi = left;
	
	while (left < right) 
	{

		//先找右, 防止左右指针相遇的位置是一个比keyi大的数字
		while (left < right && a[right] >= a[keyi]) //&&左边这里为了相遇后随时能够跳出循环。&&右边加=号是为了防止右指针找到一个和keyi相同的数字, 左指针找到一个和keyi相同的数字,交换后数值不变造成死循环。
		{
			right--;
		}

		//
		while (left < right && a[left] <= a[keyi]) 
		{
			left++;
		}
		//
		Swap(&a[left], &a[right]);//交换左右指针指向的数据
	}
	Swap(&a[keyi], &a[left]);//这个left指针指向的一定不会比keyi指向的大。 因为right先找, 找到了也就是找到了, 找不到也会
	//和left指向同一个位置。 所以不会比keyi大。

	return left;//返回分割位置.
}

 挖坑法

挖坑法的思想和霍尔的类似, 知识没有霍尔的坑那么多。 

如图:

 

就是让一个变量key保存最左边的值。

然后让一个坑指针指向这个位置, 将这个位置看作没有值。 然后右指针开始找小的(这里就记住, 右边因为比最后的排好位置的值要大, 所以要找小,将这个小的值扔到左边。 左边同样的道理, 因为左边的值要小于那个值, 所以要找大, 然后将大的值扔到右边, 然后霍尔是互相扔, 挖坑是扔到对方挖的坑里面)。找到后将值放到左边的坑里,这里的坑被填了。 然后右指针指向的位置的值相当于没了, 就让坑指向该位置。

往复, 直到两指针相遇, 将保存的key的值放到坑里。这个key的值就是排好的单趟的值。

这里为代码


//挖坑法
int partSort2(int* a, int left, int right) 
{
	int get = GetMidIndex(a, left, right);
	Swap(&a[get], &a[left]);
	int key = a[left];
	int hole = left;//让hole开始等于坑


	while (left < right) 
	{
		while (left < right && a[right] >= key) //右边找小, 所以遇到大于key的就继续向左找
		{
			right--;
		}
		//找到后就将值给给坑的位置, 然后自己的位置变成坑
		a[hole] = a[right];
		hole = right;

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

	a[hole] = key;

	return hole;
}

前后指针法

前后指针法不同于上面两种方法, 思想有差别。 

前后指针法的流程就是cur遇不到小的就一路走到底, 遇到小的prev指针就向前追。 同时两个指针的值互相扔给对方。

下图为过程图:

 

 绿指针cur只要不遇到小于keyi指针指向的值得数就向前走, 遇到得话prev就向前移动一位然后和绿指针cur指向位置交换数据。 直到cur越界退出循环然后prev红指针和keyi指向的位置互换数据。 这个时候prev指向的位置就是排好序的位置。 

以下为代码


//前后指针法
int partSort3(int* a, int left, int right)
{

	int cur = left + 1;//cur指向第二个数据
	int prev = left;//prev和keyi都指向第一个数据
	int keyi = left;
	int get = GetMidIndex(a, left, right);
	Swap(&a[get], &a[keyi]);

	while (cur <= right) //遍历直到cur出了数组范围
	{
		if (a[cur] < a[keyi] && ++prev != cur) //cur找小, 只要找到小的, 那么prev++后交换prev和cur的数据。 
			//找不到小的;cur就一路往前冲。
		{
			Swap(&a[prev], &a[cur]);
		}
		cur++;

	}
	Swap(&a[prev], &a[keyi]);//交换完成之后, prev指向了小于keyi的最后一个数据。 然后keyi的值和prev的值交换。
	
	return prev;//返回prev。
}

以上, 三个单趟排序方法都有一个特点, 那就是每趟都排好一个数据, 而且这个数据的前面的数据一定小于这个数据。 后面的数据一定大于这个数据。 

多趟排序

多趟排序分为递归和非递归。

递归

首先来实现递归方法,如下为代码:


//快排递归方法
void QuickSort(int* a, int begin, int end) 
{
	if (begin >= end) //如果begin >= end说明只剩一个节点, 一个节点就是有序或者区间不存在。 都要返回。
	{
		return;
	}
	//
	int keyi = partSort3(a, begin, end);//递归就是分治, 每次单趟排序后都接收一下排好的位置, 然后对这个位置的左边和这个位置的右边再次进行单趟排序。 

	QuickSort(a, 0, keyi - 1);//分治左树
	QuickSort(a, keyi + 1, end);//分治右树

}

递归就是分治, 每次单趟排序后都接收一下排好的位置, 然后对这个位置的左边和这个位置的右边再次进行单趟排序。 一直分治到只剩下一个节点或者给的排序区间根本不存在。如图:

 非递归

这里来使用栈实现非递归。

如下为代码


//快排非递归
void QuickSortRno(int* a, int begin, int end) 
{
	Stack st;
	StackInit(&st);//栈初始化

	//先将第一趟数据压栈
	StackPush(&st, end);//
	StackPush(&st, begin);//
	//

	while (!StackEmpty(&st)) //栈不是空那么就进入循环
	{
		//取到左右区间这两个数据。 
		int left = StackTop(&st);//取出左右区间, 然后进行但趟排序。 
		StackPop(&st);

		int right = StackTop(&st);
		StackPop(&st);//

		int keyi = partSort3(a, left, right);



		if (right > keyi + 1) //然后将新的区间压入栈中。 遇到节点或者不存在区间就不能压栈。 然后直到栈中没有数据。 
		{
			StackPush(&st, right);
			StackPush(&st, keyi + 1);
		}
		if (keyi - 1 > left) 
		{
			StackPush(&st, keyi - 1);
			StackPush(&st, left);
		}

	}

	StackDestroy(&st);


}

非递归需要使用栈压入区间。 然后判定排序结束的条件就是栈中无数据。 

这个过程同样是拆解分治的过程。 和递归相同, 递归过程所调用的函数 非递归过程都调用了一遍。 只不过过程不同:递归是函数逐步调用, 层数往深处走。 而非递归是平行的,将需要调用的函数的参数区间保存到栈中, 一个函数调用完成之后继续调用另一个函数。 

其中需要注意的是区间先入的后出。 后入的先出。

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

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

相关文章

Carla自动驾驶仿真九:车辆变道路径规划

文章目录 前言一、关键函数二、完整代码效果 前言 本文介绍一种在carla中比较简单的变道路径规划方法&#xff0c;主要核心是调用carla的GlobalRoutePlanner模块和PID控制模块实现变道&#xff0c;大体的框架如下图所示。 一、关键函数 1、get_spawn_point(),该函数根据指定r…

360文件夹(窗口标签化工具)使用:windows系统的文件管理标签化

软件介绍 360 文件夹是一款单窗口多标签的资源管理器&#xff0c;提高了用户使用各类文件夹操作效率。单窗口多标签&#xff0c;像浏览器一样用多标签管理每个文件夹&#xff0c;以便更加快速高效地切换文件夹&#xff0c;告别凌乱的窗口&#xff0c;加快办公效率&#xff1b;…

剑指offer面试题28:对称的二叉树

#试题28&#xff1a;对称的二叉树 题目&#xff1a; 请设计一个函数判断一棵二叉树是否 轴对称 。 示例 1&#xff1a; 输入&#xff1a;root [6,7,7,8,9,9,8] 输出&#xff1a;true 解释&#xff1a;从图中可看出树是轴对称的。示例 2&#xff1a; 输入&#xff1a;root …

ssm个人学习01

Spring配置文件: spring环境的搭建: 1:导入对应的spring坐标 也就是依赖 2:编写controller, service, dao相关的代码 3:创建配置文件(在resource下面配置文件) 例如:applicationContext.xml <bean id "" class ""> <property name "&…

SQL窗口函数, 测试题

第一题 create table user_score (logday date, -- 考试时间 userid VARCHAR(20), -- 考试用户 score int); -- 考试成绩Insert into user_score values (2019-10-20,11111,85) ,(2019-10-20,22222,83) ,(2019-10-20,33333,86) ,(2019-10-21,11111,87) ,(2019-10-2…

node 之 http模块

1.什么是http模块 在网络节点中&#xff0c;负责消费资源的电脑叫做客户端&#xff1b;负责对外提供网络资源的电脑&#xff0c;叫做服务器 http模块是node.js官方提供的&#xff0c;用来创建web服务器的模块&#xff0c;通过http模块提供的http.createServer()方法&#xff0c…

烧脑问题解决办法:如何选择一款合适自己的手机流量卡

现在社会人们越来越离不开手机了&#xff0c;手机给我们生活带来了翻天覆地的变化&#xff0c;手机需要最多的就是流量了&#xff0c;所以选择一款合适自己的手机流量卡就显得尤为重要了&#xff0c;今天小编就给大家来分享一下我的经验&#xff0c;希望对大家能有帮助&#xf…

构建大语言模型的四个主要阶段

大规模语言模型的发展历程虽然只有短短不到五年的时间&#xff0c;但是发展速度相当惊人&#xff0c;国内外有超过百种大模型相继发布。中国人民大学赵鑫教授团队在文献按照时间线给出 2019 年至 2023 年比较有影响力并且模型参数量超过 100 亿的大规模语言模型。大规模语言模型…

关于synchronized介绍

synchronized的特性 1. 乐观锁/悲观锁自适应,开始时是乐观锁,如果锁冲突频繁,就转换为悲观锁 2.轻量级/重量级锁自适应 开始是轻量级锁实现,如果锁被持有的时间较长,就转换成重量级锁 3.自旋/挂起等待锁自适应 4.不是读写锁 5.非公平锁 6,可重入锁 synchronized的使用 1&#…

01背包(详细)

背包最大重量为4。 有物品3件&#xff0c;分别有其质量和价值。 vector<int> weight{1,3,4}; vector<int> value{15,20,30}; int bag4; 问背包能背的物品最大价值是多少&#xff1f; 这是标准的动态规划问题&#xff0c;每一个问题鱼鳍前面的子问题相联。 目…

structuredClone() 详解

您是否知道&#xff0c;现在 JavaScript 中有一种原生的方式可以深拷贝对象&#xff1f; 没错&#xff0c;这个内置于 JavaScript 运行时的structuredClone函数就是这样&#xff1a; const calendarEvent {title: "Builder.io大会",date: new Date(123),attendees…

#WEB前端(CSS基础)

1.实验&#xff1a;HTML是网页骨架&#xff0c;CCS是网页装修 2.IDE&#xff1a;VSCODE 3.记录&#xff1a; style 4.代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"view…

Ajax(黑马学习笔记)

Ajax介绍 Ajax概述 我们前端页面中的数据&#xff0c;如下图所示的表格中的学生信息&#xff0c;应该来自于后台&#xff0c;那么我们的后台和前端是互不影响的2个程序&#xff0c;那么我们前端应该如何从后台获取数据呢&#xff1f;因为是2个程序&#xff0c;所以必须涉及到…

风电机组来说,CMS振动数据和SCADA数据各有其优点和缺点

对于风电机组来说&#xff0c;CMS振动数据和SCADA数据各有其优点和缺点。 CMS振动数据的缺点主要包括&#xff1a; 数据解读难度高&#xff1a;振动数据包含大量的专业信息&#xff0c;如振动幅度、频率、相位等&#xff0c;需要专业的知识和技能才能准确解读。受环境影响大&…

DETR详解

1. 动机 传统的目标检测任务需要大量的人工先验知识&#xff0c;例如预定义的先验anchor&#xff0c;NMS后处理策略等。这些人工先验知识引入了很多人为因素&#xff0c;且较难处理。如果能够端到端到直接生成目标检测结果&#xff0c;将会使问题变得很优雅。 2. 主要贡献 提…

2024最新算法:电鳗觅食优化算法(Electric eel foraging optimization,EEFO)求解23个基准函数(提供MATLAB代码)

一、电鳗觅食优化算法 电鳗觅食优化算法&#xff08;Electric eel foraging optimization,EEFO&#xff09;由Weiguo Zhao等人提出的一种元启发算法&#xff0c;EEFO从自然界中电鳗表现出的智能群体觅食行为中汲取灵感。该算法对四种关键的觅食行为进行数学建模&#xff1a;相…

ESP8266智能家居(5)——开发APP深入篇

1.代码解析 接下来重点介绍一下逻辑代码 这里面主要是设置mqtt服务器的IP地址和端口号&#xff0c;设置服务器的用户名和登录密码 绑定好订阅主题和发布主题&#xff08;和8266上的订阅、发布交叉就行&#xff09; 绑定界面&#xff0c;设置界面标题 绑定6个文本控件 将从mq…

【C语言】熟悉文件基础知识

欢迎关注个人主页&#xff1a;逸狼 创造不易&#xff0c;可以点点赞吗~ 如有错误&#xff0c;欢迎指出~ 文件 为了数据持久化保存&#xff0c;使用文件&#xff0c;否则数据存储在内存中&#xff0c;程序退出&#xff0c;内存回收&#xff0c;数据就会丢失。 程序设计中&…

在您的下一个项目中选择 Golang 和 Node.js 之间的抉择

作为一名软件开发者&#xff0c;我总是在寻找构建应用程序的最快、最高效的工具。在速度和处理复杂任务方面&#xff0c;我认为 Golang 和 Node.js 是顶尖技术。两者在性能方面都享有极高的声誉。但哪一个更快——Golang 还是 Node&#xff1f;我决定深入一些硬核基准测试&…

抽象类、模板方法模式

抽象类概述 在Java中abstract是抽象的意思&#xff0c;如果一个类中的某个方法的具体实现不能确定&#xff0c;就可以申明成abstract修饰的抽象方法&#xff08;不能写方法体了&#xff09;&#xff0c;这个类必须用abstract修饰&#xff0c;被称为抽象类。 抽象方法定义&…