【数据结构】常见排序算法——快速排序的三种实现、 hoare版本、挖坑法、前后指针版本

news2024/11/24 10:29:12

文章目录

  • 1.常见排序
  • 2.快速排序
    • 2.1hoare版本
    • 2.2快速排序优化
    • 2.3挖坑法实现
    • 2.4前后指针实现

1.常见排序

在这里插入图片描述

2.快速排序

  快速排序(Quick Sort) 是一种常见的排序算法,也是一种基于分治算法的排序。该算法的基本思想是将一个数据集分成两个子集,其中一个子集比另一个子集的所有元素都小,然后递归地对子集进行排序,最终得到排好序的数据集。

  具体地说,快速排序的具体步骤为:首先从待排序的数据集中选取一个元素作为“基准”(key),然后将所有小于基准的元素移动到基准的左边,将所有大于基准的元素移动到基准的右边,最后将基准置于两个子集的中间位置。然后递归地对左右两个子集进行快速排序,直到所有子集只包含一个元素,排序完成。

  快速排序具有时间复杂度为O(nlogn)的优秀排序效率,并且实现简单,因此被广泛应用于各种排序场景。

在这里插入图片描述

快速排序的实现思想:

快速排序的实现类似于二叉树的前序遍历,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

2.1hoare版本


hoare版本快速排序的实现思想:

  Hoare版本的快速排序是一种基于分治思想的快速排序算法。其基本思路是:

(1)选取基准元素(key),通常是从中间取,将待排序的数据集分成两部分。

(2)分别从第一部分和第二部分(即数据集的两端)开始,设定两个指针i和j,然后让i从左往右扫描,找到第一个大于等于key的元素,然后让j从右往左扫描,找到第一个小于等于key的元素,最后将i和j所指的元素交换位置。

(3)重复2中的操作,直到i >= j,也就是第一次遍历时找到的第一个小于等于key的元素和第一个大于等于key的元素已经相遇(或者说交叉)。

(4)将当前i所指的元素(即第一个大于等于key的元素)和key交换位置。

(5)此时,所有小于等于key的元素都在左边,所有大于key的元素都在右边,而key本身也已经到了中间的位置,也就是分割线的位置。

(6)对左右两部分分别重复上述步骤,直到每部分只剩下一个元素或者为空。

值得注意的是,在Hoare版本中,交换i和j所指的元素时并不需要额外的判断,因为我们在第一步就选取了某一个元素作为基准,i和j所指向的元素必须满足小于或等于key或大于或等于key的条件,这样才可以保证算法的正确性。

在这里插入图片描述

hoare版本快速排序的实现:

  算法首先判断left和right的大小,如果left >= right,则数组已经有序,直接返回。

  然后定义begin为left,end为right,keyi为基准元素的下标,一开始取的是left。

  接下来,算法使用两个while循环来对数组进行分割。具体来说,第一个while循环先从右端开始,找到第一个小于基准元素a[keyi]的元素a[right],第二个while循环从左端开始,找到第一个大于基准元素a[keyi]的元素a[left],然后交换a[left]和a[right]的值。

  当两个while循环都结束后,交换基准元素a[keyi]和a[left]的值,并将keyi更新为left。

  接下来,算法根据keyi的值将a数组分成两部分,分别对左半部分 [begin, keyi-1] 和右半部分 [keyi+1, end] 进行递归调用,直到每个子数组只有一个元素,排序完成。

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void QuickSortHoare(int* a, int left, int right)
{
	if (left >= right)
		return;

	int begin = left, end = right;
	
	int keyi = left;
	while (left < right)
	{
		while (left < right && a[right] >= a[keyi])
		{
			--right;
		}

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

		Swap(&a[left], &a[right]);
	}

	Swap(&a[keyi], &a[left]);
	keyi = left;

	// [begin, keyi-1] keyi [keyi+1, end] 
	// 递归
	QuickSortHoare(a, begin, keyi - 1);
	QuickSortHoare(a, keyi + 1, end);
}

2.2快速排序优化

  快速排序虽然是一种高效的排序算法,但是在一些情况下,它也可能会表现出很差的性能。当所需要排序数组有序时,它近似于冒泡排序,时间复杂度为 O(n^2)。

(1)随机化选取基准元素(key):为了避免快速排序在面对特定输入时产生性能问题,我们也可以通过随机化选取基准元素来保证快速排序的平均性能。具体来说,我们可以随机选取数组中的一个元素作为基准元素。

(2)三数取中法:为了避免快速排序在对于已经有序或者基本有序的数组排序时性能退化,我们可以采用三数取中法来选择基准元素。具体来说,我们可以从待排序数组的左、中、右三个位置选取一个中间大小的数字作为基准元素。还有其他如插入排序或左右端点优化的方法。

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

int GetMidNumi(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else // a[left] > a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

void QuickSortHoare(int* a, int left, int right)
{
	if (left >= right)
		return;

	int begin = left, end = right;

	//随机选key
	//int randi = left + (rand() % (right - left));
	//Swap(&a[left], &a[randi]);

	//三数取中
	int midi = GetMidNumi(a, left, right);
	if (midi != left)
	Swap(&a[midi], &a[left]);

	int keyi = left;
	while (left < right)
	{
		while (left < right && a[right] >= a[keyi])
		{
			--right;
		}

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

		Swap(&a[left], &a[right]);
	}

	Swap(&a[keyi], &a[left]);
	keyi = left;

	// [begin, keyi-1] keyi [keyi+1, end] 
	// 递归
	QuickSortHoare(a, begin, keyi - 1);
	QuickSortHoare(a, keyi + 1, end);
}

2.3挖坑法实现

挖坑法快速排序的实现思想:

  挖坑法的基本思路是选取一个元素作为基准(通常是中间元素),然后定义两个指针L和R,L指向序列的第一个元素,R指向序列的最后一个元素。然后,先从R开始向左遍历,找到第一个小于基准的数,并把它填入L所指的坑中,然后L再从左开始向右遍历,找到第一个大于基准的数,并把它填入R所指的坑中,直到L >= R为止。此时,所有小于等于基准的数都在L的左边,而所有大于基准的数都在R的右边。

  最后,把基准值与i所指的位置互换,从而将基准值放入正确的位置,然后分别对左右两个部分递归进行快速排序,直到每个部分的长度不超过1,排序完成。

  挖坑法的优点是实现简单,且不需要额外的数组空间来存储分割点。

在这里插入图片描述

挖坑法快速排序的实现:

  我们首先使用GetMidNumi三数取中函数来选取一个中间大小的数作为基准(key),然后将它与数组的第一个位置交换。

  接下来,定义key为基准值,hole为坑的位置指针,初始值为left。使用while循环来扫描待排序的部分,将小于等于基准值的数放到key左边,将大于基准值的数放到key右边。while循环中,先从右边开始找到第一个小于key的数,然后将它填到左边hole所指的位置,让hole指针指向这个位置;然后再从左边开始找到第一个大于key的数,将他放到hole所指的位置,然后hole指针指向这个位置。直到left >= right为止。

  最后,将key值放到hole所指的位置,这样,左边的数都小于key,右边的数都大于key。

  这个函数所作的操作是对数组的一部分进行划分,并返回key的位置hole。对于这个hole所在的位置左侧的数据来说,都是小于等于基准key的;而hole所在位置的数据就是基准key;右侧的数据都是大于key的。需要注意的是,最后可能存在多个大于或小于key的数字,只有最后一个hole放置key之后,才可以确定划分的位置。

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

int QuickSortHole(int* a, int left, int right)
{
	// 三数取中
	int midi = GetMidNumi(a, left, right);
	if (midi != left)
		Swap(&a[midi], &a[left]);

	int key = a[left];
	int hole = left;
	while (left < right)
	{
		// 右边找小
		while (left < right && a[right] >= 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;
}

2.4前后指针实现

前后指针法快速排序的实现思想:

(1)选取任意一个元素作为基准值key,通常是选取待排序数组的第一个元素。

(2)定义两个指针,分别指向待排序数组的第一个元素和第二一个元素,用cur表示后指针,prev表示前指针。

(3)然后不断将cur和prev指针不断后移,向所指向的元素与基准值进行比较,如果cur大于基准值且prev小于基准值,交换prev和cur所指向的元素。prev指针向后移动一个位置;将cur指针向后移动一个位置,

(4)重复上述过程,直到cur指针越界。

(5)最后将基准值所在的位置与prev所在的位置交换,然后分别以基准值所在的位置为分界点,对左右两侧的部分进行递归排序。

在这里插入图片描述

前后指针法快速排序的实现:

  函数主体使用了前后指针对数组元素进行交换排序。定义prev和cur两个指针并初始化为left和left+1,分别指向前一个位置和当前位置。循环遍历数组,当a[cur]小于a[keyi]时,将cur所指的值与++prev所指的值交换,从而保证prev之前的元素都是小于基准值的,cur之后的元素都是大于等于基准值的。

  当循环结束后,将基准值与prev所指的位置互换,将基准值归位并返回其位置。

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

int QucikSortPointer(int* a, int left, int right)
{
	// 三数取中
	int midi = GetMidNumi(a, left, right);
	if (midi != left)
		Swap(&a[midi], &a[left]);

	int keyi = left;

	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		if (a[cur] < a[keyi] && ++prev != cur)
			Swap(&a[cur], &a[prev]);

		++cur;
	}

	Swap(&a[prev], &a[keyi]);
	keyi = prev;

	return keyi;
}

快速排序的特性总结:

(1)快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

(2)时间复杂度:O(N*logN)

(3)空间复杂度:O(logN)

(4)稳定性:不稳定

这些就是数据结构中有关快速排序三种实现的简单介绍了😉
如有错误❌望指正,最后祝大家学习进步✊天天开心✨🎉

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

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

相关文章

实验四、shell编程

一、实验目的 1.了解shell的特点和主要种类。 2.掌握 shel1 脚本的建立和执行方式。 3.掌握bash的基本语法。 4.学会编写shell 脚本。 二、实验内容 shell 脚本的建立和执行。历史命令和别名定义。shell变量和位置参数、环境变量。bash的特殊字符。一般控制结构。算术运算及…

Redis事务和管道

一、Redis事务 1、定义 可以一次执行多个命令&#xff0c;本质上是一组命令的集合。一个事务中的所有命令都会序列化&#xff0c;按顺序的串行化执行而不会被其他命令插入&#xff0c;不能加塞。 2、作用 一个队列中&#xff0c;一次性、顺序性、排他性的执行一系列命令。 …

第Y3周:yolov5s.yaml文件解读

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊|接辅导、项目定制 ✅本周任务&#xff1a;将yolov5s网络模型中第4层的C3*2修改为C3*1&#xff0c;第6层的C3*3修改为C3*2。 简单介绍&#xff1a; YOLOv5配置了…

企业Wiki和知识库-SaaS产品运营指南

内部Wiki也叫做企业Wiki&#xff0c;是员工可以存储、共享和协作创作的地方&#xff0c;将企业内部员工知识共享集中到一个地方&#xff0c;并且相关内容与其他团队成员协作完成&#xff0c;它可以包含企业内部的各种知识&#xff0c;从操作指南到培训手册&#xff0c;再到客户…

RabbitMQ - 延迟队列

RabbitMQ - 延迟队列 延迟队列介绍RabbitMQ 中的 TTL整合 springboot队列 TTL延时队列TTL优化Rabbitmq 插件实现延迟队列总结 延迟队列介绍 延迟队列概念&#xff1a; 延时队列,队列内部是有序的&#xff0c;最重要的特性就体现在它的延时属性上&#xff0c;延时队列中的元素是…

《Lua程序设计》--学习3

输入输出 简单I/O模型 Lua 文件 I/O | 菜鸟教程 (runoob.com) 暂留 补充知识 局部变量和代码块 Lua语言中的变量在默认情况下是全局变量&#xff0c;所有的局部变量在使用前必须声明 在交互模式中&#xff0c;每一行代码就是一个代码段&#xff08;除非不是一条完整的命…

spark的高阶用法

广播变量broadcast 使用场景:本地集合变量和分布式变量(rdd)进行关联的时候使用 优点:1.可以节省io操作.2.减少executor的内存占用 #定义 map_list {(1,dawang,22),(2,xiaogou,333).....} broadcast sc..broadcast(map_list) #使用 for i in broadcast.value:print(i)累加器a…

【TA100 】 LDR与HDR

一、LDR和HDR的基本概念 1.HDR 、LDR、动态范围 ● Dynamic Range&#xff08;动态范围&#xff09;最高亮度/最低亮度 ● HDR High Dynamic Range ● LDR Low Dynamic Range ● ToneMapping&#xff1a;将超高的动态范围&#xff08;HDR&#xff09;转换到我们日常显示的屏…

指定英国名校|社会科学老师喜赴曼彻斯特大学访学研究

社会科学较理工科专业申请访问学者的难度更大&#xff0c;何况M老师还有学校、专业、时间等要求。最终我们为其落实了世界50强名校—曼彻斯特大学全球发展研究所的职位&#xff0c;专业方向高度契合。在对方行政办理流程时遇到一些阻力&#xff0c;好在有惊无险地完成了全部流程…

Simulink尝试双脉冲实验验证MOSFET二极管反向恢复的特性(附仿真模型)

目录 前言 双脉冲实验 Simulink仿真对比 总结 前言 最近在做交错串联的图腾柱单相PFC的项目&#xff0c;基于模型的开发&#xff0c;想要在仿真上实现过零点尖峰电流产生并通过软启动进行抑制&#xff0c;把整个过程都通过仿真实现出来&#xff0c;在这个过程中尝试了Simul…

深入 Synchroized 原理,从入门到精通

目录 一、倔强青铜 1.1 多线程一定快吗&#xff1f; 1.2 上下文切换 1.3 测试上下文切换次数 1.4 Java内存模型 1.5 主内存与工作内存之间的数据交互过程 二、秩序白银 2.1 多线程带来的可见性问题 2.2 多线程带来的原子性问题 2.3 多线程带来的有序性问题 三、荣耀…

Yolov5涨点神器:RIFormerBlock助力检测|CVPR2023|RIFormer:无需TokenMixer也能达成SOTA性能的极简ViT架构

1.RIFormer介绍 论文:https://arxiv.org/pdf/2304.05659.pdf 本文基于重参数机制提出了RepIdentityFormer方案以研究无Token Mixer的架构体系。紧接着,作者改进了学习架构以打破无Token Mixer架构的局限性并总结了优化策略。搭配上所提优化策略后,本文构建了一种极致简单且…

目标检测算法:Faster-RCNN论文解读

目标检测算法&#xff1a;Faster-RCNN论文解读 前言 ​ 其实网上已经有很多很好的解读各种论文的文章了&#xff0c;但是我决定自己也写一写&#xff0c;当然&#xff0c;我的主要目的就是帮助自己梳理、深入理解论文&#xff0c;因为写文章&#xff0c;你必须把你所写的东西表…

Python爬虫——爬取阳光高考专业数据并对所有专业进行数据分析

前言 阳光高考是中国高考信息网&#xff0c;覆盖了中国所有院校以及所有专业信息。本文目的是爬取阳光高考的专业信息&#xff0c;包括专业名称&#xff0c;专业代码&#xff0c;专业简介&#xff0c;男女比例&#xff0c;在校生规模&#xff0c;就业方向&#xff0c;平均薪资…

LVS负载均衡 DR模式

目录 -----------------DR模式 LVS负载均衡群集部署----------------------------------- 1.配置负载调度器&#xff08;192.168.110.100&#xff09; 2.部署共享存储&#xff08;NFS服务器&#xff1a;192.168.80.13&#xff09; 3.配置节点服务器&#xff08;192.168.80.…

H5吊起微信小程序(适用于从短信、邮件、微信外网页等场景打开小程序任意页面)

​1.实现功能 H5页面中实现打开微信小程序的功能用户在网页中一键唤起小程序 2.前提条件 必须是企业的小程序获取AppID&#xff0c;也就是小程序唯一凭证&#xff0c;可在微信公众平台 - 设置 - 开发设置」页中获得。&#xff08;需要已经成为开发者&#xff0c;且帐号没有异…

springboot mybatis-plus 代码生成工具

介绍 基于mybatis-plus代码生成工具 后续会不断完善 规划 后续会基于此功能搞低代码平台&#xff0c;会有前端VUE mybatis-plus介绍&特性 • 无侵入&#xff1a;只做增强不做改变&#xff0c;引入它不会对现有工程产生影响&#xff0c;如丝般顺滑 • 损耗小&#xff1…

【玩转Linux操作】查找命令时间日期指令

&#x1f38a;专栏【玩转Linux操作】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【Counting Stars 】 欢迎并且感谢大家指出小吉的问题&#x1f970; 文章目录 &#x1f354;查找⭐find命令&#x1f388;按文件名&#x1f388…

Mac 远程连接Windows服务器

要从 Mac 电脑远程连接到 Windows 服务器&#xff0c;您可以使用 macOS 上内置的远程桌面连接 (RDC) 客户端。 方法如下&#xff1a; 确保您要连接的 Windows 服务器已启用远程桌面并且可以从您的网络访问。 您可能需要配置服务器的防火墙设置以允许远程桌面连接。 在您的 Ma…

Knife4j的请求示例当中有很多空白行

问题描述&#xff1a; 按正常来说不应该有上方的空白&#xff0c;当然如果只是查看我也不至于非要解决他&#xff0c;主要是假如接口是json传参&#xff0c;调试界面都没办法修改参数…网上相关的资料又非常少&#xff0c;我别的项目引用的同样的依赖并没有出现如此情况。 引入…