[数据结构] 归并排序快速排序 及非递归实现

news2024/10/6 7:11:25

()标题:[数据结构] 归并排序&&快速排序 及非递归实现

@水墨不写bug


(图片来源于网络)


目录

(一)快速排序

类比递归谋划非递归

快速排序的非递归实现:

(二)归并排序 

归并排序的递归实现:

归并排序的非递归

细节处理:

归并排序的非递归实现:


 

 正文开始:

(一)快速排序

        快速排序一般通过递归来实现,但是递归也有递归的劣势:当递归程度太深,会导致栈溢出的问题,我们在前面的分享中已经讲解了快速排序的递归实现,这里不再赘述,为了便于讲解,直接给出快速排序的递归实现:


int GetRandomKey(vector<int>& nums, int l, int r)
{
	return nums[rand() % (r - l + 1) + l];
}
void QuickSort(vector<int>& nums,int l,int r)
{
	//递归出口
	if (l >= r)
		return;
	int key = GetRandomKey(nums,l,r);
	int left = l - 1, right = r + 1, cur = l;
	while (cur < right)
	{
		if (nums[cur] < key)
			swap(nums[cur++], nums[++left]);
		else if (nums[cur] == key)
			cur++;
		else
			swap(nums[--right],nums[cur]);
	}
	QuickSort(nums, l, left);
	QuickSort(nums, right, r);
}

这里给出的快速排序的递归实现是比较完备的优化过的快排,它解决了:

        (1)、key选取不合适导致的分区不平衡的问题。

        (2)、key在数据中重复大量出现的问题。

递归的过程:

        其实通过观察快排的过程,我们会发现之所以在传入参数的时候必须传入左右区间,是因为我们在快排的内部过程中并不确定需要对哪一个区间的数据 进行排序。

        随着递归的进行,函数栈帧逐层开辟,每一层函数栈帧中都存有需要排序的区间的边界值。

每一个函数栈帧都有一个 

        左区间端点值 :l 

        右区间端点值 :r 

递归是在栈区进行的,我们既然需要避免计算机自己的栈区溢出,那么我们为什么不自己模拟一个栈呢?


递归原理:

        通过模拟一个栈,来协助存储左右区间端点值,以此来达到让快排正常进行的目的。

因此,重要的是需要对自己实现的栈精确的控制。


类比递归谋划非递归

        什么时候递归停止?

        当所有递归都返回的时候递归停止——当模拟实现的栈为空的时候停止迭代;

        递归出口的条件设置?

        当递归区间不存在的时候,递归通过return返回到上一层——当递归区间不存在的时候,直接进入下一次迭代,这里就用到了continue;

        如何准确的控制接收的左右区间的端点值?

        通过栈来模拟,需要注意栈的后进先出的特点,push的顺序和pop的顺序是相反的,比如:先push左端点,再push右端点;在top的时候,先取得的是右端点值,pop后,top再取得的是左端点值。

快速排序的非递归实现:

 


void QuickSort_NoR(vector<int>& nums,int l1,int r1)
{
	stack<int> st;
	st.push(l1);
	st.push(r1);
	while (!st.empty())
	{
		int r = st.top();
		st.pop();
		int l = st.top();
		st.pop();

		if (l >= r)
			continue;

		int left = l - 1, right = r + 1, cur = l;
		int key = GetRandomKey(nums, l, r);

		while (cur < right)
		{
			if (nums[cur] < key)
				swap(nums[cur++], nums[++left]);
			else if (nums[cur] == key)
				cur++;
			else
				swap(nums[--right], nums[cur]);
		}
		st.push(right);
		st.push(r);
		st.push(l);
		st.push(left);
	}
}

(二)归并排序 

         归并是一种算法,当归并应用在排序中,实际上的操作就是将两个有序数组合并为一个有序数组的过程。

        归并排序一般通过非递归实现,其核心思想是分治,但是递归的缺点明显,本文上半部分也说明了递归的缺点,因此非递归实现归并有很大意义。

 

 
        时间复杂度:O(N*logN)
        空间复杂度:O(N)
        稳定性:稳定

        归并的缺点:需要O(N)的空间复杂度

我们在实现归并排序的时候,需要注意的是:

(1)、需要一个N个空间的数组辅助进行排序,由于递归次数很多,在递归过程中创建数组代价太大,所以我们在全局来创建一个数组tem,作为辅助,不仅在每一层递归中都可使用,也节省了资源。

(2)、归并的主要过程通过三目运算符处的逻辑实现。

(3)、三目运算符之后,需要再将没有遍历到末尾的数据继续添加到tem末尾即可,此时归并结束。

(4)、最终不要忘了将tem内的数据拷贝回原数组。

归并排序的递归实现:

vector<int> tem(0);
void MergeSort(vector<int>& nums,int l,int r)
{
	if (l >= r)
		return;
	int mid = (r - l) / 2 + l;
	int cur1 = l, cur2 = mid + 1;
	MergeSort(nums, l, mid);
	MergeSort(nums, mid + 1, r);
	int i = 0;
	while (cur1 <= mid && cur2 <= r)
	{
		tem[i++] = nums[cur1] < nums[cur2] ?
			nums[cur1++] : nums[cur2++];
	}
	while (cur1 <= mid)tem[i++] = nums[cur1++];
	while (cur2 <= r)tem[i++] = nums[cur2++];
	for (int j = l; j <= r;++j)
	{
		nums[j] = tem[j - l];
	}
}
int main()
{
	vector<int> nums = { 99,0,7,5,44,3,78,653,90,81 };
	tem.resize(nums.size());
	Print(nums);
	MergeSort(nums,0,nums.size()-1);
	Print(nums);
	return 0;
}

归并排序的非递归

        想要实现归并的非递归,在整体上需要换一种思路。

        在局部上,归并的逻辑仍然是与递归是一致的;

我们在思考的时候要将问题逐渐拆成一个一个的小问题:

(1)、归并过程:

        将[begin1,end1],[begin2,end2]归并为一个有序的数组,算法本质和步骤和非递归的实现方法完全一致;

(2)、非递归省去了进入递归的过程,而是直接将数组分为多份,每一份有gap个:

        gap开始取1,表示一个数字就是一个区间,这个步骤是数组本身就满足的;

        gap每次*2,表示区间扩大的过程,这样一来gap逐渐扩大,就在思路上完成了归并;

        通过分析,你也知道了最重要的是对区间的左右端点的控制,也就是需要控制好区间的偏移和越界问题。

细节处理:

(1)区间的偏移:

        通过一个循环,循环变量为k,两个区间的开始位置是由k来决定的,用k来控制区间的偏移:由于每次是归并两个数组,所以每次归并完成后,k+=2*gap:

演示(以gap=2为例):

偏移后:(k+=2*gap

(2)区间的越界:

我们上图举的例子是一个特殊情况,数组元素个数刚好够归并需要的元素,如果元素有9个而不是8个,这就需要考虑区间的越界问题了。

当数组的长度更加一般时,会出现区间的越界问题,对于每一个区间端点:

        begin1:由k决定(k< n,所以不可能越界)

        end1:begin1+gap-1,有可能越界;如果越界,数组个数只有一个,则不再归并。

        begin2:begin1+gap,可能越界;如果越界,数组个数只有一个,则不再归并。

        end2:begin1+2*gap-1;可能越界;如果越界,数组个数有两个,修正end2的位置后再归并。

归并排序的非递归实现:


void MergeSort_NoR(vector<int>& nums, int l, int r)
{
	int n = nums.size();
	int gap = 1;
	while (gap < n)
	{
		for (int k = 0; k < n; k += 2*gap)
		{
			// 对两组进行归并  [beign1,end1]  [begin2,end2] 
			// 组内宽度gap 
			int begin1 = k, end1 = begin1 + gap - 1;
			int begin2 = end1 + 1, end2 = begin2 + gap - 1;
			if (end1 >= n || begin2 >= n)
				break;
			if (end2 >= n)
				end2 = n-1;
			int i = k;
			while (begin1 <= end1 && begin2 <= end2)
				tem[i++] = nums[begin1] < nums[begin2] ?nums[begin1++] : nums[begin2++];
			while (begin1 <= end1) tem[i++] = nums[begin1++];
			while (begin2 <= end2) tem[i++] = nums[begin2++];
			for (int j = k; j <= end2; ++j)
				nums[j] = tem[j];
		}
		gap *= 2;
	}
}

完~

未经作者同意禁止转载 

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

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

相关文章

antdesignvue对话框用户可移动并缩放

原贴 轻松搞定Ant Design Modal对话框拖拽缩放 - ByteZoneX社区https://www.bytezonex.com/archives/IFRuoJhd.html Ant Design 模态对话框&#xff1a;实现拖拽缩放功能 **子 Ant Design 是一个流行的前端 UI 框架&#xff0c;提供了一系列实用的组件&#xff0c;包括模态对…

数据结构——二叉树相关题目

1.寻找二叉树中数值为x的节点 //寻找二叉树中数值为x的节点 BTNode* TreeFind(BTNode* root, BTDataType x)//传过来二叉树的地址和根的地址&#xff0c;以及需要查找的数据 {if (root Null){return Null;}//首先需要先判断这个树是否为空&#xff0c;如果为空直接返回空if (…

Linux下fcitx框架输入法输入中文标点时为半角(英文)标点符号的解决

目录 引入解决1.打开fcitx设置2.打开全局配置3. 随便找个可以输入地方敲下快捷键 总结 本文由Jzwalliser原创&#xff0c;发布在CSDN平台上&#xff0c;遵循CC 4.0 BY-SA协议。 因此&#xff0c;若需转载/引用本文&#xff0c;请注明作者并附原文链接&#xff0c;且禁止删除/修…

RNN 交叉熵

RNN善于处理时序 序列数据 简单RNN 展开就是 LSTM 遗忘门f_t决定上期记忆保留多少 隐藏层 在神经网络中&#xff0c;隐藏层指的是除了输入层和输出层之外的层&#xff0c;它们的输出不会直接用于网络的最终输出&#xff0c;而是作为中间步骤用于提取和转换数据。因此&#x…

构件软件开发-系统架构师(二十五)

1、一路和二路公交车都将在10分钟随机到达同一车站&#xff0c;他们相隔四分钟的概率是&#xff08;&#xff09;。 A0.36 B0.48 C0.64 D0.76 解析&#xff1a; 如果把x为1路&#xff0c;y为2路&#xff0c;则x-y4&#xff0c;y-x4 所以可以得到坐标 (4,0)(10,6)(0,4)(6,1…

(ECCV,2022)Mask-CLIP:从CLIP中提取自由密集标签

文章目录 Extract Free Dense Labels from CLIP相关资料摘要引言方法Mask-CLIPMask-CLIP 实验 Extract Free Dense Labels from CLIP 相关资料 代码&#xff1a;https://github.com/chongzhou96/MaskCLIP 论文&#xff1a;https://arxiv.org/abs/2112.01071 摘要 对比语言-…

PyVideoTrans:一款功能全面的视频翻译配音工具!【送源码】

PyVideoTrans是一款功能全面的视频翻译配音工具&#xff0c;专为视频内容创作者设计。它能够将视频中的语言翻译成另一种语言&#xff0c;并自动生成与之匹配的字幕和配音。支持多种语言&#xff0c;包括但不限于中文&#xff08;简繁体&#xff09;、英语、韩语、日语、俄语、…

算法010:无重复字符的最长子串

无重复字符的最长子串. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/longest-substring-without-repeating-characters/ 使用的算法&#xff1a;滑动窗口 在这个…

apk反编译修改教程系列-----修改apk 解除软件限制功能 实例操作步骤解析_3【二十二】

在前面的几期博文中有过解析去除apk中功能权限的反编译步骤。另外在以往博文中也列举了修改apk中选项功能权限的操作方法。今天以另外一款apk作为演示修改反编译去除软件功能限制的步骤。兴趣的友友可以参考其中的修改过程。 课程的目的是了解apk中各个文件的具体作用以及简单…

C-11 三角剖分的调研

C-11 三角剖分算法 三角剖分就是将输入的多边形&#xff0c;分割成一系列互不重叠的三角形&#xff0c;其重要性就在这不多赘述。这个是一个别人总结的链接&#xff1a;http://vterrain.org/Implementation/Libs/triangulate.html 图片链接&#xff1a;http://www-cgrl.cs.m…

STM32点灯闪烁

stm32c8t6引脚图 开发板引脚图 GPIO端口的每个位可以由软件分别配置成 多种模式。 ─ 输入浮空 ─ 输入上拉 ─ 输入下拉 ─ 模拟输入 ─ 开漏输出 ─ 推挽式输出 ─ 推挽式复用功能 ─ 开漏复用功能 配置GPIO端口步骤&#xff1a;开启时钟->使用结构体设置输出模式…

Android项目中,查看项目依赖树的多种方式

1.使用预设的Task来进行查看 1.1 命令行 查看某个模块的所有依赖树&#xff1a; gradlew [模块名称]:dependencies 例如&#xff1a;gradlew app:dependencies查看某个模块的某功能的依赖树&#xff1a; gradlew [模块名称]:dependencies --configuration [功能名称] 例如&…

国内采用docker部署open-metadata

背景 最近看看开源的元数据管理项目&#xff0c;比较出名点的有open-metadata、datahub、OpenLineage、atlas。 open-metadata有1千多的贡献者&#xff0c;4.8K的stars&#xff0c;社区现在也比较活跃&#xff0c;支持的数据库类型还蛮多&#xff0c;基本市面上常见的都有支持…

C++ 函数高级——函数的占位参数

C中函数的形参列表里可以有占位参数&#xff0c;用来做占位&#xff0c;调用函数时必须填补改位置 语法&#xff1a; 返回值类型 函数名&#xff08;数据类型&#xff09;{ } 在现阶段函数的占位参数存在意义不大&#xff0c;但是后面的课程中会用到该技术 示例&#xff1a;…

离线开发(VSCode、Chrome、Element)

一、VSCode 扩展 使用能联网的电脑 A&#xff0c;在VSCode官网下载安装包 使用能联网的电脑 A&#xff0c;从扩展下载vsix扩展文件 将VSCode安装包和vsix扩展文件通过手段&#xff08;u盘&#xff0c;刻盘 等&#xff09;导入到不能联网的离线电脑 B 中 在离线电脑 B 中安装…

Gitlab Fork Workflow(协作工作流)

Gitlab Fork WorkFlow&#xff08;协作工作流&#xff09; Fork WorkFlow用于团队间的协作开发。在开发过程中&#xff0c;我们都需要将最新修改的代码合并到代码库上&#xff0c;在代码合并之前&#xff0c;为了保证代码符合上传要求&#xff08;符合需求、代码规范等&#xf…

PageHelper分页查询遇到的小问题

如果我们是这样子直接查询 pagehelper会拼接导我们的sql语句之后 这样子我们搜索出来的list&#xff0c;就是里面参杂了PageHelper的东西 所以我们可以直接转成我们的Page类型 但是如果我们搜索出来的是List<Blog>&#xff0c;我有些信息不想返回给前端&#xff0c;所以…

运维系列.Nginx中使用HTTP压缩功能

运维专题 Nginx中使用HTTP压缩功能 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.csdn.net/qq_28550…

系统级别的原生弹窗窗口

<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>原生的弹出窗口dialog</title><style>…

【密码学】信息安全五大属性

信息安全的五大属性&#xff0c;通常被称为CIA三元组加上两个额外的属性&#xff0c;他们是确保信息在存储、处理和传输过程中保持安全、完整和可用的关键要素。这些属性共同构成了信息安全的基础框架。 一、信息安全五大属性 我先给出一个直观的列表&#xff0c;方面大家后续…