归并排序(递归+非递归)

news2024/11/23 18:31:56

目录

  • 一、什么是归并排序?
  • 二、归并排序(递归)
  • 三、归并排序(非递归)

一、什么是归并排序?

归并排序,是创建在归并操作上的一种有效的排序算法。算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。归并排序思路简单,速度仅次于快速排序,为稳定排序算法。

二、归并排序(递归)

归并排序(递归)的基本思路:递归的思路很简单,就是把整个大数组用二分法逐步划分成小数组,直到数组只有一个数或者没有数的时候开始往回归并,归并就是用两个有序的数组遍历比较,哪个数小就把它归并到开辟的临时数组中去,这样就能把两个有序的数组归并成一个有序的数组,那归并得到的这个有序的数组再和另外一个有序的数组归并,以此类推,则最后一次就是把左右两半有序的数组进行归并从而使整个数组有序。
在这里插入图片描述
参考代码如下:


void _MergeSortR(int* a, int left, int right, int* tmp)
{
	//如果递归到小区间只有一个值(left==right)或者不存在(left>right),则返回
	if (left >= right)
	{
		return;
	}
	
	//递归把需要排序的大区间逐步二分成小区间,直到区间只有
	//一个值或者区间不存在的时候再开始归并且回归
	int mid = (left + right) / 2;
	_MergeSortR(a, left, mid, tmp);
	_MergeSortR(a, mid + 1, right, tmp);

	//来到这的时候说明区间只有一个值或者区间不存在了,开始归并
	int begin1 = left;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = right;

	//两个小数组归并时把数据归并到开辟的临时数组tmp中,从left位置开始放,
	//方便归并完数组的数据之后把tmp数据拷贝回到原数组中
	int i = left;

	//当两个小数组都还有数据就继续归并,直到某一个小数组没有数据了就停止
	while (begin1 <= end1 && begin2 <= end2)
	{
		//哪个数据小就把它归并到tmp对应的位置上去(升序)
		if (a[begin1] <= a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}
	//来到这的时候一定有一个小数组已经归并完了,但是是左边那个还是右边那个
	//是不知道的,那么我们不妨写下两个循环,如果是右边走完了,那就进入第一个
	//循环,把剩下的数据逐一放到tmp数组中去,否则就进入第一个循环
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}

	//最后把这归并到tmp中对应位置的数据拷贝回原数组中,注意要
	//小心地控制边界,tmp是从left位置开始的,所以是tmp+left
	//同理,拷贝数据回原数组也是要放到a+left的位置上去的,元素个数
	//是right-left+1个,因为这个是左闭右闭区间,最后需要+1才是元素个数
	//例如,[0,9]的下标是有9-0+1个元素的
	memcpy(a + left, tmp + left, sizeof(int) * (right - left + 1));
}

void MergeSortR(int* a, int n)
{
	assert(a);
	//开辟一个临时数组用于存放归并时的数据
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("");
		return;
	}

	_MergeSortR(a, 0, n - 1, tmp);

	free(tmp);
	tmp = NULL;
}

递归动图:
在这里插入图片描述

时间复杂度:由代码可知,这里的递归运用的是二分法,所以需要递归logN层,在每一层归并子数组时,都需要遍历一次整个数组,所以每一次归并时比较次数时O(N),所以毫无疑问,归并排序递归法的时间复杂度为O(N*logN),空间复杂度为O(N),因为开辟了一个临时数组。

三、归并排序(非递归)

由于递归排序终究需要不断地开辟栈帧,当数据量很大的时候递归深度会很大,所以我们有必要学会用非递归排序的方法代替递归排序。那么如何把归并排序的递归版本改写成非递归呢?接下来我们探讨一下。
可以看到,归并排序的递归是把整个大数组逐步二分成小数组,直到每个小数组的元素个数是一个或者没有的时候才开始回归归并的,而且两个数组能够归并的前提条件是它们都是有序的,所以我们不能直接把大数组分成两个小数组直接归并,因为数组的两边不一定是有序的,那么我们可以反过来归并,直接从1个数和1个数归并(因为一个数的数组可以认为是有序的)开始,然后再2个数和2个数归并,再到4个数和4个数归并,以此类推,直到最后一次大数组的左右两边都有序了,那么再归并一次就能使整个数组有序了。
递归可以只加一个判断条件就能控制住边界越界的问题,当left>=right就直接返回就控制住了,而循环无法完成类似操作,所以只能自己在每一次归并的时候检查边界值是否越界,如果有越界就加以修正。在这里控制边界才是归并排序非递归的最核心的地方,稍有不慎,就会导致程序崩溃了。

具体操作如下图:
在这里插入图片描述
参考代码如下:

void MergeSortNonR(int* a, int n)
{
	assert(a);
	//开辟一个临时数组用于存放归并时的数据
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("");
		return;
	}

	//gap代表每个小数组有多少个元素
	int gap = 1;
	//每一组的元素个数当然不能大于等于数组本身的大小了
	while (gap < n)
	{
		int i = 0;
		//第一次进来,gap==1,那么就是一一归并
		for (i = 0; i < n; i += 2 * gap)
		{
			//这里边界的确定请参考上面图片
			int begin1 = i;
			int end1 = i + gap - 1;
			int begin2 = i + gap;
			int end2 = i + 2 * gap - 1;
			//当数组的元素个数不是2的整数倍时,end1,begin2,end2都
			//有可能存在越界,所以需要判断和修正边界值
			//如果end1或者begin2越界了,其实只需要判断begin2是否越界也就行了,
			//因为end1越界那么begin2一定也越界的,那么归并时[begin2,end2]
			//就没有数据了,也就没有和[begin1,end1]元素归并的必要了,直接break
			//进入下一次循环
			if (end1 >= n || begin2 >= n)
			{
				break;
			}
			//如果end2>=n越界,那么说明[begin2,end2]中还有元素需要归并,所以
			//修正以下end2,使end指向数组中最后一个元素即可,再和[begin1,end1]
			//中的元素进行归并
			else if (end2 >= n)
			{
				end2 = n - 1;
			}
			//谨记:这里不能写成else,因为下面的步骤是必须执行的

			//以下就是归并的步骤了,和递归的写法是一样的
			int j = begin1;
			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++];
			}
			/*int k = 0;
			for (k = i; k <= end2; k++)
			{
				a[k] = tmp[k];
			}*/

			//这里的边界控制也要注意,不能写成a+begin1、tmp+begin1和
			//end2-begin1+1,因为begin1已经再归并的时候改变了
			memcpy(a + i, tmp + i, sizeof(int) * (end2 - i + 1));

		}//end of for

		//一一归并完了之后就是两两归并,然后四四归并,所以这里是gap*=2
		gap *= 2;
	}

	free(tmp);
	tmp = NULL;
}

时间复杂度:由于这里的gap也是按照×=2走了,所以这里的外循环也是走了logN次,里面的归并排序每次也是遍历一遍数组,即比较N次,所以非递归的时间复杂度也是O(N*logN).

以上就是归并排序的所有内容,你学会了吗?如果对你有所帮助,那就点亮一下小心心呗,顺便点个关注后期还会持续更新数据结构的相关知识哦!!!

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

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

相关文章

企业电子招标采购系统源码Spring Boot + Mybatis + Redis + Layui + 前后端分离 构建企业电子招采平台之立项流程图

项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以及…

【redis】缓存双写一致性之更新策略探讨(上)

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目…

移动通信技术的毫米波波束成形系统构成

随着通信产业尤其是移动通信的高速发展&#xff0c;无线电频谱的低端频率已趋饱和。采用各种调制方法或多址技术扩大通信系统的容量&#xff0c;提高频谱的利用率&#xff0c;也无法满足未来通信发展的需求&#xff0c;因而实现高速、宽带的无线通信势必向微波高频段开发新的频…

No.035<软考>《(高项)备考大全》【第19章】流程管理

【第19章】流程管理1 章节相关2流程管理基础3 流程分析、设计、实施与评估4 流程重构与改进5 项目管理流程的管理和优化6 练习题参考答案1 章节相关 本章节看一遍即可。 2流程管理基础 ★1、BPM是一种以规范化的构造端到端的卓越业务流程为中心&#xff0c;以持续的提高组织…

Springboot基础学习之(十九):通过Mybatis和shiro框架实现授权功能

在shiro整合mybatis实现认证功能 在此篇文章的基础上实现授权的功能&#xff1a;对网页的访问设置权限&#xff0c;只有登录的用户具有该网页的访问权限才能够访问此网页&#xff0c;那篇文章已经将系统的环境全都配好了&#xff0c;只需要在完善功能即可&#xff0c;所以关于项…

怎么入驻Tik Tok的跨境MCN机构呢?

TIKTOK强势崛起&#xff0c;跨境MCN成黄金赛道 社交媒体格局的改变&#xff0c;往往体现在青少年身上。面对TIKTOK的强势崛起&#xff0c;全球社媒霸主Facebook已经出现衰落的趋势。 据相关数据显示&#xff0c;TIKTOK目前有四分之一的用户都是二十岁一下的青少年&#xff0c;拥…

大公司的okr管理- 氛围 + 月历

OKR之剑实战篇06&#xff1a;OKR致胜法宝-氛围&业绩双轮驱动&#xff08;下&#xff09;_vivo互联网技术的博客-CSDN博客 先说说氛围。组织氛围的提出者库尔特勒温被尊为“社会心理学之父”&#xff0c;他的核心理论非常通俗易懂&#xff0c;决定人类行为的&#xff0c;不是…

TK4860E交流充电桩检定装置

TK4860系列是专门针对现有交流充电桩现场检测过程中接线复杂、搬运困难、检测效率低、成本投入高等问题而研制的一系列高效检测仪器&#xff0c;旨在更好的开展充电桩的强制检定工作。 TK4860E交流充电桩检定装置是其中一款专用于现场检定电动汽车单相充电桩的一体式便携式仪器…

236页10万字精选数据中台建设方案2022版(word)

本资料来源公开网络&#xff0c;仅供个人学习&#xff0c;请勿商用&#xff0c;如有侵权请联系删除 1. 数据中台建设方案 1.1. 总体建设方案通过对客户大数据应用平台服务需求的理解&#xff0c;根据建设目标、设计原则的多方面考虑&#xff0c;建议采用星环科技Transwarp Data…

JAVA局域网飞鸽传书软件设计与实现

本文的目标是设计一个类似飞鸽传输的局域网通信软件&#xff0c;并分析它在其领域的优势。本设计以C编 写&#xff0c;能在windows 2000/net/xp等环境下运行。设计共分为五大模块&#xff0c;分别是&#xff1a;首先&#xff0c;介绍选题背景及意义和国内外研究现状&#xff1b…

【id:31】【20分】A. Point(类与构造)

题目描述 下面是一个平面上的点的类定义&#xff0c;请在类外实现它的所有方法&#xff0c;并生成点测试它。 输入 测试数据的组数 t 第一组测试数据点p1的x坐标 第一组测试数据点p1的y坐标 第一组测试数据点p2的x坐标 第一组测试数据点p2的y坐标 .......... 输出 输出…

阿里都在用的线上问题定位工具【收藏备用】

简介 Arthas 是Alibaba开源的Java诊断工具&#xff0c;动态跟踪Java代码&#xff1b;实时监控JVM状态&#xff0c;可以在不中断程序执行的情况下轻松完成JVM相关问题排查工作 。支持JDK 6&#xff0c;支持Linux/Mac/Windows。这个工具真的很好用&#xff0c;而且入门超简单&…

【故障诊断】用于轴承故障诊断的性能增强时变形态滤波方法及用于轴承断层特征提取的增强数学形态算子研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。⛳座右铭&#…

动态网站开发讲课笔记06:JSP技术

文章目录零、本节学习目标一、JSP概述&#xff08;一&#xff09;什么是JSP1、JSP的概念2、JSP的特征&#xff08;1&#xff09;跨平台&#xff08;2&#xff09;业务代码相分离&#xff08;3&#xff09;组件重用&#xff08;4&#xff09;预编译&#xff08;二&#xff09;编…

Kettle(9.3.0)连接ClickHouse

注意&#xff1a;低版本的kettle即使装ClickHouse驱动包后也不一定支持ClickHouse数据库连接&#xff08;具体kettle从什么版本开始支持ClickHouse没测试过&#xff09;&#xff0c;只有高版本的kettle在安装ClickHouse驱动包后才支持ClickHouse数据库连接&#xff0c;因此这里…

海康工业相机USB相机问题排查思路—Windows 系统

​第一步骤:固件版本 a) 查看相机固件版本 b) 若较老建议升级成最新固件后再测试异常是否消失。 注意切勿跨大版本升级; 第二步骤:驱动排查查看客户端中 USB 相机枚举列表中的相机图标,正常情况下如图 2-1:若有“2”的字样(如图 2-2),则说明 PC 的 USB 口是 2.0 的,…

故障注入的方法与工具

可靠性是评估软件质量的重要属性&#xff0c;关键安全系统&#xff08;Safety Critical Systems&#xff0c;SCS&#xff09;对可靠性的要求尤为严格&#xff0c;因其一旦失效&#xff0c;将可能对生命、财产或环境造成重大损害。以汽车为例&#xff0c;ISO26262中ASIL D要求相…

TCP三次握手四次挥手及time_wait状态解析

TCP的建立——三次握手 1.服务器必须准备好接受外来的连接。通常通过调用socket&#xff0c;bind&#xff0c;listen这三个函数来完成&#xff0c;我们称之为被动打开(passive open)。 2. 客户端通过调用connect函数发起主动的打开(active open)。这导致客户TCP发送一个SYN(同步…

【分享】除了压缩文件,WinRAR还有这些好用的功能

WinRAR是一款功能强大的压缩软件&#xff0c;可以解压缩RAR、ZIP及其它类型文件。但很多人不知道&#xff0c;除了解压、压缩文件&#xff0c;WinRAR还有其他的功能&#xff0c;今天小编就来分享一下。 功能一&#xff1a;锁定文件&#xff0c;禁止增删或修改压缩包里的文件 W…

国内外人工智能AI工具网站大全(一键收藏,应有尽有)

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 国内外人工智能AI工具网站大全&#xff08;一键收藏&#xff0c;应有尽有&#xff09;摘要一、AI写作工具二、AI图像工具2.1、常用AI图像工具2.2、AI图片插画生成2.3、AI图片背景移除…