归并排序的详解!

本文旨在讲解归并排序的实现(递归及非递归)搬好小板凳,干货来了!


 

前序:

在介绍归并排序之前,需要给大家介绍的是什么是归并,归并操作,也叫归并算法,指的是将两个顺序序列合并成一个顺序序列的方法,相信不少小伙伴之前都做过合并两个有序链表或者两个有序数组的例题,归并就是将两个数组或链表合并成一个链表或数组,还得保证与其原来的顺序相同!那么归并排序就是用到了归并这个思想,将一组元素完成排序的算法!


归并排序的介绍 

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。


归并排序的时间复杂度和空间复杂度

时间复杂度:O(N*logN),因为其是一种二叉树结构,其高度为logN,每层需要排序的个数都是N个,所以其时间复杂度为O(N*logN)。

空间复杂度:因为创建了一个新的数组,所以其空间复杂度为O(N);


归并排序的思想与思路

归并排序就是本质上是分治的方法来实现的,是将一组数据分割成若干组有序数组,然后对这若干个有序数组两两进行归并即可得到我们想要的排序!


归并排序的思路图


归并排序的动态图展示


归并排序的大致实现思路 

归并排序其实现的思路其实很简单,就是将一组数据分割,分割到若干组有序数组,然后两两进行归并,那么如何保证分割的数组为有序数组呢,这其实很简单,当分割到数组中只有一个元素的时候,那么该数组就是有序的数组了!然后进行归并拷贝到原数组上即可!


归并排序的代码实现

(C版本递归)

void _MergeSort(int* a, int left, int right, int* tem)
{//当再次需要调用的区间不存在时,返回即可!if (left == right)  //很显然,left不会大于right,保险起见,加上大于条件没有影响!{return;}//每次取出中间坐标,用于下次的左半边的递归,右半边递归同理!int mid = (left + right) / 2;_MergeSort(a, left, mid, tem);_MergeSort(a, mid + 1, right, tem);//到此,分割区间已经结束,每组区间都能保证时有序的了!下一步就开始进行归并!int begin1 = left, end1 = mid;int begin2 = mid + 1, end2 = right;int i = left;  //i用于对tem数组的下标进行表示!//下面开始归并两个有序数组,当两个有序数组其中一个遍历完成就退出循环!while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tem[i++] = a[begin1++];}else{tem[i++] = a[begin2++];}}while (begin1 <= end1){tem[i++] = a[begin1++];}while (begin2 <= end2){tem[i++] = a[begin2++];}//归并结束后,将tem数组中的数据,拷贝到元素组中相应的位置即可!memcpy(a + left, tem + left, sizeof(int)*(right - left + 1));//个数为右边界减去左边加1,因为为闭区间!//至此,归并结束,拷贝结束!
}
void MergeSort(int* a, int n)
{//在堆上申请开辟一个tem数组,用来最后拷贝到原数组中!int* tem = (int *)malloc(sizeof(int) * n);//因为存在递归的调用,所以再创建一个函数,若仍在此函数上重复调用时,则会重复开辟新的空间,可能导致空间不足!_MergeSort(a, 0, n - 1, tem);//用完之后释放到tem数组!free(tem);
}

需要注意的是:当进行递归归并排序的时候,需要注意返回的条件,当区间不存在或者区间内部只有一个元素的时候就可以返回了!还需要注意的是,因为要进行拷贝,不能在原数组上直接拷贝,所以需要创建一个新的数组用来存储归并后的元素的位置,最后归并结束重新拷贝到原数组中即可!


(C版本非递归)

分段拷贝

// 归并排序非递归实现//思路如下:要想实现归并排序的非递归,那么需要注意分组,从每组一个元素开始,因为当只有一个元素的时候,默认是有序的,然后
//进行归并拷贝即可,每组一个元素遍历结束之后,进行每组两个,依次进行每组2倍个元素进行归并!,当每组的元素为所有元素的一半或大于一半,
//即可完成排序,需要特别注意的是,进行非递归归并排序的时候,需要注意区间的取值,在此有两个拷贝方式,一种是整体拷贝,一组是归并一段拷贝一段!//进行非递归实现归并的时候也需要创建一个新的数组,不能在原数组上进行对数据的改变,因为可能会造成数据的覆盖,导致数据不能完成排序!
//创建一个新数组,然后让每组元素为一个依次递增二倍,进行归并拷贝,直至每组的元素个数大于数组个数结束归并即可完成排序!//下面先来进行分段拷贝!
//void MergeSortNonR(int* a,int n)
//{
//	//注意:gap代表的是每组归并时的元素的个数!
//	int gap = 1;
//	int* tem = (int*)malloc(sizeof(int) * n);
//	while (gap < n)		//当gap大于n时结束循环即可完成!
//	{
//		int j = 0;
//		//每组为一个的进行遍历!
//		for (int i = 0; i <n ; i+=2*gap)
//		{
//
//			//每组个数为1的进行归并排序!
//			//区间范围如下!
//			int begin1 = i, end1 = i + gap - 1;
//			int begin2 = i + gap, end2 = i + 2 * gap - 1;
//
//			//当end1>n,begin2>n时,不需要进行归并!
//			if (end1 >= n||begin2>=n)
//			{
//				break;
//			}
//
//			//对end2边界进行修改!
//			if (end2 >= n)
//			{
//				end2 = n-1;
//			}
//			
//			//开始进行归并拷贝!
//			while (begin1 <= end1 && begin2 <= end2)
//			{
//				if (a[begin1] < a[begin2])
//				{
//					tem[j++] = a[begin1++];
//				}
//				else
//				{
//					tem[j++] = a[begin2++];
//				}
//			}
//			while (begin1 <= end1)
//			{
//				tem[j++] = a[begin1++];
//			}
//			while (begin2 <= end2)
//			{
//				tem[j++] = a[begin2++];
//			}
//
//			//注意:需要注意的是:当求元素的个数时,应该用end2-i,不能减去begin1,因为begin1每次都会改变,记录的不再是数组开始拷贝的地方!
//			memcpy(a + i, tem + i, sizeof(int) * (end2 - i + 1));
//		}
//		gap *= 2;
//	}
//	free(tem);
//}

整体拷贝

//整段拷贝!
void MergeSortNonR(int* a, int n)
{//注意:gap代表的是每组归并时的元素的个数!int gap = 1;int* tem = (int*)malloc(sizeof(int) * n);while (gap < n)		//当gap大于n时结束循环即可完成!{//j的声明必须写在for循环的外面,因为若写到for循环内部时,在每组循环都会将原来归并好的数据放到前面的那些位置//导致以及归并好的又被覆盖,导致排序失败!(每组的归并都放在前两组内部,导致不能将全部归并结束,!)int j = 0;//每组为一个的进行遍历!for (int i = 0; i < n; i += 2 * gap){//每组个数为1的进行归并排序!//区间范围如下!int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;//当end1>n,begin2>n时,不需要进行归并!if (end1 >= n){end1 = n - 1;//将第二块区间设置为不存在的区间!如果设置为n-1那么会造成对最后一个数据的重复使用,拷贝,导致排序错误!begin2 = n;end2 = n - 1;}else if (begin2 >= n){begin2 = n;end2 = n - 1;}else if(end2>=n){end2 = n - 1;}//开始进行归并拷贝!while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2]){tem[j++] = a[begin1++];}else{tem[j++] = a[begin2++];}}while (begin1 <= end1){tem[j++] = a[begin1++];}while (begin2 <= end2){tem[j++] = a[begin2++];}//注意:需要注意的是:进行整段拷贝的时候,不需要再从a+begin1的位置开始拷贝啦,直接将所有tem中的元素拷贝到原数组即可!}memcpy(a, tem, sizeof(int) * n);gap *= 2;}free(tem);
}

需要注意的是:非递归的归并排序,整体拷贝和分段拷贝大致思路是一样的,只是最后进行memcpy的起始位置和个数有所不同!相关细节与思路都在源代码上加有注释标明,需要注意的是:当进行整体拷贝的时候,用于标记tem数组的j的坐标的定义一定要在for循环外部定义赋值,若在内部赋值定义,则每进行一次都会覆盖原来已经归并好的数据上面,导致归并排序不能正确进行!

今日的归并排序分享到此结束,欢迎大家积极评论支持。若有不足及补充,及时提出,必将改正! 

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

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

相关文章

lv3 嵌入式开发-3 linux shell命令(文件搜索、文件处理、压缩)

目录 1 查看文件相关命令 1.1 常用命令 1.2 硬链接和软链接 2 文件搜索相关命令 2.1 查找文件命令 2.2 查找文件内容命令 2.3 其他相关命令 3 文件处理相关命令 3.1 cut 3.2 sed 过滤 3.3 awk 匹配 4 解压缩相关命令 4.1 解压缩文件的意义 4.2 解压缩相关命令 1 …

大学物理 之 安培环路定理

文章目录 前言什么是安培环路定理安培环路定理有什么作用 深入了解深入学习 前言 什么是安培环路定理 安培环路定理的物理意义在于描述了电流和磁场之间的相互作用&#xff0c;以及如何在一个封闭的回路中分析这种相互作用。 简单的来说 , 用环路定理来解决在磁场中B对任意封…

[管理与领导-65]:IT基层管理者 - 辅助技能 - 4- 职业发展规划 - 乌卡时代(VUCA )

前言&#xff1a; 大多数IT人&#xff0c;很勤奋&#xff0c;但都没有职业规划&#xff0c;被工作驱动着前行&#xff0c;然而&#xff0c;作为管理者&#xff0c;你就不能没有职业规划思维&#xff0c;因为你代表一个团队&#xff0c;你的思维决定了一个团队的思维。本文探讨…

redis实战-实现优惠券秒杀解决超卖问题

全局唯一ID 唯一ID的必要性 每个店铺都可以发布优惠券&#xff1a; 当用户抢购时&#xff0c;就会生成订单并保存到tb_voucher_order这张表中&#xff0c;而订单表如果使用数据库自增ID就存在一些问题&#xff1a; id的规律性太明显&#xff0c;容易被用户根据id的间隔来猜测…

如何实现的手机实景自动直播,都有哪些功能呢?

手机实景自动直播最近真的太火了&#xff0c;全程只需要一部手机&#xff0c;就能完成24小时直播带货&#xff0c;不需要真人出镜&#xff0c;不需要场地&#xff0c;不需要搭建直播间&#xff0c;只需要一部手机就可以了。真人语音讲解&#xff0c;真人智能回复&#xff0c;实…

C#-单例模式

文章目录 单例模式的概述为什么会有单例模式如何创建单例模式1、首先要保证&#xff0c;该对象 有且仅有一个2、其次&#xff0c;需要让外部能够获取到这个对象 示例通过 属性 获取单例 单例模式的概述 总结来说&#xff1a; 单例 就是只有 一个实例对象。 模式 说的是设计模式…

《Go 语言第一课》课程学习笔记(十二)

函数 Go 函数与函数声明 在 Go 语言中&#xff0c;函数是唯一一种基于特定输入&#xff0c;实现特定任务并可返回任务执行结果的代码块&#xff08;Go 语言中的方法本质上也是函数&#xff09;。在 Go 中&#xff0c;我们定义一个函数的最常用方式就是使用函数声明。 第一部…

Mybatis学习|注解开发、lombok

1.使用注解开发 无需再编写相应的Mapper.xml文件&#xff0c;直接将sql用注解的形式写在Mapper接口的对应方法上即可。 然后因为没有xml文件,所以要在mybatis-config.xml核心配置文件中注册这个Mapper接口&#xff0c;而不用去注册之前的Mapper.xml&#xff0c;这里其实如果用…

QT listWidget 中实现元素的自由拖拽

QListWIdget中拖拽元素移动 setMovement(QListView::Movement::Free);setDragEnabled(true); setDragDropMode(DragDropMode::DragDrop); setDefaultDropAction(Qt::DropAction::MoveAction);

nginx-反向代理缓存

反向代理缓存相当于自动化动静分离。 将上游服务器的资源缓存到nginx本地&#xff0c;当下次再有相同的资源请求时&#xff0c;直接讲nginx缓存的资源返回给客户端。 本地缓存资源有一个过期时间&#xff0c;当超过过期时间&#xff0c;则重新向上游服务器重新请求获取资源。…

SIEM(安全信息和事件管理)解决方案

什么是SIEM 安全信息和事件管理&#xff08;SIEM&#xff09;是一种可帮助组织在安全威胁危害到业务运营之前检测、分析和响应安全威胁的解决方案&#xff0c;将安全信息管理 (SIM) 和安全事件管理 (SEM) 结合到一个安全管理系统中。SIEM 技术从广泛来源收集事件日志数据&…

Matlab——二维绘图(最为详细,附上相关实例)

为了帮助各位同学备战数学建模和学习Matlab的使用&#xff0c;今天我们来聊一聊 Matlab 中的绘图技巧吧&#xff01;对于 Matlab 这样的科学计算软件来说&#xff0c;绘图是非常重要的一项功能。在数据处理和分析时&#xff0c;良好的绘图技巧能够更直观地呈现数据&#xff0c;…

【DP】CF Edu 21 E

Problem - E - Codeforces 题意&#xff1a; 思路&#xff1a; 就是一个 N为1e5&#xff0c;M为3e5的背包问题&#xff0c;不过特殊条件是 w < 3 我们去从最简单的情况开始考虑 当只有w 1的物品和w 2的物品时&#xff0c;考虑贪心地把物品按价值排序&#xff0c;然后选…

Docker部署项目

相关系列文章&#xff1a; 1、DockerHarbor私有仓库快速搭建 2、DockerJenkinsHarbor 3、Docker安装Mysql、Redis、nginx、nacos等环境 1、jenkins构建前端并上传服务器 在这篇文章中(DockerJenkinsHarbor)未完成前端的远程部署&#xff0c;这里对前端vue工程进行编译打包并上…

虚拟内存相关笔记

虚拟内存是计算机系统内存管理的一个功能&#xff0c;它允许程序认为它们有比实际物理内存更多的可用内存。它使用硬盘来模拟额外的RAM。当物理内存不足时&#xff0c;操作系统将利用磁盘空间作为虚拟内存来存储数据。这种机制提高了资源的利用率并允许更大、更复杂的应用程序的…

BLDC无感方波控制

BLDC无感控制 反电动势过零检测反电动势检测方法比较器模式采样过零信号闭环的建立 BLDC 方波启动技术转子预定位电机的外同步加速电机运行状态的转换 程序部分 反电动势过零检测 它的主要核心就是通过检测定子绕组的反电动势过零点来判断转子当前的位置。 三相六状态 120通电…

IDEA 报 Cannot resolve symbol ‘HttpServletResponse‘ 解决

springboot2版本换成springboot3之后&#xff0c;代码这里突然报红了&#xff0c; 首先要淡定&#xff0c;把原先Import的引入删掉&#xff0c;重新引入试试呢&#xff0c;是不是很简单哈哈。 原来&#xff0c;springboot3的路径是&#xff1a; import jakarta.servlet.http…

4.2 实现基于栈的表达式求值计算器(难度4/10)

本作业主要考察&#xff1a;解释器模式的实现思想/栈结构在表达式求值方面的绝对优势 C数据结构与算法夯实基础作业列表 通过栈的应用&#xff0c;理解特定领域设计的关键作用&#xff0c;给大家眼前一亮的感觉。深刻理解计算机语言和人类语言完美结合的杰作。是作业中的上等…

Docker进阶:mysql 主从复制、redis集群3主3从【扩缩容案例】

Docker进阶&#xff1a;mysql 主从复制、redis集群3主3从【扩缩容案例】 一、Docker常规软件安装1.1 docker 安装 tomcat&#xff08;默认最新版&#xff09;1.2 docker 指定安装 tomcat8.01.3 docker 安装 mysql 5.7&#xff08;数据卷配置&#xff09;1.4 演示--删除mysql容器…

嵌入式学习之进程

1.进程间通信概述 UNIX系统IPC是各种进程通信方式的统称。 2.管道通信原理 特点&#xff1a; 1.它是半双工的&#xff08;即数据只能在一个方向上流动&#xff09;&#xff0c;具有固定的读端和写端。 2.它只能用于具有亲缘关系的进程之间通信&#xff08;也是父子进程或者…