快排非递归 归并排序

news2024/11/25 6:47:13

递归深度太深会栈溢出

程序是对的,但是递归个10000层就是栈溢出

int fun(int n)
{
	if (n <= 1)
	{
		return n;
	}
	return fun(n - 1) + n;
}

在这里插入图片描述
所以需要非递归来搞快排和归并,在效率方面没什么影响,只是解决递归深度太深的栈溢出问题
有的能直接改,例如斐波那契,知道第一个第二个的迭代,有的需要辅助,直接改不了

斐波那契数列非递归

// 计算Fibonacci的空间复杂度?
// 返回斐波那契数列的前n项
long long* Fibonacci(size_t n)
{
 if(n==0)
 return NULL;
 
 long long * fibArray = (long long *)malloc((n+1) * sizeof(long long));
 fibArray[0] = 0;
 fibArray[1] = 1;
 for (int i = 2; i <= n ; ++i)
 {
 fibArray[i] = fibArray[i - 1] + fibArray [i - 2];
 }
 return fibArray;
}

快排 非递归

思路:看递归栈帧保存了什么,就是两个区间的下标,用栈来辅助改循环

栈没法一下入两个,非要搞可以弄一个结构体保存两个区间的下标,但那样太麻烦,简单点就是可以每次入一个,入两次栈,也要注意先入右区间下标再入左区间下标

注意的就是区间入栈的顺序,先入右子树的区间,再入左子树,这样出栈时保证先序,并且正确模拟递归过程嘛
在这里插入图片描述
区间[0,9]单趟排选出一个key,就可以将[0,9]出栈,分出[begin, keyi-1] keyi [keyi+1, end]左右区间,左右区间如果只有一个值,或者区间不存在,就不需要再入栈了,不然就入栈,入栈顺序是右边区间先入栈,再左边入栈
在这里插入图片描述
[0,9]出栈带入[0,4][6,9]继续出栈[0,4],选key单趟排,再入栈左右区间
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
此时[0,1]出栈单趟选key后左右子区间不符合入栈条件,则继续出[3,4]
在这里插入图片描述
[3,4]处理完了就到了[6,9],再继续单趟,分左右区间…
如此循环下去,直到栈为空就结束

//非递归 效率和递归 无区别  只是解决了递归深度过高栈溢出
void QuickSortNonR(int* a, int left, int right)
{
	ST st;
	StackInit(&st);
	StackPush(&st, right);
	StackPush(&st, left);
	while (!StackEmpty(&st))
	{
		int begin = StackTop(&st);
		StackPop(&st);
		int end = StackTop(&st);
		StackPop(&st);

		int keyi = PartSort3(a, begin, end);
		//[begin, keyi-1] keyi [keyi+1, end]
		if (keyi + 1 < end)
		{
			StackPush(&st, end);
			StackPush(&st, keyi + 1);
			
		}
		if (begin < keyi-1)
		{
			StackPush(&st, keyi-1);
			StackPush(&st, begin);
		}
		
	}
	StackDestroy(&st);
}

归并排序

时间复杂度

在这里插入图片描述
每层归并都是N,一共有logN层,就是O(NlogN)

思想

两个有序区间归并:
依次比较,小的尾插到新空间
前提是他们两个区间 都 有序
没序怎么办,平分变成子问题,继续让左区间有序,右区间有序
平分区间只有一个数,可以认为此时有序
开辟一个临时空间,将左区间1个数,右区间1个数归并,再拷贝回原数组
不开辟导致数据覆盖问题

左区间有序右区间有序再归并,这是后序

涉及开辟临时空间就需要在写一个子函数,不然每次递归调用自己都开辟空间

如何平分取中间下标也挺有意思

公式为 mid = (left + right) / 2。其中,left为左边界下标,right为右边界下标,mid为中间下标。
mid是左右边界的平均值,两个数加起来取个平均值嘛整形直接中间值,三数取中同理

结束条件是否有不存在的区间,还是只有一个数的情况呢?

根据递归图看出 只有[0,0][1,1]这种只有一个数的区间情况,并不存在不存在的区间

1.递归

递归过程-后序-先进入左子树,右子树,归并

在这里插入图片描述
在这里插入图片描述

void _MergeSort(int* a, int left, int right,int* tmp)
{
	if (left >= right)//不会有不存在的区间,这样写肯定没错
		return;

	int mid = (right + left) / 2;//左边界和右边界的平均值,整形直接中间值
		//[left mid] [mid+1 right],子区间递归排序
	_MergeSort(a, left, mid, tmp);
	_MergeSort(a, mid+1, right, tmp);

	int i = left;
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	//归并
	//[begin1 end1] [begin2 end2]//涉及left right 建议设置局部变量,不直接使用left right
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
	memcpy(a+left,tmp+left,sizeof(int)*(right-left+1));
}
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	_MergeSort(a, 0, n - 1,tmp);

	free(tmp);
}

注意i = left 和 memcpy(a+left,tmp+left,sizeof(int)*(right-left+1));
归并的区间在右边就不是从0 开始的,而是从left开始的,归并到tmp中也要归并到从left开始的位置,不可让i =0 会导致归并临时空间tmp都从0开始,调试时也不清晰。

2.非递归

归并排序 无法用栈保存区间 来搞非递归
因为归并后序 和快排栈辅助非递归的前序 顺序不一样,一开始的[0,9]上来就出栈了,归并排序后回到[0,9]你还得用[0,9]来归并,如果强行用栈来搞也不是不行,但是很困难,不建议

我们知道归并最后一层都是一个一个归并 再 两两归并,四四归并…
那就像类似斐波那契数列知道第一第二求第三
直接写循环控制,用gap来控制 gap = 1 gap =2 gap =4来11归,22归,44归

gap是归并过程中,每组数据个数
控制两组有序数组归并
在这里插入图片描述
如果数据个数不能如此平分达到11 22 44归并 必然会造成越界
又或者说数据个数不是2^n倍,但是先把符合的写出来

先弄出符合倍数的{ 10,6,7,1,3,9,4,2 }
在这里插入图片描述

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail\n");
		return;
	}

	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			//[begin1 end1] [begin2 end2]
			printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);

			int j = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}

			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}

			// 拷贝
		}
		printf("\n");
		gap *= 2;
	}

	free(tmp);
}

不要过于依赖调试,调试适合看一些简单的情况,如果有多种组合情况,可以用printf()函数打印你想看的值
为了更好的看分组情况加了//[begin1 end1] [begin2 end2] printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);在控制这四个下标下面
符合2的n次方倍的分成功了,但是不符合的会越界,导致报错崩溃,但也没关系,这个printf()输出仍然能够输出,方便看分组情况
比如下面
不符合的并不是奇数偶数的问题,而是2的n次方倍的问题

当数据个数是偶数12,仍然越界
在这里插入图片描述

同样是偶数 但是不够分 达不到2^4 = 16 gap = 4的时候不够分,造成越界

那越界怎么办呢

更古不变的–复杂问题分解为简单问题:分类处理
刚刚12个数据的例子不太好,只是说明不是奇数偶数的问题
现在我们用9个数据,覆盖越界情况更广
在这里插入图片描述

画红线的都是越界的区间

将复杂问题分解为简单问题:分类处理
三种情况
在这里插入图片描述
在这里插入图片描述
begin1 = i i < n 所以 begin1 一定不会越界

end1 都越界了 begin2 end2肯定也越界了,两个区间没法归并了,就不归了
end1没越界,begin2越界,end2也肯定越界,也没法归并了
end1,begin2没有越界,但是end2越界了,还能修正一下end2 = n-1,继续归并
其实第三步挺重要的。

不参与归并是如何有序的呢?

虽然不参与归并,但是最后平分归并时经过修正后区间begin2没越界 end2 = n-1 也没越界
参与归并数据只不过是少了也就是递归最后[b1 e1] [b2 e2] n/2 n/2 最后一次归并平分成2个有序数组进行归并
不管有几个这种二分越界不参与归并的区间,他们最后一定有中点,那么e1 b2不会越界,e2越界就修正完成非递归的最终归并排序

这个图可以感受最后一次归并数据达不到16个 左边[0,7][8,8]右边修正end2 = n -1 同样也归并排序完成
在这里插入图片描述

并且gap<=n/2 也就是gap不会超过半数数据,举例N=8来说,最后是4 4 归并,一组数据个数gap=4,所以他们一定有中点,并且达到平分那么e1 b2肯定不会越界

临时数组拷贝到原数组不能归并后一把梭哈

在这里插入图片描述

因为你在归并后,可以归并的2段区间没问题,不归并的区间如果没拷贝到tmp中,你又要一把梭哈拷贝回原数组,那么就会导致随机值,所以就得不归并的也得拷贝到tmp中再考回原数组。

不如归并一部分拷贝一部分

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	int gap = 1;
	while (gap < n)
	{
		//int j = 0;
		for (int i = 0; i < n; i += (2 * gap))
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			//[begin1 end1] [begin2 end2]
			//printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);

			if (end1 >= n || begin2 >= n)//if (begin2 >= n )
			{
				break;
			}
			if (end2 >= n )
			{
				end2 = n - 1;
			}
			printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);
			int j = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])//改稳定 <= 相同数保持前后关系
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}
			// 归并一部分拷贝一部分
			memcpy(a+i, tmp+i, sizeof(int) * (end2-i+1));//不能end2-begin1+1 因为begin1++ 一直在变
		}
		printf("\n");
		gap *= 2;
	}
	free(tmp);
}

最重要的是区间下标的规律

在这里插入图片描述

int gap = 1;
	while (gap < n)
	{
		//int j = 0;
		for (int i = 0; i < n; i += (2 * gap))
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			//[begin1 end1] [begin2 end2]
			//printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);

begin1 = i 就带进去看 i += (2 * gap) 跳过这两个组 到下两个组
再看 begin2 = i + gap 跳到下一个区间开始
end1 = i + gap - 1就是begin2 -1
end2 = i + 2 * gap - 1 是begin2 + gap - 1 看规律看出来的
这块结合图中gap =2 和gap=1来看规律

总的来说就是控制好这个下标,并且不要一把梭哈

计数排序

总结:计数排序适合范围集中,且范围不大的整形数组排序。不适合范围分散或者非整形的排序,如:字符串、浮点数等

  1. 绝对映射
    在这里插入图片描述
    如果范围很大 100~200 如果使用绝对映射,前面的空间全部浪费,所以需要相对映射

  2. 相对映射

在这里插入图片描述
需要注意的是,相对映射 是用a[ i ]-min 来确定映射计数的,并且可以处理负数的排序,但如果范围过大,那么计数排序不再适合。
range = max-min+1是countA空间大小

countA数组一开始都初始化为0

处理负数
在这里插入图片描述

完整代码

如果range和N相近就很快

时间复杂度 O(N+range)
空间复杂度 O(range)

多遍历了一次找最大最小 但是常数次N根据时间复杂度规则认为是N

// 计数排序 相对映射可处理负数
//时间复杂度 O(N+range)
//空间复杂度 O(range)
void CountSort(int* a, int n)
{
	
	int min = a[0], max = a[0];
	for (int i = 1; i < n; i++)
	{
		if (a[i] < min)
		{
			min = a[i];
		}
		if (a[i] > max)
		{
			max = a[i];
		}
	}
	int range = max - min + 1;
	int* countA = (int*)calloc(range, sizeof(int));
	if (countA == NULL)
	{
		perror("calloc fail");
		return;
	}
	for (int i = 0; i < n; i++)
	{
		countA[a[i] - min]++;
	}
	int j = 0;
	for (int i = 0; i < range; i++)
	{
		while (countA[i]--)
		{
			a[j++] = i + min;
		}
	}
	
	free(countA);
}

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

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

相关文章

2023年Android开发现状~

随着Android 开发行业的快速发展&#xff0c;市场需求也在不断提升&#xff0c;导致低端Android 开发市场就业大环境不好、行业趋势下滑&#xff0c;使得不少初中级的Android开发开始失业&#xff0c;找不到工作。 为什么这么说&#xff1f; 现在不像2012年——2018年的这段期…

性能调优通用逻辑

调优准备 定目标&#xff1a;根据线上预估访问量评估单场景QPS及混合场景QPS&#xff0c;和对应的RT值 环境区分&#xff1a; 测试环境单机压测进行链路问题排查问题&#xff0c;通常需要把单机打到CPU到100%&#xff0c;如果CPU到不了100%且请求已经各种超时或RT高于目标值…

Voting_Averaging算法预测银行客户流失率

Voting_Averaging算法预测银行客户流失率 描述 为了防止银行的客户流失&#xff0c;通过数据分析&#xff0c;识别并可视化哪些因素导致了客户流失&#xff0c;并通过建立一个预测模型&#xff0c;识别客户是否会流失&#xff0c;流失的概率有多大。以便银行的客户服务部门更…

【大型互联网应用轻量级架构实战の一】轻量级架构概述

1、轻量级架构概述 1.1.1、前言 当下&#xff0c;互联网应用呈高速发展的趋势&#xff0c;要想不被市场淘汰&#xff0c;就必须与时间赛跑&#xff0c;故而&#xff0c;快 就成了所有互联网公司产品的特征&#xff0c;只有率先推出产品&#xff0c;才能获取主动权。 1.1.2、…

大模型时代下的paper生存= =

第一类&#xff1a;PEFT类论文 &#xff08;我还挺喜欢的&#xff0c;不知道自己什么时候可以搞出这种工作 &#xff08;为什么中英文穿插&#xff0c;利于自己写论文&#xff1a;&#xff09; COMPOSITIONAL P ROMPT T UNING WITH M OTIONC UES FOR O PEN - VOCABULARY V ID…

构建数字时代下的必要防线 消除医疗行业数据安全建设“盲区”

4月7日&#xff0c;由厦门市卫生健康信息学会和厦门大学附属第一医院、厦门服云信息科技有限公司举办的医疗数据安全学术研讨会顺利开展。 作为国内云原生安全领导厂商&#xff0c;安全狗除了协助举办此次活动&#xff0c;还以数据安全治理专家的身份参与演讲分享。 厦门服云…

全网最详细,Jmeter性能测试-性能进阶, 无界面命令运行CLI模式(六)

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 如果使用jmeter.bat…

代码随想录算法训练营第四十一天-动态规划3|343. 整数拆分 ,96.不同的二叉搜索树

343整数拆分&#xff0c;有两种解法&#xff0c;一种是数学的方法&#xff0c;利用当f>4时&#xff0c;2*&#xff08;f - 2&#xff09;2f - 4 > f的性质&#xff0c;将所有的因子都拆成3&#xff0c;最后的余数再乘进去。另外一种是动态规划&#xff0c;把前面的数拆了…

算法---文件的最长绝对路径

题目 假设有一个同时存储文件和目录的文件系统。下图展示了文件系统的一个示例&#xff1a; 这里将 dir 作为根目录中的唯一目录。dir 包含两个子目录 subdir1 和 subdir2 。subdir1 包含文件 file1.ext 和子目录 subsubdir1&#xff1b;subdir2 包含子目录 subsubdir2&…

PHP快速入门11-文件操作,附写入文件、文件重命名等20个高频使用案例

文章目录前言一、文件操作介绍二、 20个文件操作的例子2.1 打开文件并写入数据2.2 读取文件中的一行数据2.3 读取文件中的一个字符2.4 读取整个文件内容2.5 向文件写入内容2.6 将整个文件读入一个数组中2.7 删除文件2.8 重命名文件2.9 复制文件2.10 判断是否为文件2.11 判断是否…

【致敬未来的攻城狮计划】RA2E1环境搭建点亮发光二极管

开启攻城狮的成长之旅&#xff01;这是我参与的由 CSDN博客专家 架构师李肯和 瑞萨MCU &#xff08;瑞萨电子 (Renesas Electronics Corporation) &#xff09; 联合发起的「 致敬未来的攻城狮计划 」的第 2 天&#xff0c;点击查看活动计划详情 &#xff01; 开发环境搭建 开…

React styled-components(三)—— 高级特性

styled-components 高级特性样式继承嵌套设置主题样式继承 新建 Demo.js 文件&#xff1a; import React, { Component } from react import styled from styled-components;const CustomStyle styled.divp { color: red;} const ContextBox styled(CustomStyle)width:…

Tableau-创建环状图:使用2个饼图

步骤 1&#xff1a;创建饼图 在“标记”下面&#xff0c;选择“饼图”标记类型。将分类拖到颜色。将任务总数拖到角度。再拖动一次任务总数&#xff0c;放到标签。根据需要调整饼图大小。 步骤 2&#xff1a;切换到双轴图表 右键点击任意一个字段&#xff0c;创建-->计算…

3年功能测试无情被裁,3个月学习自动化测试重新开始........

前言 不知不觉在软件测试行业工作了3年之久&#xff0c;虽然说我是主做的功能测试&#xff0c;但是我也一直是兢兢业业的呀&#xff0c;不曾想去年7月份无情被辞的消息让我感到一阵沉重。我曾经一直坚信自己的技能和经验足以支撑我在这个领域的未来&#xff0c;但现实却告诉我&…

考研数据结构——表达式的转换用栈实现表达式的概述

一、用表达式实现中缀表达式转后缀表达式 把括号里的符号移到括号外 二、用栈实现中缀表达式转后缀表达式 1、遇到字母写下来 2、遇到符号加入栈中 3、遇到成对括号才出栈 4、当前读取运算符要小于等于栈顶运算符优先级则出栈 从左向右扫描 三、表达式方法实现中缀表达式转…

Shader Graph9-世界空间、物体空间、相机空间、切线空间

一、World Space世界空间 在下用的Blender软件&#xff0c;新建了一个平面&#xff0c;中间的黄色小圆点表示的世界空间的原点&#xff0c;在世界空间的物体的位置&#xff0c;都是相对于这个原点来说的&#xff0c;红色箭头表示x轴&#xff0c;绿色箭头表示y轴&#xff0c;蓝…

算法 二叉树2 || 层序遍历 226.翻转二叉树 101. 对称二叉树 104.二叉树的最大深度 111 二叉树的最小深度 222.完全二叉树的节点个数

102 二叉树的层序遍历 队列先进先出&#xff0c;符合一层一层遍历的逻辑&#xff0c;而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。 而这种层序遍历方式就是图论中的广度优先遍历&#xff0c;只不过我们应用在二叉树上。 迭代法&#xff1a; /*** Definition for …

springboot和vue写个小项目

遵循“约定优于配置”的原则&#xff0c;只需要很少的配置或使用默认的配置。 能够使用内嵌的Tomcat、Jetty服务器&#xff0c;不需要部署war文件。 提供定制化的启动器Starters,简化Maveni配置&#xff0c;开箱即用。 纯&#xff09;java配置&#xff0c;没有代码生成&#xf…

4月11日,每天30秒,昨夜今晨一览无余/我国首条“西氢东送”管道纳入国家规划/国际机构:中国经济蓬勃复苏,展现广阔投资

-> 昨天的世界(点击进入) <- http://mp.weixin.qq.com/s?__bizMzU4MzQ4Mzk0Nw&mid2247488724&idx1&snd19817d3c7fd9aeb521052090eb439e0&chksmfda90390cade8a864965c805b86acd253f5d74368ae8767b7f00b80d4af5bcc42feef7635641&scene21#wechat_redir…

Qt扫盲-Qt图表类综述

Qt支持图表类综述一、概述二、图表类型1. 折线图和柱状图2. 面积图和散点图3. 柱状图4. 饼图5. 盒须图6. 烛台图表7. 星座图图表三、坐标轴 Axes四、图例五、与图表交互1. 动态绘制数据2. 深入数据3. 缩放和滚动4. 鼠标悬停六、主题一、概述 Qt Charts支持创建时尚的、交互式的…