快速排序【hoare版本】【挖坑法】【双指针法】(数据结构)

news2024/12/24 19:03:08

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

一、hoare版本

       该算法的大体框架为:假设取数组的头为key同时保存索引变量begin的值在此处,取key的另一端为索引变量end,end不断向左移动,直至处于一个小于key的值的位置,此时再让索引变量begin不断向右移动,直至处于一个大于key的值的位置,交换两个值。由此不断循环往复,最终肯定会在一个大于key值的位置处end和begin相遇,最终交换该值与key所在的位置,即可得到一个左边比key小,右边比key大的一个数组。

       由于数组是以key为基准,左右两边分别小于和大于key,所以该数组又可分为[left,key-1] , [key+1,right],然后递归调用上面的算法,左右子序列重复该过程,直至所有元素都排列在相应位置上即可。

下图为单趟快速排序的过程

程序源代码

//单趟排序
int PartSort1(int* a, int begin, int end)
{
	int key = a[begin];//选取左边做key
	int keyindex = begin;
	while (begin < end)
	{
		while (begin < end && a[end] >= key)//此处必须要在此判断begin<end,防止end越界
		{
			end--;
		}
		while (begin < end && a[begin] <= key)
		{
			begin++;
		}
		Swap(&a[end], &a[begin]);
	}
	//此时begin == end
	Swap(&a[end], &a[keyindex]);//此处不能跟key交换,因为key是局部变量
	return begin;
}
void QuickSort(int* a, int left , int right)
{
	assert(a);
	if (left >= right)
	{
		return;
	}
	int div = PartSort1(a, left, right);
	//[left,div - 1]  div  [div + 1 , right]
	QuickSort(a, left, div - 1);
	QuickSort(a, div + 1, right);
}

注意事项

1.若选取右边的值做key,那么一定要让左边的begin先走,这样能保证他们性欲的位置一定是一个比key大的位置...(选取左边值做key同理)

2.注意在移动begin和end的时候每次都需要判断begin<end,防止begin和end相遇之后错过,无法进行正确的排序

优化

       经过分析我们发现:快速排序的最坏情况是每次选择的基准元素都是当前子数组的最大或最小值,导致每次划分只能减少一个元素的规模。在这种情况下,算法的时间复杂度接近于O(N*N),快速排序也就没有什么优势了,因此我们要做出优化。 

       为避免选取到最大值或者最小值,出现了三数取中法。即选取三个数的中间值作为基准,就可以很好地避免这种情况。改进后的代码变为:

//三数取中法
int GetMidIndex(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] < a[mid])
	{
		if (a[right] < a[left])
		{
			return left;
		}
		else if (a[right] > a[mid])
		{
			return mid;
		}
		else
		{
			return right;
		}
	}
	else//a[left] > a[mid]
	{
		if (a[left] < a[right])
		{
			return left;
		}
		else if (a[mid] > a[right])
		{
			return mid;
		}
		else
		{
			return right;
		}
	}

}
//单趟排序
int PartSort1(int* a, int begin, int end)
{
	int midindex = GetMidIndex(a, begin, end);
	Swap(&a[midindex], &a[begin]);

	int key = a[begin];//选取左边做key
	int keyindex = begin;
	while (begin < end)
	{
		while (begin < end && a[end] >= key)
		{
			end--;
		}
		while (begin < end && a[begin] <= key)
		{
			begin++;
		}
		Swap(&a[end], &a[begin]);
	}
	//此时begin == end
	Swap(&a[end], &a[keyindex]);//此处不能跟key交换,因为key是局部变量
	return begin;
}
void QuickSort(int* a, int left , int right)
{
	assert(a);
	if (left >= right)
	{
		return;
	}
	int div = PartSort1(a, left, right);
	//[left,div - 1]  div  [div + 1 , right]
	QuickSort(a, left, div - 1);
	QuickSort(a, div + 1, right);
}

 二、挖坑法

       挖坑法是最主流的快速排序的方法,因为其易于理解。挖坑法,顾名思义,就是要把数据挖出来,假设以最左端位置为坑,把它的值保存在一个变量key内,定义索引变量begin和end,使end不断向左边移动,直至a[end]的值小于key,把它放在坑内,然后begin再向右移动,直至a[begin]的值大于key,把它放在a[end]的坑内。由此循环往复,我们可以得到一个与上面排序相同的结果。

程序源代码

/挖坑法
int PartSort2(int* a, int begin, int end)
{
	int midindex = GetMidIndex(a, begin, end);
	Swap(&a[midindex], &a[begin]);
	
	int key = a[begin];//坑
	while (begin < end)
	{
		while (begin < end && a[end] >= key)
		{
			end--;
		}
		a[begin] = a[end];
		while (begin < end && a[begin] <= key)
		{
			begin++;
		}
		a[end] = a[begin];
	}
	//begin == end
	a[begin] = key;
	return begin;
}
void QuickSort(int* a, int left , int right)
{
	assert(a);
	if (left >= right)
	{
		return;
	}
	int div = PartSort3(a, left, right);
	//[left,div - 1]  div  [div + 1 , right]
	QuickSort(a, left, div - 1);
	QuickSort(a, div + 1, right);
}

注意事项

在移动begin和end的时候,同样要每次判断begin是否小于end,防止begin和end错过

三、双指针法

双指针法相对来说较难理解一点。我们要取最后一个元素为key,定义两个变量cur,prev,其中cur是数组的首元素的索引(begin),prev位于数组首元素的前一个位置,即begin - 1。算法的思想是:cur向右不断移动,直至找到a[cur] < key,++prev,然后让prev所在位置与cur所在位置的值进行交换。依次重复这个过程,我们也可以得到跟上面两种方法相同的结果。

程序源代码

//双指针法
int PartSort3(int* a, int begin, int end)
{
	int midindex = GetMidIndex(a, begin, end);
	Swap(&a[midindex], &a[begin]);

	int key = a[end];
	int cur = begin;
	int prev = begin - 1;
	while (cur <= end)
	{
		while (cur<= end && a[cur] >= key)//注意!!!
		{
			cur++;
		}
		if (cur <= end)
		{
			prev++;
			Swap(&a[cur], &a[prev]);
			cur++;
		}
	}
	Swap(&a[++prev], &a[end]);
	return prev;
}
void QuickSort(int* a, int left , int right)
{
	assert(a);
	if (left >= right)
	{
		return;
	}
	int div = PartSort3(a, left, right);
	//[left,div - 1]  div  [div + 1 , right]
	QuickSort(a, left, div - 1);
	QuickSort(a, div + 1, right);
}

代码也可以换一种形式来呈现:

//双指针法
int PartSort3(int* a, int begin, int end)
{
	
	int prev = begin - 1;
	int cur = begin;
	int keyindex = end;
	while (cur <= end)
	{
		if (a[cur] < a[keyindex] && a[cur] != a[keyindex])
		{
			Swap(&a[cur], &a[++prev]);
		}
		cur++;
	}
	Swap(&a[keyindex], &a[++prev]);

	return prev;
}
void QuickSort(int* a, int left , int right)
{
	assert(a);
	if (left >= right)
	{
		return;
	}
	int div = PartSort3(a, left, right);
	//[left,div - 1]  div  [div + 1 , right]
	QuickSort(a, left, div - 1);
	QuickSort(a, div + 1, right);
}

注意事项

在初始化prev的值时,不能将其初始化为-1,要初始化为begin-1。初始化为-1,会导致子数列在进行递归时出现问题。 


今天的分享到这就结束了,欢迎持续关注,有问题可以私信交流~

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

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

相关文章

文件上传进阶绕过(二)4个技巧和靶场实战

★★免责声明★★ 文章中涉及的程序(方法)可能带有攻击性&#xff0c;仅供安全研究与学习之用&#xff0c;读者将信息做其他用途&#xff0c;由Ta承担全部法律及连带责任&#xff0c;文章作者不承担任何法律及连带责任。 0、环境准备 请移步《文件上传靶场实战&#xff1a;upl…

小迪安全第二天

文章目录 一、Web应用&#xff0c;架构搭建二、web应用环境架构类三、web应用安全漏洞分类总结 一、Web应用&#xff0c;架构搭建 #网站搭建前置知识 域名&#xff0c;子域名&#xff0c;dns,http/https,证书等 二、web应用环境架构类 理解不同web应用组成角色功能架构 开发…

基于JavaWeb+BS架构+SpringBoot+Vue+Hadoop短视频流量数据分析与可视化系统的设计和实现

基于JavaWebBS架构SpringBootVueHadoop短视频流量数据分析与可视化系统的设计和实现 文末获取源码Lun文目录前言主要技术系统设计功能截图订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 文末获取源码 Lun文目录 目  录 目  录 I 1绪 论 1 1.1开发背景 1 1.2开…

【C++】__declspec含义

目录 一、__declspec(dllexport)如果这篇文章对你有所帮助&#xff0c;渴望获得你的一个点赞&#xff01; 一、__declspec(dllexport) __declspec(dllexport) 是 Microsoft Visual C 编译器提供的一个扩展&#xff0c;用于指示一个函数或变量在 DLL&#xff08;动态链接库&…

VueCli-自定义创建项目

参考 1.安装脚手架 (已安装可以跳过) npm i vue/cli -g2.创建项目 vue create 项目名 // 如&#xff1a; vue create dn-demo键盘上下键 - 选择自定义选型 Vue CLI v5.0.8 ? Please pick a preset:Default ([Vue 3] babel, eslint)Default ([Vue 2] babel, eslint) > M…

OpenHarmony——Linux之IR驱动

Linux之IR驱动 背景 在光谱中波长自760nm至400um的电磁波称为红外线&#xff0c;它是一种不可见光。红外遥控成本很低&#xff0c;以前广泛应用在电视&#xff0c;空调等电器的控制上面&#xff0c;现在随着蓝牙遥控器慢慢普及&#xff0c;红外遥控越来越少&#xff0c;但在某…

leetcode 142 环形链表II

题目 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使…

阳光抑郁症测试

大部分人对抑郁症的理解&#xff0c;就是每天无精打采&#xff0c;死气沉沉&#xff0c;可实际上&#xff0c;还有一种阳光抑郁症&#xff0c;完全不是这个样子。这种抑郁症的人&#xff0c;做事情非常有活力&#xff0c;魅力十足&#xff0c;给人感觉十分有自信&#xff0c;但…

2024Flutter岗位面试题总结

StatelessWidget和StatefulWidget的区别是什么&#xff1f; StatelessWidget是一个不可变的类&#xff0c;充当UI布局中某些部分的蓝图&#xff0c;当某个组件在显示期间不需要改变&#xff0c;或者说没有状态&#xff08;State&#xff09;&#xff0c;你可以使用它。 Statef…

什么是DDOS高防ip?DDOS高防ip是怎么防护攻击的

随着互联网的快速发展&#xff0c;网络安全问题日益突出&#xff0c;DDoS攻击和CC攻击等网络威胁对企业和网站的正常运营造成了巨大的威胁。为了解决这些问题&#xff0c;高防IP作为一种网络安全服务应运而生。高防IP通过实时监测和分析流量&#xff0c;识别和拦截恶意流量&…

MySQL 协议(非常详细适合小白学习)

MySQL 查询过程 MySQL 查询过程大致如下&#xff1a; 1&#xff09;客户端与服务器端建立连接&#xff1b; 2&#xff09;客户端登陆 MySQL&#xff1b; 3&#xff09;客户端向服务器端发起一条请求&#xff1b; 4&#xff09;服务器端先检查查询缓存&#xff0c;如果命中缓…

ArkUI-X跨平台已至,何需其它!

运行环境 DevEco Studio&#xff1a;4.0Release OpenHarmony SDK API10 开发板&#xff1a;润和DAYU200 自从写了一篇ArkUI-X跨平台的文章之后&#xff0c;好多人都说对这个项目十分关注。 那么今天我们就来完整的梳理一下这个项目。 1、ArkUI-X 我们之前可能更多接触的…

运动模型非线性扩展卡尔曼跟踪融合滤波算法(Matlab仿真)

卡尔曼滤波的原理和理论在CSDN已有很多文章&#xff0c;这里不再赘述&#xff0c;仅分享个人的理解和Matlab仿真代码。 1 单目标跟踪 匀速转弯&#xff08;CTRV&#xff09;运动模型下&#xff0c;摄像头输出目标状态camera_state [x, y, theta, v]&#xff0c;雷达输出目标状…

windows的换行符与linux风格的换行符不同的问题

问题展示&#xff1a; 说明&#xff1a; 出现这个错误的原因是脚本文件包含了windows风格换行符&#xff08;‘\r\n’&#xff09;&#xff0c;而在linux环境下&#xff0c;通常使用unix风格的换行符&#xff08;‘\n’&#xff09;.这个问题通常在windows环境下编辑脚本文件然…

档案数字化加工是如何利用档案的

档案数字化加工是通过将实体档案转化为电子形式&#xff0c;利用数字化技术对档案进行处理和管理。这样做可以带来以下几个方面的利益&#xff1a; 1. 提高档案的可访问性&#xff1a;数字化档案可以轻松存储在电脑或云存储中&#xff0c;可以随时随地通过计算机或移动设备访问…

Zung氏抑郁自评量表SDS

抑郁症是常见的心理障碍&#xff0c;其症状表现为&#xff1a;心境低落、思维迟缓、意志活动减退、认知功能损害、躯体症状等。在生活中常有悲观消沉&#xff0c;灰心丧气&#xff0c;对所有事情都提不起兴趣&#xff0c;严重的还会出现肢体僵硬和耳鸣等症状。 部分人有明显的…

操作系统进程定义和PCB详解

进程的定义和PCB 什么是进程&#xff1f;进程就是一个运行起来&#xff08;也就是说加载到内存&#xff09;的一个程序。而程序的本质就是文件&#xff0c;当我们写完代码保存&#xff0c;它便形成了一个保存在磁盘上的二进制代码文件。由于冯诺伊曼体系&#xff0c;cpu只和存储…

RK3568笔记九: DRM显示摄像头

若该文为原创文章&#xff0c;转载请注明原文出处。 一、介绍 学习DRM的目的是想做类似NVR显示多路实时流&#xff0c;通过勇哥&#xff08;Marc)的指导&#xff0c;大概流程是通过Zlmedia拉流&#xff0c;RK3568的MPP解码,DRM显示&#xff0c;可以使用HDMI或DIS屏幕&#xf…

数据结构学习 jz59 滑动窗口的最大值

关键词&#xff1a;排序 大顶堆 双端队列 题目&#xff1a; 望远镜中最高的海拔 方法一&#xff1a;维护一个辅助队列。 方法二&#xff1a;大顶堆。 我还在主站 239 写了找最小值的方法。 方法一&#xff1a;最优解 这个方法和jz30维护一个非严格递减的辅助栈是基本一样的…

15.鸿蒙HarmonyOS App(JAVA)进度条与圆形进度条

15.鸿蒙HarmonyOS App(JAVA)进度条与圆形进度条 progressBar2.setIndeterminate(true);//设置无限模式,运行查看动态效果 //创建并设置无限模式元素 ShapeElement element new ShapeElement(); element.setBounds(0,0,50,50); element.setRgbColor(new RgbColor(255,0,0)); …