排序算法——快速排序(队列和栈实现非递归)

一、背景 

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

二、实现

1、hoare版本


 

老规矩我们先写单趟:

以最左边元素为基准:右边先走,找到左边大于key的数,右边小于key的数:

int key = left;
int begin = left;
int end = right - 1;
while (a[end] >= a[key])//在右边找到第一个比key小的值
{
	end--;
}
while(a[begin] <= a[key])//在左边找到第一个比key大的值
{
	begin++;
}
swap(&a[begin], &a[end]);

相遇时说明第一趟排好,所以我们可以写出如下代码:

int PartSort1(int* a, int left, int right)
{
	int key = left;
	int begin = left;
	int end = right - 1;
	while (begin < end);
	{
		
		while (a[end] >= a[key])//在右边找到第一个比key小的值
		{
			end--;
		}
		while (a[begin] <= a[key])//在左边找到第一个比key大的值
		{
			begin++;
		}
		swap(&a[begin], &a[end]);
	}
	
}

剩下的排序类似树的左右子树遍历用递归实现:
递归结束条件为剩余长度<=1(也可以写成right<=left),注意在经行找值的时候,还要保持左小于右。

void QuickSort(int* a, int left, int right)
{
	if (right-left<=1)
	{
		return;
	}
	int key = left;
	int begin = left;
	int end = right;
	while (begin < end)
	{
		while (begin < end && a[end] >= a[key])//在右边找到第一个比key小的值
		{
			--end;
		}
		while (begin < end && a[begin] <= a[key])//在左边找到第一个比key大的值
		{
			++begin;
		}
		swap(&a[begin], &a[end]);
	}
	swap(&a[begin], &a[key]);
	key = begin;
	QuickSort(a, left, key-1);
	QuickSort(a, key+1, right);
}

2、挖矿法

选择一个基准值,仍然通过begin和end指针来操作

首先,我们仍然是从end开始向前找一个小于28的数,找到后直接放在begin的位置,注意这里不是交换,如下图:

箭头所指向的是我们选取的基准值,这时可以理解为它不在这个数列当中,而end对应的位置就出现了一个坑,这时,begin开始向后寻找,找到第一个大于基准值的数后放到end的位置,如下图

此时,begin的位置又会出现一个坑,这时再次让end向前移动继续找第一个小于基准值的数,放到begin的位置,再使begin向后移动找第一个大于基准值的数,重复此操作直到begin和end相遇,这时,把基准值放到该位置。这样,一趟快速排序结束,如下图

 代码实现:

void QuickSort(int* a, int left, int right)
{
	if (right - left <= 1)
	{
		return;
	}
	int key = left;
	int begin = left;
	int pit = left;//坑
	int end = right;
	while (begin < end)
	{
		while (begin < end && a[end] >= a[key])//在右边找到第一个比key小的值
		{
			end--;
		}
		a[pit] = a[end];//放入坑中
		pit = end;//坑更新
		while (begin < end && a[begin] <= a[key])//在左边找到第一个比key大的值
		{
			++begin;
		}
		a[pit] = a[begin];
		pit = begin;
	}
	a[pit] = a[key];//最后将key放入坑中
	QuickSort(a, left, begin-1);
	QuickSort(a, begin+1, right);
}

3、前后指针法

 

前后指针法:
第一步:选最右边的值做key(key是单趟排序后能排到最后该待的位置的数据)
第二步:cur开始找,cur遇到比key小或相等的,cur和prev交换,cur和prev一起向右走。cur遇到比key大的,cur向右走,prev不动。一直循环第二步(若cur走出了数组,结束)

还是先写单趟


int key = left;
int pre = left;
int cur = left + 1;
while (cur <= right - left + 1)
{
	if (a[cur] < a[key] && ++pre != cur)//pre加后如果与cur相等为自己与自己交换
	{
		swap(&a[cur], &a[pre]);//交换后要cur++,大于也++,所以直接出判断++
	}
	cur++;
}
swap(&a[key], &a[pre]);

当出现自己于自己交换时可以不用交换,因为交不交换都要cur++所以直接在if后面++ 

整体代码:

 

void QuickSort(int* a, int left, int right)
{
	/*if (right - left <= 1)
	{
		return;
	}*/
	 //小区间优化,不再递归分割排序,减少递归的次数
	if ((right - left + 1) < 10)
	{
		InsertSort(a + left, right - left + 1);//这一定要加left
	}
	else
	{
		int mid = findmid(a, left, right);
		swap(&a[left], &a[mid]);
		int key = left;
		int pre = left;
		int cur = left + 1;
		while (cur <= right - left + 1)
		{
			if (a[cur] < a[key] && ++pre != cur)//pre加后如果与cur相等为自己与自己交换
			{
				swap(&a[cur], &a[pre]);//交换后要cur++,大于也++,所以直接出判断++
			}
			cur++;
		}
		swap(&a[key], &a[pre]);
		int key = pre;
		QuickSort(a, left, key - 1);
		QuickSort(a, key+ 1, right);
	}
}

三、优化:

1、避免重复选择

 插入排序时间复杂度为O(nlogn)(每一层n个值,树的高度为logn)

但是对于我们刚刚实现的版本如果一个数列已经排好了,但是代码任然会一个一个比较;

所以我们接下来重点考虑其怎么优化?最佳的答案为三数取中原则:即给定三个数取不大不小的那个,这样会大大减少在循环中比较的次数。从而提高效率:
代码实现:

lfindmid(int* a, int left, int right)
{
	int midi = (left + right) / 2;
	// left midi right
	if (a[left] < a[midi])
	{
		if (a[midi] < a[right])
		{
			return midi;
		}
		else if (a[left] < a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
	else // a[left] > a[midi]
	{
		if (a[midi] > a[right])
		{
			return midi;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}
void QuickSort(int* a, int left, int right)
{
	if (right - left <= 1)
	{
		return;
	}
	int key = findmid(a, left, right);
	int begin = left;
	int pit = left;//坑
	int end = right;
	while (begin < end)
	{
		while (begin < end && a[end] >= a[key])//在右边找到第一个比key小的值
		{
			end--;
		}
		a[pit] = a[end];
		pit = end;
		while (begin < end && a[begin] <= a[key])//在左边找到第一个比key大的值
		{
			++begin;
		}
		a[pit] = a[begin];
		pit = begin;
	}
	a[pit] = a[key];
	QuickSort(a, left, begin-1);
	QuickSort(a, begin+1, right);
}

2、时间优化

对于一颗树来说最后几层几乎占了大部分的数据,例如:

对于排好五个数如果我们用递归实现则需要递归6次这是非常麻烦的,所以我们最好用插入排序去实现剩余部分的排序:

lfindmid(int* a, int left, int right)
{
	int midi = (left + right) / 2;
	// left midi right
	if (a[left] < a[midi])
	{
		if (a[midi] < a[right])
		{
			return midi;
		}
		else if (a[left] < a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
	else // a[left] > a[midi]
	{
		if (a[midi] > a[right])
		{
			return midi;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}
void QuickSort(int* a, int left, int right)
{
	if (right - left <= 1)
	{
		return;
	}
	// 小区间优化,不再递归分割排序,减少递归的次数
	if ((right - left + 1) < 10)
	{
		InsertSort(a + left, right - left + 1);//这一定要加left
	}
	int key = findmid(a, left, right);
	int begin = left;
	int pit = left;//坑
	int end = right;
	while (begin < end)
	{
		while (begin < end && a[end] >= a[key])//在右边找到第一个比key小的值
		{
			end--;
		}
		a[pit] = a[end];
		pit = end;
		while (begin < end && a[begin] <= a[key])//在左边找到第一个比key大的值
		{
			++begin;
		}
		a[pit] = a[begin];
		pit = begin;
	}
	a[pit] = a[key];
	QuickSort(a, left, begin-1);
	QuickSort(a, begin+1, right);
}

四、hoare版的相关证明:   

hoare版中比较让人难以理解的是为什么最后相遇时值比key小。(其次hoare规定如果让左边做key,那么右边一点要先走),相关证明如下:



 

五、非递归两种实现方法

将递归改为非递归一共有两种办法:
1、是通过栈来模拟实现递归,2、是直接通过循环来实现

而快速排序最好的办法是用栈来模拟实现:

众所周知,栈的特性是先进后出,我们拿数组arr=[5,2,4,7,9,1,3,6]来举栗子。
第一步:我们先把区间的右边界值7进行压栈,然后把区间的左边界值0进行压栈,那我们取出时就可以先取到左边界值,后取到后边界值

第二步:我们获取栈顶元素,先取到0给left,后取到7给right,进行单趟排序


第三步:第一趟排完后,区间被分为左子区间和右子区间。为了先处理左边,所以我们先将右子区间压栈,分别压入7和5,然后压入左子区间,3和0


第四步:取出0和3进行单趟排序


第五步:此时左子区间又被划分为左右两个子区间,但是右子区间只有4一个值,不再压栈,所以只入左子区间,将1和0压栈

第六步:取出0和1进行单趟排序


至此,左子区间全部被排完,这时候才可以出5和7排右子区间,这个流程其实和递归是一模一样的,顺序也没变,但解决了递归的致命缺陷——栈溢出。后面的流程就不一一展现了

 

void QuickSortNonR(int* a, int left, int right)
{
	Stack st;
	StackInit(&st);
	StackPush(&st, right);//先入右,再入左
	StackPush(&st, left);
	while (!StackEmpty(&st))//栈为空说明区间排完了
	{
		int begin = StackTop(&st);// 取的时候为先左后右
		StackPop(&st);
		int end = StackTop(&st);
		StackPop(&st);
		int key = PartSort1(a, begin, end);
		if (key + 1 < end)//如果右存在先压右
		{
			StackPush(&st, end);
			StackPush(&st, key + 1);
		}
		if (key - 1 > begin)//如果左存在先压左
		{
			StackPush(&st, key - 1);
			StackPush(&st, begin);
		}
	}
	StackDestroy(&st);
}

那么我们是否可以通过队列来实现呢?当然是可以的只不过将类似前序遍历,改成了层序遍历。

 

void QuickSortNonR2(int* a, int left, int right)
{
	Queue qe;
	QueueInit(&qe);
	QueuePush(&qe, left);//先进先出先加左边
	QueuePush(&qe, right);
	while (!QueueEmpty(&qe))
	{
		int begin = QueueFront(&qe);
		QueuePop(&qe);
		int end = QueueFront(&qe);
		QueuePop(&qe);
		int key = PartSort1(a, begin, end);
		if (key - 1 > begin)//如果左存在先压左
		{
			QueuePush(&qe, begin);
			QueuePush(&qe, key - 1);
		}
		if (key + 1 < end)//如果右存在先压右
		{
			QueuePush(&qe, key + 1);
			QueuePush(&qe, end);
		}
	}
	QueueDestroy(&qe);

}

 

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

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

相关文章

【php实战项目训练】——thinkPhP的登录与退出功能的实现,让登录退出畅通无阻

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

对新手友好的最简单方便的本地项目关联git远程仓库教程

对新手友好的最简单方便的本地项目关联git远程仓库教程 前置条件1.本地项目2.gitee上创建同名项目 关联操作1.在本地进行clone远程仓库操作2.把本地项目下的目录和文件都复制到这个克隆自git的项目文件夹里面3.查看文件状态和提交文件 在我们创建项目时&#xff0c;一般都是在本…

Java IO流的基本概念和使用,包括文件读写、序列化等

Java 输入输出&#xff08;IO&#xff09;系统提供了一套丰富的类和接口&#xff0c;用于处理文件读写、网络通信、数据序列化等各种数据操作。 IO 操作在任何编程语言中都扮演着重要角色&#xff0c;而 Java 的 IO 系统以其强大的灵活性和扩展性&#xff0c;成为开发者进行数…

【全开源】Fastflow工作流系统(FastAdmin+ThinkPHP)

&#x1f680;Fastflow工作流系统&#xff1a;高效协作&#xff0c;流程无忧​ 一款基于FastAdminThinkPHP开发的可视化工作流程审批插件&#xff0c;帮助用户基于企业业务模式和管理模式自行定义所需的各种流程应用&#xff0c;快速构建企业自身的流程管控体系&#xff0c;快…

短剧cps系统搭建开发,热门短剧推广分销系统。短剧分销是怎么操作的?

目录 前言&#xff1a; 二、短剧是怎么推广分销的&#xff1f; 二、 短剧分销系统有什么功能&#xff1f; 三、怎么搭建&#xff1f; 总结&#xff1a; 前言&#xff1a; 短剧分销项目目前的现状是多元化且充满活力的。随着短剧市场的快速发展和观众接受度的提高&#xff0…

浏览器打不开网页是什么原因?这里有详细解答!

在日常使用电脑的过程中&#xff0c;我们经常需要通过浏览器访问各种网站。然而&#xff0c;有时会遇到浏览器无法打开网页的情况&#xff0c;这可能导致工作中断或者无法获取所需的信息。那么浏览器打不开网页是什么原因呢&#xff1f;其实浏览器无法打开网页的原因可能有很多…

stm32-DMA转运数据

在配置前要记得先定义一下DMA转运的源端数组和目标数组两个数组哦。 接下来我们就开始准备配置吧 配置 初始化 1.RCC开启时钟&#xff08;开启DMA的时钟&#xff09; void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState) 作用&#xff1a;开启时…

JUC 笔记 8

1. Semaphore 信号量 基本使用 [ˈsɛməˌfɔr] 信号量&#xff0c;用来限制能同时访问共享资源的线程上限。 public static void main(String[] args) {// 1. 创建 semaphore 对象Semaphore semaphore new Semaphore(3);// 2. 10个线程同时运行for (int i 0; i < 10; …

一款史上最强的智能优化算法软件,MATLAB APP Designer开发

很久没有整理干货了&#xff0c;原因是最近一直在精心打磨一款智能优化算法APP&#xff0c;前前后后改了很多次&#xff0c;今天终于完工了&#xff01;接下来跟我一起来看看这款软件吧&#xff01; 目录 引言 01 单个算法测试介绍 02多种算法对比介绍 03软件安装及一些限…

深入解析ETL与ELT架构:数据集成技术的演进与发展

摘要&#xff1a;随着大数据时代的到来&#xff0c;数据集成成为企业信息化建设的重要环节。本文将深入探讨ETL与ELT两种架构&#xff0c;分析它们在数据处理、性能、可扩展性等方面的差异&#xff0c;为企业数据集成提供技术指导。 一、引言 在大数据时代&#xff0c;企业需要…

slf4j等多个jar包冲突绑定的排查方法使用IDEA的maven help解决

1.安装 2.使用maven help解决&#xff0c;找到对应包存在的冲突 使用exclude直接解决即可

HTML跨年烟花

目录 写在前面 关于小编 HTML简介 程序设计 系列文章 写在后面 写在前面 学会了这个html烟花秀&#xff0c;跨年就不缺文案喽~ 关于小编 平易近人&#xff0c;慈眉善目&#xff0c;爱交朋友&#xff0c;舍己为人&#xff0c;和蔼可亲&#xff0c;能说会道&#xff0c;…

解决 Mac Django 连接Mysql 出现 image not found 问题

最近在使用 Django 框架&#xff0c;因为升级到4.2版本了&#xff0c;对应的本机 Mysql 5.7 就不适用了&#xff0c;于是升级到了 Mysql 8.0&#xff0c;写好代码之后出现如下错误&#xff1a; 仔细分析一下错误的描述&#xff1a; ImportError: dlopen(/Library/Frameworks/P…

【安装笔记-20240529-Windows-Electerm 终端工具】

安装笔记-系列文章目录 安装笔记-20240529-Windows-Electerm 终端工具 文章目录 安装笔记-系列文章目录安装笔记-20240529-Windows-Electerm 终端工具 前言一、软件介绍名称&#xff1a;electerm主页官方介绍功能特性 二、安装步骤测试版本&#xff1a;electerm-1.39.35-win-x…

Pixi绘制地图和小车

之前已经用Pixi绘制出了各种图形以及通过图片绘制精灵&#xff0c;这节用pixi绘制网格地图&#xff0c;并通过图片制作一个Sprite&#xff0c;让这个Sprite在网格地图上运动。首先需要在页面中添加一个div用来后期展示canvas的画布&#xff0c;并将此div实例化为PIXI的Applicat…

Doris 少数SQL在Datagrip无法执行,而在DorisUI或程序调用可以执行的问题

问题&#xff1a;Doris 少数SQL在Datagrip无法执行&#xff0c;而在DorisUI或程序调用可以执行 解决&#xff1a;Datagrip 执行SQL切分异常&#xff0c;设置默认执行语句方式&#xff0c;将分句改为整句执行 但是 支持多SQL批量分开执行更好用

茶树三维基因组-文献精读19

The high-resolution three-dimensional (3D) chromatin map of the tea plant (Camellia sinensis) 茶树&#xff08;Camellia sinensis&#xff09;的高分辨率三维染色质图&#xff0c;还记得茶属的转录组分析嘛~ 比较转录组分析揭示了116种山茶属(Camellia)植物的深层系统…

IDEA下项目发送到Gitee

一、首先在Gitee创建一个仓库&#xff08;什么都不选&#xff0c;这是最简单的方式&#xff0c;否则需要 pull push等一些操作&#xff0c;我嫌麻烦&#xff09; 二、按图点击&#xff08;创建存储区&#xff0c;选择你要上传的项目&#xff09; 三、按图点击后正常文件名会变绿…

ChatTTS 如何安装可视化操作

可视化一键安装下载地址&#xff1a; 百度网盘 Download from GitHub 从 GitHub 下载代码。 git clone https://github.com/2noise/ChatTTS 下载地址 Install Dependencies 在开始之前&#xff0c;请确保已安装必要的软件包。如果您尚未安装它们&#xff0c;可以使用 pip …

51种企业应用架构模式详解

01 什么是企业应用 我的职业生涯专注于企业应用&#xff0c;因此&#xff0c;这里所谈及的模式也都是关于企业应用的。&#xff08;企业应用还有一些其他的说法&#xff0c;如“信息系统”或更早期的“数据处理”。&#xff09;那么&#xff0c;这里的“企业应用”具体指的是什…