深入理解——快速排序

news2024/11/29 10:49:10

目录

💡基本思想

💡基本框架

💡分割方法

⭐Hoare版本

⭐挖坑法

⭐前后指针法

💡优化方法

⭐三数取中法

⭐小区间内使用插入排序

💡非递归实现快速排序

💡性能分析


💡基本思想

任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列左子序列中所有元素均小于基准值右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

💡基本框架

// 假设按照升序对array数组中[left, right)区间中的元素进行排序
void QuickSort(int* array, int left, int right)
{
 if(right - left <= 1)
 return;
 
 // 按照基准值对array数组的 [left, right)区间中的元素进行划分
 int div = partion(array, left, right);
 
 // 划分成功后以div为边界形成了左右两部分 [left, div) 和 [div+1, right)
 // 递归排[left, div)
 QuickSort(array, left, div);
 
 // 递归排[div+1, right)
 QuickSort(array, div+1, right);
}

这是快速排序递归实现的主框架,可以发现与二叉树的递归十分相似,在递归时可以想想二叉树的递归规则。

💡分割方法

⭐Hoare版本

这是Hoare于1962年提出的一种二叉树结构的交换排序方法

这个方法的思想就是R找比key小的数L找比key大的数,然后将R和L对应的数交换,当R和L相遇时,将R和L对应的数与key交换,最终使得比key大的数都在key的右边,比key小的数都在key的左边。

这里其实我们保存的时基准值的下标,记为keyi,这样做是为了方便交换,不然交换时只是与key这个临时变量发生了交换而没有影响到原来的数组里的数。

这里其实还有几个疑点:

  • 当a[left],a[right]与a[keyi]相等时,怎么办?这里的处理方法其实就是不管它,直接继续原来的过程就可以了,最终两边排序时都会将这个数放到合理的位置。
  • 为什么当R与L相遇时,它们所对应的数一定比a[keyi]小?要得到这个结论,必须要R先开始走,当R和L相遇时,有两种情况,一是L动的时候遇见R,此时R由于先走且一直在找比基准值小的数,所以当R停下时,R对应的数一定是小于等于基准值,L找比基准值大的数,一直没有找到,遇见R就停下来;二是R动的时候遇见L,R没有找到比key小的,所以一直走,又因为L一直在找比基准值大的数,所以当L停下时,L对应的数一定大于基准值,因此,只要R先走,R和L相遇时,对应的数一定比a[keyi]小。

⭐挖坑法

所谓挖坑法,就是第一次将基准值的位置设为坑(hole),然后R找比key小的数,填入到坑中,并使R对应的位置成为新的坑,然后L找比key大的数,填入到坑中,并使L对应的位置成为新的坑,再重复进行这个过程,当R和L相遇时,此时它们所对应的位置一定是一个坑,然后再将key填入到坑中,此时key左边的数一定比它小,key右边的数一定比他大。

这个方法相较于hoare的方法更加好理解,但是性能上并没有太大的变化。

//挖坑法
int PartSort(int* a, int begin, int end)
{
	int midi = GetMidi(a, begin, end);
	swap(&a[begin], &a[midi]);
	int key = a[begin];
	int hole = begin;
	while (begin < end)
	{
		//右边找小,填到左边的坑
		while (begin < end && a[end] >= key)
		{
			end--;
		}
		a[hole] = a[end];
		hole = end;
		//左边找大,填到右边的坑
		while (begin < end && a[begin] <= key)
		{
			begin++;
		}
		a[hole] = a[begin];
		hole = begin;
	}
	a[hole] = key;
	return hole;
}

⭐前后指针法

这个方法就是

  1. cur遇到比key大的数,cur++;
  2. cur遇到比key小的数,prev++,交换cur与prev位置的值,cur++。
  3. 当cur超出数组边界时,将prev位置的值与key位置的值交换。
int PartSort(int* a, int begin, int end)
{
	int midi = GetMidi(a, begin, end);
	swap(&a[begin], &a[midi]);
	int keyi = begin;
	int prev = begin;
	int cur = begin + 1;
	while (cur <= end)
	{
		if (a[cur] < a[keyi] && ++prev != cur)//自身交换减少了
		{
			swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	swap(&a[keyi], &a[prev]);
	keyi = prev;
	return prev;
}

💡优化方法

⭐三数取中法

所谓三数取中法,其实取的是三个数中的中位数,将这个数作为基准值,能够避免某些极端情况的出现(比如数组已经接近有序)。

⚠注:这是针对基数选取进行的优化,另外还有随机数法选数,在这里就不过多介绍了。

int GetMidi(int* a, int begin, int end)
{
	int midi = (begin + end) / 2;
	//取中位数
	if (a[begin] <= a[midi])
	{
		if (a[midi] <= a[end])
		{
			return midi;
		}
		else 
		{
			if (a[begin] <= a[end])
				return end;
			else
				return begin;
		}
	}
	else   //midi begin
	{
		if (a[begin] >= a[end])
		{
			if (a[midi] >= end)
			{
				return midi;
			}
			else
				return end;
		}
		else
			return begin;
	}
}

⭐小区间内使用插入排序

在递归到较小区间时,如果仍然使用快速排序,会造成时间上的浪费,假如这个区间内有7个数,那就要递归7次才能得到这个7个数的有序序列。

 if(end-begin+1 <= 10)
{//某个区间内的小规模排序直接插入排序
     //进行插入排序
     InsertSort(arr,end-begin+1);
     return;
}

💡非递归实现快速排序

非递归实现方法其实与递归的方法类似,但是需要借助栈这个数据结构(避免其他方法造成栈溢出)。

每次将要排序的区间的起始位置入栈,然后排序时再取栈顶的前两个元素作为一个排序区间进行快速排序,然后依次对key的左区间、右区间进行这样的操作,最终得到有序序列。

void QuickSortNonR(int* a, int begin, int end)
{
	ST s;
	STInit(&s);
	STPush(&s, end);
	STPush(&s, begin);

	while (!STEmpty(&s))
	{
		int left = STTop(&s);
		STPop(&s);
		int right = STTop(&s);
		STPop(&s);

		int keyi = PartSort(a, left, right);
		// [left, keyi-1] keyi [keyi+1, right]
		if (left < keyi - 1)
		{
			STPush(&s, keyi - 1);
			STPush(&s, left);
		}

		if (keyi + 1 < right)
		{
			STPush(&s, right);
			STPush(&s, keyi + 1);
		}
	}

	STDestroy(&s);
}

💡性能分析

  • 时间复杂度:最差O(N^2),最好O(NlogN),平均O(NlogN)
  • 空间复杂度:O(logN),因为递归时创建的栈帧(申请的空间)没有销毁,递归的深度为logN
  • 稳定性:不稳定
  • 特点:数据越乱排序越快

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

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

相关文章

Java已死!

许多开发者仍然认为 Java 与当今时代息息相关&#xff0c;看完本文&#xff0c;你会发现 Java 的影响力已经大幅减弱。实际上&#xff0c;Java 是一种濒临灭绝的编程语言。尽管 Java 一直是世界上使用最广泛、最受欢迎的编程语言之一&#xff0c;但它很快就会面临消亡的危险。 …

【OpenHarmony 北向应用开发】ArkTS语言入门(构建应用页面)

ArkTS语言入门 在学习ArkTS语言之前&#xff0c;我们首先需要一个能够编译并运行该语言的工具 DevEco Studio。 了解ArkTS ArkTS是OpenHarmony优选的主力应用开发语言。ArkTS围绕应用开发在TypeScript&#xff08;简称TS&#xff09;生态基础上做了进一步扩展&#xff0c;继…

01--二分查找

一. 初识算法 1.1 什么是算法&#xff1f; 在数学和计算机科学领域&#xff0c;算法是一系列有限的严谨指令&#xff0c;通常用于解决一类特定问题或执行计算 不正式的说&#xff0c;算法就是任何定义优良的计算过程&#xff1a;接收一些值作为输入&#xff0c;在有限的时间…

【动态读取配置文件】ParameterTool读取带环境的配置信息

不同环境Flink配置信息是不同的&#xff0c;为了区分不同环境的配置文件&#xff0c;使用ParameterTool工具读取带有环境的配置文件信息 区分环境的配置文件 三个配置文件&#xff1a; flink.properties&#xff1a;决定那个配置文件生效 flink-dev.properties&#xff1a;测…

【计算机网络】—— 详解码元,传输速率的计算|网络奇缘系列|计算机网络

&#x1f308;个人主页: Aileen_0v0&#x1f525;系列专栏: 一见倾心,再见倾城 --- 计算机网络~&#x1f4ab;个人格言:"没有罗马,那就自己创造罗马~" 目录 码元 速率和波特 思考1 思考2 思考3 带宽&#xff08;Bandwidth&#xff09; &#x1f4dd;总结 码元…

MapReduce序列化实例代码

1 &#xff09;需求&#xff1a;统计每个学号该月的超市消费、食堂消费、总消费 2 &#xff09;输入数据格式 序号 学号 超市消费 食堂消费 18 202200153105 8.78 12 3 &#xff09;期望输出格式 key &#xff08;学号&#xff09; value &#xff08; bean 对象&#xf…

用Rust刷LeetCode之58 最后一个单词的长度

58. 最后一个单词的长度[1] 难度: 简单 原描述: 思路 使用标准库: package mainimport ( "fmt" "strings")func lengthOfLastWord(s string) int { s strings.TrimSpace(s) // 删除 首尾 的空格 arr : strings.Split(s, " ") // 字符串转为切片…

FL Studio 21.1.0.3713中文版最新安装激活图文教程及系统配置要求

FL Studio 21.1.0.3713中文版是一款功能强大的编曲软件&#xff0c;它也能够剪辑、混音、录音&#xff0c;它的矢量界面&#xff0c;能更好用在4K、5K甚至8K显示器上。完全重新设计混音器、动态缩放、具有 6 种布局风格、外加 3个用户自定义面板管理音轨、多推子选择和调整、混…

Python 反编译Il2Cpp APK

引入 https://github.com/Perfare/Il2CppDumper/ 实现 开源的Ii2Cpp Dumper可以帮助我们将So和globalmetadata.dat文件反编译出 Assembly-CSharp.dll 本博客教程可以帮助我们直接拖入APK反编译出来 调用方式 两种 第一种 拖入后回车运行 第二种 放入运行的根目录下 源码 i…

Pandas-DataFtame的索引与切片(第3讲)

Pandas-DataFtame的索引与切片(第3讲)         🍹博主 侯小啾 感谢您的支持与信赖。☀️ 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ�…

数据解密战:.mallox勒索病毒攻击下的数据保护

引言&#xff1a; 近期&#xff0c;网络安全领域再度掀起一场不安的浪潮&#xff0c;.Mallox勒索病毒作为最新一位悍匪登场&#xff0c;以其毒辣的加密技术&#xff0c;将用户的数据转瞬间变成了无解之谜。这股数字黑暗力量的出现引起了广泛关注&#xff0c;让用户和企业重新审…

事件监听的艺术:掌握`addEventListener`的魅力

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

前端实现一个时间区间内,再次单选功能,使用Antd组件库内日历组件Calendar

需求&#xff1a;需要先让用户选择一个时间区间&#xff0c;然后再这个时间区间中&#xff0c;让用户再次去单选其种特殊日期。 思路&#xff1a; 1.先用Antd组件库中日期选择DatePicker.RangePicker实现让用户选择时间区间 2.在选择完时间区间后&#xff0c;用这个时间区间…

Java之Clonable接口和深浅拷贝

Clonable接口 我们船舰了一个人的对象&#xff0c;想要克隆一个一模一样的对象&#xff0c;可以用到object类里面的克隆方法 object不是所有类的父类吗&#xff1f;那为什么用person1点不出这个方法呢&#xff1f;可以看一下源码 这是Object类里面的clone方法的声明&#xff0…

随机游走Python中的实现

随机游走是一个数学对象&#xff0c;称为随机或随机过程&#xff0c;它描述了一条路径&#xff0c;该路径由一些数学空间&#xff08;如整数&#xff09;上的一系列随机步骤组成。随机游走的一个基本例子是整数线上的随机游走&#xff0c;它从0开始&#xff0c;每一步以相等的概…

docker小白第五天

docker小白第五天 docker的私有库 有些涉密的信息代码不能放在阿里云的镜像仓库&#xff0c;因此需要构建一个个人内网专属的私有库&#xff0c;将镜像或者容器代码进行推送保存。 下载镜像docker registry 执行代码docker pull registry&#xff0c;用于搭建私服前的准备。…

为什么选择计算机?大数据时代学习计算机的价值探讨

还记得当初自己为什么选择计算机? 计算机是在90年代兴起的专业,那时候的年轻人有驾照、懂外语、懂计算机是很时髦的事情! 当初你问我为什么选择计算机,我笑着回答:“因为我梦想成为神奇的码农!我想像编织魔法一样编写程序,创造出炫酷的虚拟世界!”谁知道,我刚入门的…

力扣刷题-二叉树-平衡二叉树

110 平衡二叉树 给定一个二叉树&#xff0c;判断它是否是高度平衡的二叉树。 本题中&#xff0c;一棵高度平衡二叉树定义为&#xff1a;一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。 示例 1: 给定二叉树 [3,9,20,null,null,15,7] 返回 true 。 给定二叉树 [1…

diffuser为pipeline设置不用的scheduler

查看默认的schedulers&#xff1a; 使用默认的schedulers生成数据 查看默认scheduler的默认配置&#xff0c;定义了采样器中的相关参数&#xff0c;网上关于DDPM和DDIM的文章较多&#xff0c;可以先去看看这两种schedulers&#xff1a; 修改scheduler&#xff0c;可以用于…

【TB作品】51单片机,具有报时报温功能的电子钟

2.具有报时报温功能的电子钟 一、功能要求: 1.显示室温。 2.具有实时时间显示。 3.具有实时年月日显示和校对功能。 4.具有整点语音播报时间和温度功能。 5.定闹功能,闹钟音乐可选。 6.操作简单、界面友好。 二、设计建议: 1.单片机自选(C51、STM32或其他单片机)。 2.时钟日历芯…