快排详解(4种写法:霍尔/挖坑法/双指针/非递归)

//本文所写快排的结果都是从小到大排序

思路

快排就是把数组的第一个值记为key,然后定义两个指针,一个叫begin,一个叫end.

begin指针从数组的头部出发,寻找比key大的值;end指针从数组的尾部出发,寻找比key小的值;

然后交换begin和end的值

......最后,begin和end相遇就停下,此时交换begin和key的值(也可以看做交换end和key的值),

就可以得到这样的结果:key左边的值都比key小,key右边的值都比key大.

然后key的左边和右边分别进行上面的排序过程,直到数组彻底有序为止.

待会还有一些细节的东西会在下面提到.

图解

比较简略,大家将就着看叭...

图中的left和right指针就是上面提到的begin和end指针...

3d5a89f9c60c4aceb7b6d8c05d5d5aae.png

霍尔版本写法(递归)

代码有两个函数,一个是QuickSort,构建了递归的框架;另一个是_QuickSort,里面写了快排的排序实现.

int _QuickSort(int* a, int left,int right)
{
	int key = left;
	int begin = left;
	int end = right;
	while (begin < end)
	{
		//end先走,end找比key小的值
		while (begin < end && a[end] >= a[key])//防止越界,加上begin < end
		{
			--end;
		}

		//begin找比key大的值
		while (begin < end && a[begin] <= a[key])
		{
			++begin;
		}

		swap(a[begin], a[end]);
	}

	swap(a[key], a[begin]);
//注意:我们交换的是数组的a[key]和 a[begin],但是key等于left,key应该更新成begin
	key = begin;
	return key;
}


void QuickSort(int* p, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	else
	{
		int key = _QuickSort(p, left, right);
		QuickSort(p, left, key - 1);
		QuickSort(p, key + 1, right);
	}

}

注意事项

1.关于end先走

end先走可以保证最终停留的位置比key小。(不考虑这种情况:在数组有序的情况下,第一次循环end直接走到begin的位置,此时key位置的值就会等于end/begin位置的值)

让我们分析循环终止的情况:

要么就是end--走到了begin的位置,begin位置的值比key小,因为begin还在上次交换的位置,begin存的是比key小的值。

要么是end找到了比key小的值,就停在了那里,begin走到了end的位置,begin存的值也会比key小。

2.end先走的作用

交换key和begin的值,要保证key的左边值都比key小,右边值都比key大,所以begin停留的位置(换而言之end停留的位置)要比key小,才能做到。

优化

1.key值的选取

万一key值是最小的或者最大的,走快排就很浪费时间,所以我们选的key最好是一个中间值.

所以我们将会写一段代码来选取合适的key值.

2.数据比较少的时候使用插入排序

当数据个数比较少的时候,走插入排序比快排快,所以假设有百万数据时,我们可以让数据小于10个的时候走插入排序,数据大于10个走插入排序

3.优化后的代码

//此代码的swap函数是库里的

//为key选取合适的中间值
int GetMidi(int* a, int left, int right)
{
	int midi = left + (right - left) / 2;
	if (a[left] >= a[midi])
	{
		if (a[right] >= a[left]) {
			return left;
		}
		else if (a[midi] >= a[right])
		{
			return midi;
		}
		else
		{
			return right;
		}
	}
	else
	{
		if (a[left] >= a[right])
		{
			return left;
		}
		else if (a[midi] <= a[right])
		{
			return midi;
		}
		else
		{
			return right;
		}
	}
}


void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;
		int temp = a[end + 1];
		while (end >= 0)
		{
			if (a[end] > temp)
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = temp;
	}
}

int _QuickSort(int* a, int left,int right)
{
	int midi = GetMidi(a, left, right);
	if (midi!=left)
	{
		swap(&a[midi], &a[left]);
	}
	int keyi = left;
	int begin = left;
	int end = right;
	while (begin < end)
	{
		//end先走,end找比key小的值
		while (begin < end && a[end] >= a[key])//防止越界,加上begin < end
		{
			--end;
		}

		//begin找比key大的值
		while (begin < end && a[begin] <= a[key])
		{
			++begin;
		}

		swap(a[begin], a[end]);
	}

	swap(a[key], a[begin]);
	key = begin;
	return key;
}

挖坑法(递归)

挖坑法在效率上和霍尔法一样,但是会少很多注意事项.

思路

大体上和霍尔排序的思路一样,也有两个指针left和right从头和尾出发,一个找比key大,一个找比key小.

区别在于选取了key值之后,原数组key的地方会空出来,等待比key 小的值right放进去,然后数组里就会空出right的位置,等待比key大的值left放进right的位置,然后left的位置会空出来.......重复此过程,直到left和right相遇.

图解

代码

//此代码的swap是自己写的


void InsertSort(int* arr, int n)
{
	
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;
		int temp = arr[end + 1];
		while (end >= 0)
		{
			if (temp < arr[end])
			{
				arr[end + 1] = arr[end];
				end--;
			}
			else
			{
				break;
			}
		}
		arr[end + 1] = temp;
	}
}

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

void swap(int* p1, int* p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}

void QuickSort(int* arr, int left,int right)
{
	if (left >= right) {
		return;
	}
	if ((right - left + 1)<=10)
	{
		InsertSort(arr + left, right - left + 1);
		return;
	}
	else
	{
		int mid = GetMid(arr, left, right);
		if (mid != left)
		{
			swap(&arr[mid], &arr[left]);
		}

		int key = arr[left];
		//int flag = 0;
		int lefti = left, righti = right;
//首次记录hole的值
		int hole = left;
		while (lefti < righti)
		{

			while (lefti < righti && arr[righti] >= key)
			{
				--righti;
			}
//比key小的值填入hole
			arr[hole] = arr[righti];
//更新hole的位置
			hole = righti;
			while (lefti < righti && arr[lefti] <= key)
			{
				++lefti;
			}
//比key大的值填入hole
			arr[hole] = arr[lefti];
//更新hole的位置
			hole = lefti;
		}
		arr[hole] = key;
		QuickSort(arr, left, hole - 1);
		QuickSort(arr, hole + 1, right);
	}
	
}

双指针法

思路

有两个指针,一个叫pre,一个叫cur,

cur初始时在pre前面的一个位置,

每循环一次,cur都会往前走一步,

如果cur的值比key小,且pre的下一个值不等于cur,

(实际上,如果cur的值比key大,pre指针就不会走)

那就交换cur和pre的值.

图解

代码

//GetMidi函数本文前面出现过,我就不放了
int partSort2(int* a, int left, int right)
{
	int midi = GetMidi(a, left, right);
	if (midi != left)
	{
		swap(&a[midi], &a[left]);
	}
	int keyi = left;
	int prev = left;
	int cur = prev + 1;
	while (cur <= right)
	{
		while (a[cur] < a[keyi] && ++prev != cur)
		{
			swap(&a[cur], &a[prev]);
		}
		++cur;
	}
	swap(&a[keyi], &a[prev]);
	return prev;
}
//QuickSort函数也要调用哦,本文前面出现过我就不放了

非递归(使用栈)

思路

和递归的部分思路是相似的,

同样需要key值,

key把数组分为左右两部分,

右边数组先入栈,

左边数组后入栈,

每次取栈顶的区间排序.

代码

void QuickSortNonR(int* a, int left, int right)
{
	stack<int> st;
	st.push(left);
	st.push(right);
	while (!st.empty())
	{
		int end = st.top();
		st.pop();
		int begin = st.top();
		st.pop();
//这是双指针法排序,在本文章的上一部分提及了
		int key = partSort2(a, begin, end);
		if (key + 1 < end) {
			st.push(key + 1);
			st.push(end);
		}
		if (begin < key - 1)
		{
			st.push(begin);
			st.push(key - 1);
		}
	}
}

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

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

相关文章

Linux服务器安装mongodb

因为项目需要做评论功能&#xff0c;领导要求使用mongodb&#xff0c;所以趁机多学习一下。 在服务器我们使用docker安装mongodb 1、拉取mongodb镜像 docker pull mongo &#xff08;默认拉取最新的镜像&#xff09; 如果你想指定版本可以这样 docker pull mongo:4.4&#…

Bert+CRF的NER实战

CRF&#xff08;条件随机场-Conditional Random Field&#xff09; 原始本文&#xff1a;我在北京吃炸酱面 标注示例&#xff1a; 我O在O北B-PLA京I-PLA吃O炸B-FOOD酱I-FOOD面I-FOOD CRF&#xff1a; 目的&#xff1a;提出一些不可能出现的预测组合&#xff08;例如I-PLA不能…

时序论文27|Fredformer:频域去偏差的时序预测Transformer模型

论文标题&#xff1a;Fredformer: Frequency Debiased Transformer for Time Series Forecasting 论文链接&#xff1a;https://arxiv.org/abs/2406.09009 代码链接&#xff1a;https://github.com/chenzRG/Fredformer 前言 这篇文章发表于KDD2024&#xff0c;作者的出发点…

带外配置IP

要想了解带内&#xff0c;私下我 管理IP:9.101.8.20 掩码&#xff1a;255.0.0.0 网关&#xff1a;9.101.0.254 1 首先自己电脑要修改ip 192.168.70.x 段 2 在cmd 去ping 192.168.70.125 必须通 3 去浏览器 登录192.168.70.125 4 更改ip 5 再次修改电脑IP 网关 掩码 7 检测…

大型复杂项目管理怎么结合传统与敏捷

大型复杂项目管理需要综合运用传统的瀑布模型与敏捷方法&#xff0c;两者各具优势&#xff0c;可以在不同的项目阶段和需求下发挥最大效能。首先&#xff0c;在项目的初期阶段&#xff0c;传统方法的详细规划和需求分析能够帮助确保项目方向正确、资源充足&#xff1b;敏捷方法…

Vue 2.0->3.0学习笔记(Vue 3 (四)- Composition API 的优势)

Vue 2.0-&#xff1e;3.0学习笔记&#xff08;Vue 3 &#xff08;四&#xff09;- Composition API 的优势&#xff09; Composition API 的优势1. Options API 存在的问题2. Composition API 的优势 Composition API 的优势 1. Options API 存在的问题 笔记 使用传统OptionsA…

工程设计与总承包行业数字化转型:现状洞察、挑战突围与前景展望

一、现状洞察 &#xff08;一&#xff09;数字化技术应用初现成效 BIM 技术局部应用&#xff1a;部分企业在工程设计阶段利用 BIM 技术实现三维建模和设计可视化&#xff0c;施工前模拟环节可优化流程提高效率&#xff0c;但普及程度有待提高。项目管理软件逐步推广&#xff…

Spring Boot优雅读取配置信息 @EnableConfigurationProperties

很多时候我们需要将一些常用的配置信息比如oss等相关配置信息放到配置文件中。常用的有以下几种&#xff0c;相信大家比较熟悉&#xff1a; 1、Value(“${property}”) 读取比较简单的配置信息&#xff1a; 2、ConfigurationProperties(prefix “property”)读取配置信息并与 …

关于音频 DSP 的接口种类以及其应用场景介绍

在音频系统中&#xff0c;DSP&#xff08;数字信号处理器&#xff09;扮演着重要角色&#xff0c;通常会通过不同的接口与音频系统中的其他组件&#xff08;如功放、扬声器、音频源等&#xff09;进行连接。以汽车应用场景为例&#xff0c;以下是一些常见的接口类型分类及其介绍…

A02、数据库性能调优

1、如何写出高性能SQL语句 1.1、慢SQL原因 1.1.1、无索引、索引失效导致慢查询 如果在一张几千万数据的表中以一个没有索引的列作为查询条件&#xff0c;大部分情况下查询会非常耗时&#xff0c;这种查询毫无疑问是一个慢 SQL 查询。所以对于大数据量的查询&#xff0c;我们需…

基于FPGA的FM调制(载波频率、频偏、峰值、DAC输出)-带仿真文件-上板验证正确

基于FPGA的FM调制-带仿真文件-上板验证正确 前言一、FM调制储备知识载波频率频偏峰值个人理解 二、代码分析1.模块分析2.波形分析 总结 前言 FM、AM等调制是学习FPGA信号处理一个比较好的小项目&#xff0c;通过学习FM调制过程熟悉信号处理的一个简单流程&#xff0c;进而熟悉…

element ui select绑定的值是对象的属性时,显示异常.

需要声明 value-key"value",如果还不行可能是数据类型不一致数字0和字符串0是不一致的. el-select v-model"value" clearable placeholder"Select" value-key"value" style"width: 240px"><!-- <el-option v-for&…

[免费]SpringBoot+Vue景区订票(购票)系统【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的SpringBootVue大景区订票(购票)系统&#xff0c;分享下哈。 项目视频演示 【免费】SpringBootVue景区订票(购票)系统 Java毕业设计_哔哩哔哩_bilibili 项目介绍 现代经济快节奏发展以及不断完善升级的信息…

2024143读书笔记|《遇见》——立在城市的飞尘里,我们是一列忧愁而又快乐的树

2024143读书笔记|《遇见》——立在城市的飞尘里&#xff0c;我们是一列忧愁而又快乐的树 第1章 年年岁岁岁岁年年第2章 遇见第3章 有个叫“时间”的家伙走过第4章 初雪第6章 回首风烟 《华语散文温柔的一支笔&#xff1a;张晓风作品集&#xff08;共5册&#xff09;》作者张晓风…

医学机器学习:数据预处理、超参数调优与模型比较的实用分析

摘要 本文介绍了医学中的机器学习&#xff0c;重点阐述了数据预处理、超参数调优和模型比较的技术。在数据预处理方面&#xff0c;包括数据收集与整理、处理缺失值、特征工程等内容&#xff0c;以确保数据质量和可用性。超参数调优对模型性能至关重要&#xff0c;介绍了多种调…

零基础Python学习

1.环境搭建 1.1 安装运行环境python3.13 Welcome to Python.org 1.2 安装集成开发环境PyCharm PyCharm: the Python IDE for data science and web development 1.3 创建项目 && 设置字体 2.基础语法 2.1 常量与表达式 在python中整数除整数不会优化&#xff0c;所…

数据链路层(三)--点对点通信协议PPP

PPP协议叫做点对点协议&#xff0c;是目前使用的最广泛的数据链路层协议。 1 PPP协议的特点 用户通常需要连接到某个ISP才能接入互联网&#xff0c;PPP协议就是用户计算机和ISP进行通信所使用的数据链路层协议。 1.1 PPP协议应满足的需求 &#xff08;1&#xff09;简单&…

嵌入式QT学习第4天:Qt 信号与槽

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 本章思维导图如下&#xff1a; 不使用 Qt Designer 的方式进行开发&#xff0c;用代码绘界面&#xff0c;可以锻炼我们的布局能力&#xff0c;和代码逻辑能力&#x…

Figma入门-自动布局

Figma入门-自动布局 前言 在之前的工作中&#xff0c;大家的原型图都是使用 Axure 制作的&#xff0c;印象中 Figma 一直是个专业设计软件。 最近&#xff0c;很多产品朋友告诉我&#xff0c;很多原型图都开始用Figma制作了&#xff0c;并且很多组件都是内置的&#xff0c;对…

Redis使用场景-缓存-缓存穿透

前言 之前在针对实习面试的博文中讲到Redis在实际开发中的生产问题&#xff0c;其中缓存穿透、击穿、雪崩在面试中问的最频繁&#xff0c;本文加了图解&#xff0c;希望帮助你更直观的了解缓存穿透&#x1f600; &#xff08;放出之前写的针对实习面试的关于Redis生产问题的博…