快速排序【hoare版】

news2024/11/22 17:40:04

目录

介绍

算法思路

函数实现

函数声明

确定基准值

 创建新函数

创建循环找数据(right,left)

交换左右数据

交换条件设置

外部循坏条件设置

初步总结代码

循环条件完善

内层循环的完善

外层循环的完善

相遇值大于keyi

相遇值等于keyi 

 相遇值小于keyi

确定基准值代码展示

递归左/右子序列

 ​编辑

完整代码

总结

介绍

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

算法思路

1)创建左右指针,确定基准值(keyi)

2)从右向左找出比基准值小的数据,从左向右找出比基准值大的数据,左右指针数据交换,进入下次循环

//递归遍历左子序列
QuickSort(arr, left, keyi - 1);
//递归遍历右子序列
QuickSort(arr, keyi + 1, right);

函数实现

函数声明

void QuickSort(int* arr, int left, int right)

对一个数组进行快速排序时,需要利用基准值取每个数组的左子序列和右子序列,然后进行递归、排序,最后返回。函数声明部分必须包含数组地址,左子序列和右子序列。

确定基准值

如何找基准值?

right:从右到左找到比基准值小的数据

left:  从左到右找到比基准值大的数据

 创建新函数

新建一个函数 _QuickSort 来查找数组的基准值,函数参数和快速排序函数参数相同。

//用来查找基准值
int _QuickSort(int* arr, int left, int right)

创建循环找数据(right,left)

创建一个数组为 int arr[] = {6,1,2,7,9,3} ,假设当前第一个有效数据为基准值keyi,left 为 keyi 后的一个有效数据,right 为最后一个有效数据。

创建二个循环,一个使 right 指向比基准值小的数据,一个使 left 指向比基准值大的数据。在设置循环条件时,需要将 arr[right] > arr[keyi] ,arr[left] < arr[keyi] 来保证循环能够正常运行。

while(arr[right] > arr[keyi])//等于该如何操作???
{
    right--;//调整right位置,直到找到比arr[keyi]小的数据为止
}

//离开循环说明right已经找到正确的位置

while(arr[left] < arr[keyi])//等于该如何操作???
{
    left++;//调整left位置,直到找到比arr[keyi]大的数据为止
}

//离开循环说明left已经找到正确的位置

如果当 arr[right] / arr[left] == arr[keyi] 时,该如何操作??? 

交换左右数据

当循环结束后,left 和 right 指针都找到了需要进行交换的数据,然后进行交换操作

Swap(&arr[left], &arr[right]);
//交换函数
void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

交换条件设置

如图所示,在 left 和 right 完成第一次交换后,满足 left < right 的条件,进入到第二次循环,此时 right 找到了比 keyi 小的数据,left 找到了比 keyi 大的数据,这种条件下 left 和 right 是否还需要交换数据???

left 的作用是将比 keyi 大的数据移到数组右边,right 的作用是将比 keyi 小的数据移到数组左边,图示案例明显违背了以上作用,所以在交换数据时,需要判断 left 和 right 的位置。

if (left < right)
{
	Swap(&arr[left], &arr[right]);
}

外部循坏条件设置

创建循环找到正确数据,并且进行交换后,只是完成了一组数据的交换,而后需要对更多的数据进行交换。

循环条件如何设置?

当 left > right 时,即right⾛到left的左侧,而 left 扫描过的数据均不大于keyi,因此 right 此时指向的数据一定不大于keyi ,所以就没有必要继续循环操作。

所以循环条件应该设置为:

while(left < right)//是否可以等于???

当小的数据都普遍位于数组左边,大的数据都普遍位于数组右边时,交换 arr[right] 和 arr[keyi] ,此时的 right 指向的就是基准值

Swap(&arr[keyi], &arr[right]);

return right;//基准值下标位置

初步总结代码

将以上代码综合,得到以下函数:

int _QuickSort(int* arr, int left, int right)
{
	int keyi = left;
	++left;

	while (left < right)//是否可以等于??
	{
		//right从右往左找到比基准值小的数据
		while (arr[right] > arr[keyi])
		{
			right--;
		}
		//left从左往右找到比基准值大的数据
		while (arr[left] < arr[keyi])
		{
			left++;
		}
		//此时left和right都找到了相对应的数据,进行交换操作
		if (left < right)
		{
			Swap(&arr[left], &arr[right]);
		}
	}
	Swap(&arr[keyi], &arr[right]);

	return right;//基准值下标位置
}

如代码所示,循环条件仍旧不够完善,没有考虑到等于的情况,现在我们开始完善代码。

循环条件完善

内层循环的完善

设置数组 int arr[] = {6,6,6,6,6,6} ,当数组数据全都相同时,我们假设内层循环条件 arr[right] >= arr[keyi],right 会一直往前遍历寻找比 keyi 小的数据,但是一直找不到,就会越界

所以我们在进行内层循环的设置时需要加上一个条件,left <= right

同理,当数组数据全都相同时,我们假设内层循环条件 arr[left] <= arr[keyi],left 会一直往后遍历寻找比 keyi 大的数据,但是一直找不到,就会越界

所以在用 left 找比 keyi 大的数据时,循环也需要加上,left <= right。 

//目前假设right和left所指向的数值可以等于keyi
while (left <= right && arr[right] >= arr[keyi])
{
	right--;
}

while (left <= right && arr[left] <= arr[keyi])
{
	left++;
}

 以该层代码来进行快速排序,right 在内层循环后来到 keyi 的位置,left 因为大于 right 无法进入循环,交换也无法实现,跳出循环后交换 right 和 keyi 的数据,发现基准值仍在最左端。

按照如此方法递归查找,发现该数组不存在左子序列,并且时间复杂度也比较差。快速排序用二分的方法去排序,这个事例表明内存循环时 arr[right] / arr[left] 不能等于 arr[keyi]。

所以在设置内层循环时,循环条件应该为:

while (left <= right && arr[right] > arr[keyi])
while (left <= right && arr[left] < arr[keyi])

 同时那我们该如何应对当 arr[right] / arr[left] == arr[keyi] 的情况呢?

当遭遇这种情况时,我们不仅无法进入 right 调整的循环,也无法进入 left 调整的循环,但是却符合 left < right 可以进入到交换的循环当中,因为缺少了对指针的调整,所以我们需要在交换完 left 和 right 的数据后主动++和--,以此来防止以外的发生。

if (left < right)//是否可以等于??
{
	Swap(&arr[left++], &arr[right--]);//保证数据相等时left和right可以调整指向
}
外层循环的完善
相遇值大于keyi

在此基础上,我们再对该循环进行检查。left 和 right 在完成两次交换后到达了相遇点,如果按照原本外层循环的代码来看,当 left == right 时,应当跳出循环,最后进行 keyi  与 right 的交换,但是相遇值却大于 keyi ,将 keyi 与其交换则会发生错误,使得大于基准值的数字在基准值的左边。 

 所以在设置外层循环的条件时,我们可以将 left == right ,使其进入到循环当中,right 满足条件--,来到下标为2的位置,此时 right <left ,无法进入内层循环与交换,则与 keyi 发生交换,以此确定了基准值的位置。

while (left <= right)

相遇值等于keyi 

同理,如果当相遇值等于 keyi 时,我们可以进入到外层循环,却不能进入到内存循环,那我们只能从交换条件上入手。

我们可以利用交换函数中的调整参数来对 left 和 right 进行调整,前提是得满足 if 条件,当前 left == right ,所以我们需要将条件修改为 left <= right。 

if (left <= right)
{
	Swap(&arr[left++], &arr[right--]);
}
 相遇值小于keyi

相遇值小于 keyi 时,原理和相遇值大于 keyi 一样,在外层循环条件为 while(left <= right) 时,进入外层循环,right 找到最小值为相遇点数值,left 往后++寻找,不满足left <= right的条件后跳出内层循环和外层循环,将 right 与 keyi 交换,找到正确的基准值。 

确定基准值代码展示

int _QuickSort(int* arr, int left, int right)
{
	int keyi = left;
	++left;

	while (left <= right)
	{
		//right从右往左找到比基准值小的数据
		//如果数组数据全部相同,则需要变更循环条件
		while (left <= right && arr[right] > arr[keyi])//加上条件使得基准值位置更加偏向于数组中央
		{
			right--;
		}
		//left从左往右找到比基准值大的数据
		while (left <= right && arr[left] < arr[keyi])
		{
			left++;
		}
		//此时left和right都找到了相对应的数据,进行交换操作
		if (left <= right)
		{
			Swap(&arr[right--], &arr[left++]);
		}
	}
	Swap(&arr[keyi], &arr[right]);

	return right;//基准值下标位置
}

递归左/右子序列

设置一个数组,int arr[] = {6,1,2,7,9,3},进行快速排序演示。 

第一次数据交换,right上3小于6,left上7大于6

 

right上3小于6,left在++时超过right,循环停止,不满足交换条件,跳出循环,right与keyi交换数据,6成为基准值。

递归左子序列,right上2小于3,left在++时超过right,循环停止,不满足交换条件,跳出循环,right与keyi交换数据,3成为基准值。

递归左子序列,right上1小于2,left在++时超过right,循环停止,不满足交换条件,跳出循环,right与keyi交换数据,2成为基准值。

递归左子序列,right上1等于1,跳出循环,不满足交换条件,right与keyi交换数据,1成为基准值。

当我们再去递归左子序列时发现,已经没有左子序列可以进行递归了,右子序列也无法递归,原因是当1为基准值时, 不满足以下条件:

//递归遍历左子序列
QuickSort(arr, left, keyi - 1);
//递归遍历右子序列
QuickSort(arr, keyi + 1, right);

所以当 left > 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);
}

 递归右子序列,right上7小于9,left在++时超过right,循环停止,不满足交换条件,right与keyi交换数据,9成为基准值。

再进行递归时发现,keyi+1大于right,keyi-1小于left,不符合规律,所以递归结束。

 返回后可以发现,每次递归所得出的基准值都是每一个数字在其有序数组中应当存在的位置。

完整代码

int _QuickSort(int* arr, int left, int right)
{
	int keyi = left;
	++left;

	while (left <= right)
	{
		//right从右往左找到比基准值小的数据
		//如果数组数据全部相同,则需要变更循环条件
		while (left <= right && arr[right] > arr[keyi])//加上条件使得基准值位置更加偏向于数组中央
		{
			right--;
		}
		//left从左往右找到比基准值大的数据
		while (left <= right && arr[left] < arr[keyi])
		{
			left++;
		}
		//此时left和right都找到了相对应的数据,进行交换操作
		if (left <= right)
		{
			Swap(&arr[right--], &arr[left++]);
		}
	}
	Swap(&arr[keyi], &arr[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);
}

总结

快速排序【hoare版】运用到了递归的方法进行实现,代码数量不多,但是递归繁琐,所需要考虑的情况也分多种。对于循环条件设置和if条件设置上表现得极其严苛。

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

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

相关文章

oracle导入线上数据的全步骤

多租户架构允许oracle数据库成为一个多租户的容器数据库&#xff0c;也就是CDB&#xff0c;container database&#xff0c;与之相对应的&#xff0c;则是插入到这个容器里面的可插拔式数据库&#xff0c;pluggable database 一个CDB可以包含0&#xff0c;1或者多个用户创建的…

嵌入式硬件实战基础篇(三)-四层板PCB设计-步进电机驱动(TMC2208/TMC2209)

引言&#xff1a;我们在嵌入式硬件杂谈&#xff08;三&#xff09;中有提到阻抗匹配的问题&#xff0c;也引入了高速PCB设计的思想&#xff0c;并且此篇实战基础篇主要是基础的四层板的绘制设计&#xff0c;后续实战会对高速板展开&#xff0c;本篇主要是提升读者的设计PCB板的…

uniapp 选择 省市区 省市 以及 回显

从gitee仓库可以拿到demo 以及 json省市区 文件 // 这是组件部分 <template><uni-popup ref"popup" type"bottom"><view class"popup"><view class"picker-btn"><view class"left" click"…

C语言练习.while语句

题目&#xff1a; 1.用C语言编程&#xff0c;运用while语句&#xff0c;写一段简短的代码。 分析&#xff1a; 1.运用while语句要注意&#xff1a;while语句&#xff0c;要设置好退出条件&#xff0c;不然就会造成无限循环的结果&#xff0c;导致程序停不下来。 2.编写代码…

Linux编辑器 - vim

目录 一、vim 的基本概念 1. 正常/普通/命令模式(Normal mode) 2. 插入模式(Insert mode) 3. 末行模式(last line mode) 二、vim 的基本操作 三、vim 正常模式命令集 1. 插入模式 2. 移动光标 3. 删除文字 4. 复制 5. 替换 6. 撤销上一次操作 7. 更改 8. 调至指定…

24.11.20 sevlet2

1.servlet是否线程安全 (线程特性) 线程安全的指标 //1.是否有共享数据 //2.多线程对共享数据做写操作 servlet中 不要创建成员变量 servlet是单实例的 所以成员变量(不加static) 就会在多线程间共享 如果service()方法中 对成员变量有写操作 则线程不安全 servlet中非特殊情…

【编译器】Dev C++建立C语言工程

【编译器】Dev C建立C语言工程 文章目录 [TOC](文章目录) 前言一、创建工程二、添加.c.h三、主函数处理四、在桌面中打开exe文件五、参考资料总结 前言 在使用了很多编译器之后&#xff0c; 要么是太大了&#xff0c; 要么是太新了&#xff0c; 要么是在线编译器&#xff0c;用…

【ArcGIS微课1000例】0132:从多个GIS视角认识与攀登珠穆朗玛峰

文章目录 1. Map Viewer中打开2. 场景查看器中打开3. ArcGIS中打开4. QGIS中打开5. Globalmapper中打开6. ArcGIS Earth中打开官网地址:https://www.arcgis.com/home/item.html?id=504a23373ab84536b7760c0add1e0c1c 1. Map Viewer中打开 以下展示不同底图样式的珠穆朗玛峰壮…

vscode uniapp 微信小程序 view、text、image标签红色波浪线

没修改前的红色波浪线样式 看好多人没解决方法&#xff0c;我的这种反正成功了&#xff0c;解决方法如下&#xff1a;首先降级Vue - Official 为 v2.0.12 选择版本 配置tsconfig.json "vueCompilerOptions": {// experimentalRuntimeMode 已废弃&#xff0c;现调整为…

SCTransNet验证测试

SCTransNet 是PRCV 2024、ICPR 2024 Track 1、ICPR 2024 Track 2 三项比赛冠军方案的 Baseline, 同时也是多个优胜算法的Baselines. Bilibili 视频分享 【工作分享】SCTransNet:面向红外弱小目标检测的空间 - 通道交叉 Transformer_哔哩哔哩_bilibili 极市平台 推文分享 …

电路模型和电路定理(二)

电路元件 是电路中最基本的组成单元。 电阻元件&#xff1a;表示消耗电能的元件 电感元件&#xff1a;表示产生磁场&#xff0c;储存磁场能的元件 电容元件&#xff1a;表示产生电场&#xff0c;储存电场能量的元件 电压源和电流源&#xff1a;表示将其他形式的能量转变成…

2023AE软件、Adobe After Effects安装步骤分享教程

2023AE软件是一款由Adobe公司开发的视频编辑软件&#xff0c;也被称为Adobe After Effects。它在广告、电影、电视和网络视频等领域广泛应用&#xff0c;用于制作动态图形、特效、合成和其他视觉效果。该软件支持多种视频和音频文件格式&#xff0c;具有丰富的插件和预设&#…

AI Large Language Model

AI 的 Large Language model LLM , 大语言模型&#xff1a; 是AI的模型&#xff0c;专门设计用来处理自然语言相关任务。它们通过深度学习和庞大的训练数据集&#xff0c;在理解和生成自然语言文本方面表现出色。常见的 LLM 包括 OpenAI 的 GPT 系列、Google 的 PaLM 和 Meta…

【大数据学习 | Spark】关于distinct算子

只有shuffle类的算子能够修改分区数量&#xff0c;这些算子不仅仅存在自己的功能&#xff0c;比如分组算子groupBy&#xff0c;它的功能是分组但是却可以修改分区。 而这里我们要讲的distinct算子也是一个shuffle类的算子。即可以修改分区。 scala> val arr Array(1,1,2,…

SrpingBoot基础

SpringBoot基本框架中重要常用的包讲解: .idea包和.mvn包框架生成不经常用 src包下主要存放前后端代码: main包下的java包存放的是后端java代码主要负责数据处理 resource包下存放的是配置资源和前端页面,其中static中存放的是前端html网页一般存放静 态资源,templates包…

Spring6 MyBatis

1. 依赖 <dependencies><!-- spring核心--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.1.14</version></dependency><!-- Resource注解的依赖…

JavaEE专栏介绍

专栏导读 在当今快速发展的信息技术时代&#xff0c;JavaEE作为企业级应用开发的核心技术之一&#xff0c;扮演着至关重要的角色。本“JavaEE”专栏旨在为对JavaEE感兴趣的开发者提供一个全面的学习平台&#xff0c;从基础概念到高级应用&#xff0c;帮助读者深入理解JavaEE框…

互联网数字化商品管理浪潮思考:从信息化到精准运营

目录 一、商品数字化转型面临的现状分析 &#xff08;一&#xff09;运营方向分析 &#xff08;二&#xff09;商品归类分析 二、商品数字化管理建设分析 三、基础建设——商品信息数字化 &#xff08;一&#xff09;商品信息质量数字化的目的 &#xff08;二&#xff0…

一文读懂Redis6的--bigkeys选项源码以及redis-bigkey-online项目介绍

一文读懂Redis6的--bigkeys选项源码以及redis-bigkey-online项目介绍 本文分为两个部分&#xff0c;第一是详细讲解Redis6的--bigkeys选项相关源码是怎样实现的&#xff0c;第二部分为自己对--bigkeys源码的优化项目redis-bigkey-online的介绍。redis-bigkey-online是自己开发的…

随机场模型与命名实体识别:深入理解CRF及其应用

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…