【数据结构】归并排序 —— 递归及非递归解决归并排序

news2024/11/23 16:29:48

归并排序

  • 一、归并排序
    • 1、归并排序的思想
    • 2、归并排序代码实现(递归)
      • <1> 归并排序的递归区间
      • <2> 归并排序的稳定性
      • <3> 拷贝
    • 3、归并排序代码实现(非递归)
      • <1> 循环区间溢出问题
  • 二、总结

一、归并排序

1、归并排序的思想

假设我们要排一个升序的数组
先看下面的动图
在这里插入图片描述

假设有两组升序数据
我们设置 ptr1 和 ptr2 来代表数组下标
将两数据进行比较,将小的填入一个新的数组
当两个数组中的数都进入新的数组
接下来我们只需要将该数组复制到原数组中
就完成了排序

如果我们要排一个升序数组
就要将它分成两个小的升序数组

若想得到两个小的升序数组
就要将每一个小的升序数组分成两个更小的升序数组

。。。。。。

————————————————————————————
于是我们想到了用递归的方式
一层层递归
直到只剩下一个不用数据,然后返回进行排序

2、归并排序代码实现(递归)

void _MergeSort(int* a, int* tmp, int left, int right)
{
	//返回停止条件
	if (left == right)
	{
		return;
	}

	//取中间值
	int mid = (left + right) / 2;
	
	//递归
	//[left, mid][mid + 1, right]
	//递归左边
	_MergeSort(a, tmp, left, mid);
	//递归右边
	_MergeSort(a, tmp, mid + 1, right);

	//进行单次归并
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;

	//记录起始位置
	int begin = left, end = right;

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


//进行拷贝
    memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));

}

//归并排序
void MergeSort(int* a, int left, int right)
{
	//开辟一个复制数组
	int* tmp = (int*)malloc(sizeof(int) * (right - left + 1));
	if (tmp == NULL)
	{
		perror("malloc fail:");
		exit(1);
	}

	//进行归并排序
	_MergeSort(a, tmp, left, right);

	//数组销毁
	free(tmp);
	tmp = NULL;
}

<1> 归并排序的递归区间

递归区间1
[left, mid][mid + 1, right]
递归区间2
[left, mid - 1][mid, right]

看这两个区间有所不同
上面代码我采用的是区间1
而没有采用区间2
因为区间2会出现死循环,导致栈溢出
看下面的图:

区间1:
在这里插入图片描述
区间2:
在这里插入图片描述
同样的 mid 但是当递归至只剩下两个时,区间2就会陷入死循环

<2> 归并排序的稳定性

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

在比较 begin1 和 begin2 时我们用的是 " <= "
这样前面的数就会在前面
在这里插入图片描述
在图上我们发现我们在面对相同的数时,我们会先将 ptr1 中的数填入数组,那么相同数的相对位置没有发生改变,我们说归并排序是稳定的

<3> 拷贝

//进行拷贝
memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));

我们是边排序边拷贝的
如果等到最后拷贝会有出问题的风险
memcpy函数

3、归并排序代码实现(非递归)

如果是用非递归的方式
我们可以用循环
设置gap变量,gap 每次循环结束 gap *= 2
完成整个数组的归并
在这里插入图片描述

代码实现:

//归并排序(非递归)
void MergeSortNonR(int* a, int left, int right)
{
	//开辟复制数组
	int* tmp = (int*)malloc(sizeof(int) * (right - left + 1));
	if (tmp == NULL)
	{
		perror("malloc fail:");
		exit(1);
	}

	int gap = 1;
	//进行单次排序
	while (gap < right + 1)
	{
		for (int i = 0; i < right + 1; i += gap * 2)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + gap * 2 - 1;
			int begin = i;
			printf("[%d, %d][%d, %d]", begin1, end1, begin2, end2);
			
			//当begin2超过数组区间
			if (begin2 > right)
			{
				break;
			}
			//当只有end2超过区间
			if (end2 > right)
			{
				end2 = right;
			}

			//单次归并
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[begin++] = a[begin1++];
				}
				else
				{
					tmp[begin++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[begin++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[begin++] = a[begin2++];
			}
			//将数组进行复制
			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));
		}
		printf("\n\n");
		gap *= 2;
	}

	//销毁复制数组
	free(tmp);
	tmp = NULL;
}

<1> 循环区间溢出问题

当begin2超过数组区间
if (begin2 > right)
{
	break;
}
当只有end2超过区间
if (end2 > right)
{
	end2 = right;
}

在循环过程中,除了 begin1 不会超出数组区间
其他三个都有可能超出区间
我们将所有区间都打印出来
在这里插入图片描述
我们发现超出区间总共分为三种情况
在这里插入图片描述
当 begin2 和 end1 超出区间时:
说明后面的整个区间都超出了数组的范围
那就不用归并

当 end2 超出区间时:
说明后面的一部分数没有超出区间
可以将 end2 改为区间的最大值,继续进行归并

二、总结

归并排序的时间复杂度为 O(N * logN)
是比较快的排序
但是归并排序有它的缺陷
它的空间复杂度为 O(N)
排序需要开辟空间
当排序数量过大时有风险

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

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

相关文章

Java技术复习提升 10异常

10 异常 10.1异常介绍及分类 异常捕获 选中后alttabt->选中try-catch 异常就是程序执行中不正常的情况 注意语法和逻辑错误并不是异常 异常分类有两种 error和exception error是错误 虚拟机无法解决的严重问题 exception是其他因为编程错误或者外在因素导致的一般性的问…

transformer.js(三):底层架构及性能优化指南

Transformer.js 是一个轻量级、功能强大的 JavaScript 库&#xff0c;专注于在浏览器中运行 Transformer 模型&#xff0c;为前端开发者提供了高效实现自然语言处理&#xff08;NLP&#xff09;任务的能力。本文将详细解析 Transformer.js 的底层架构&#xff0c;并提供实用的性…

HCIA笔记3--TCP-UDP-交换机工作原理

1. tcp协议 可靠的连接 1.1 报文格式 1.2 三次握手 1.3 四次挥手 为什么TIME_WAIT需要2MSL的等待时间&#xff1f; &#xff08;a&#xff09; 为了实现可靠的关闭 &#xff08;b&#xff09;为了让过期的报文在网络上消失 对于(a), 假设host发给server的last ack丢了。 ser…

[Redis#2] 定义 | 使用场景 | 安装教程 | 快!

目录 1. 定义 In-memory data structures 在内存中存储数据 2. 优点&#xff01;快 Programmability 可编程性 Extensibility 扩展性 Persistence 持久化 Clustering 分布式集群 High availability 高可用性 ⭕快速访问的实现 3. 使用场景 1.Real-time data store …

Dubbo源码解析-服务调用(七)

一、服务调用流程 服务在订阅过程中&#xff0c;把notify 过来的urls 都转成了invoker&#xff0c;不知道大家是否还记得前面的rpc 过程&#xff0c;protocol也是在服务端和消费端各连接子一个invoker&#xff0c;如下图&#xff1a; 这张图主要展示rpc 主流程&#xff0c;消费…

Postman之newman

系列文章目录 1.Postman之安装及汉化基本使用介绍 2.Postman之变量操作 3.Postman之数据提取 4.Postman之pm.test断言操作 5.Postman之newman Postman之newman 1.基础环境node安装1.1.配置环境变量1.2.安装newman和html报告组件 2.newman运行 newman可以理解为&#xff0c;没有…

shell脚本(五)

声明&#xff01; 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&#…

人口老龄化社区服务|基于springboot+vue的人口老龄化社区服务与管理平台(源码+数据库+文档)

目录 基于springbootvue的人口老龄化社区服务与管理平台 一、前言 二、系统设计 三、系统功能设计 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设布道师&#xff0c;阿里云…

初识WGCLOUD - 监测磁盘空间还能使用多久

WGCLOUD是一款免费开源的运维监控软件&#xff0c;性能优秀&#xff0c;部署简单&#xff0c;轻巧使用&#xff0c;支持大部分的Linux和Windows、安卓、MacOS等平台安装部署 最近发布的新版本 v3.5.4&#xff0c;WGCLOUD新增了可以自动计算每个磁盘剩余空间的可使用天数&#x…

Linux各种并发服务器优缺点

本文旨在介绍针对“无并发C/S模型”改进的方法总结以及各种改进方法的优缺点&#xff0c;具体函数的实现并不介绍。 1. 无并发C/S模型 创建服务器流程分析&#xff1a; socket()创建服务器的监听套接字bind()将服务器给服务器的监听套接字绑定IP地址和Port端口号listen()设置…

【PPTist】添加PPT模版

前言&#xff1a;这篇文章来探索一下如何应用其他的PPT模版&#xff0c;给一个下拉菜单&#xff0c;列出几个项目中内置的模版 PPT模版数据 &#xff08;一&#xff09;增加菜单项 首先在下面这个菜单中增加一个“切换模版”的菜单项&#xff0c;点击之后在弹出框中显示所有的…

【Python · PyTorch】卷积神经网络 CNN(LeNet-5网络)

【Python PyTorch】卷积神经网络 CNN&#xff08;LeNet-5网络&#xff09; 1. LeNet-5网络※ LeNet-5网络结构 2. 读取数据2.1 Torchvision读取数据2.2 MNIST & FashionMNIST 下载解包读取数据 2. Mnist※ 训练 LeNet5 预测分类 3. EMnist※ 训练 LeNet5 预测分类 4. Fash…

如何用通义灵码助力项目开发 | OceanBase obdiag 项目共建实践

本文来自 obdiag 项目共建的用户分享 一、背景 我的数据库探索之旅始于OceanBase。作为一位满怀好奇心的DBA&#xff0c;我内心始终怀揣着对数据库内部运作机制的无尽向往。开源如同一把钥匙&#xff0c;为我们这些求知欲旺盛的“好奇猫”解锁了通往新知的神秘大门。在众多分布…

如何给 Apache 新站点目录配置 SELinux ?

在 web 服务器管理领域&#xff0c;确保服务器环境的安全性至关重要。SELinux (Security-Enhanced Linux) 是保护 Linux 服务器最有效的工具之一&#xff0c;它是一种强制访问控制 (MAC mandatory access control) 安全机制。当使用最流行的 web 服务器 Apache 提供 web 内容时…

【电子物证培训】龙信助力三明市公安局电子物证取证竞赛

文章关键词&#xff1a;电子数据取证、手机取证、电子物证、介质取证 为了进一步提升福建省三明市公安机关刑侦部门在电子物证领域的专业技能&#xff0c;强化队伍实战能力&#xff0c;三明市公安机关刑侦部门举办电子物证专业技能竞赛&#xff0c;龙信受邀为竞赛提供全方位的…

【海思Hi3519DV500】双目网络相机套板硬件规划方案

Hi3519DV500双目网络相机套板是针对该芯片设计的一款 IP 编码板 PCBA&#xff0c;硬件接口支持双目sensor 接入&#xff0c;SDIO3.0 接口、USB2.0、USB3.0、UART 接口以及丰富的 IO 扩展应用&#xff0c;可根据各种使用场景设计相应扩展板&#xff0c;丰富外围接口&#xff0c;…

【青牛科技】电流模式PWM控制器系列--D4870

概述&#xff1a; D4870是用于开关电源的电流模式PWM(PWM)控制器系列产品。 该电路待机功耗低&#xff0c;启动电流低。在待机模式下&#xff0c;电路进入间歇工作模式&#xff0c;从而有效地降低电路的待机功耗。 电路的开关频率为 65KHz&#xff0c;抖动的振荡频率&…

对象:是什么,使用,遍历对象,内置对象

对象使用&#xff1a; 对象访问&#xff1a;&#xff08;对象每个属性之间用逗号隔开&#xff09; 补充&#xff1a;也可以通过 对象名[‘属性名’] 对象方法&#xff1a; 方法名:匿名函数 调用方法不需要控制台打印&#xff0c;只要调用就自动输出值 遍历对象&#xff1a; …

tcp/ip异常断开调试笔记——lwip

问题一&#xff1a;异常掉线 异常断开模拟 1、单片机端做服务端&#xff08;只监听一个客户端&#xff09;&#xff0c;电脑做客户端连接 2、尝试连接确定通信正常&#xff0c;断开网线。电脑客户端点击断开 3、经过一段时间&#xff08;超过tcp/ip 3次握手时间&#xff09…

JavaScript获取URL参数常见的4种方法

&#x1f680; 个人简介&#xff1a;某大型国企资深软件开发工程师&#xff0c;信息系统项目管理师、CSDN优质创作者、阿里云专家博主&#xff0c;华为云云享专家&#xff0c;分享前端后端相关技术与工作常见问题~ &#x1f49f; 作 者&#xff1a;码喽的自我修养&#x1f9…