归并排序的详解!

news2025/1/23 0:02:16

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


 

前序:

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


归并排序的介绍 

归并排序(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.coloradmin.cn/o/966119.html

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

相关文章

11 - 深入了解NIO的优化实现原理

Tomcat 中经常被提到的一个调优就是修改线程的 I/O 模型。Tomcat 8.5 版本之前&#xff0c;默认情况下使用的是 BIO 线程模型&#xff0c;如果在高负载、高并发的场景下&#xff0c;可以通过设置 NIO 线程模型&#xff0c;来提高系统的网络通信性能。 我们可以通过一个性能对比…

机器学习——决策树与随机森林

机器学习——决策树与随机森林 文章目录 前言一、决策树1.1. 原理1.2. 代码实现1.3. 网格搜索1.4. 可视化决策树 二、随机森林算法2.1. 原理2.2. 代码实现 三、补充&#xff08;过拟合与欠拟合&#xff09;总结 前言 决策树和随机森林都是常见的机器学习算法&#xff0c;用于分…

OS 内存分区和分页 多级页表与快表

每个进程的PCB都有一个LDT 内存紧缩不实用&#xff0c;所需时间太长 类似于段表&#xff0c;存在页表 但是不连续需要的空间太多了&#xff0c;太麻烦了 多级页表&#xff1a;类比于书的章目录和节目录 构建页目录 每个页目录号指向4M的地址 快表是寄存器&#xff0c;很昂…

Kotlin管道Channel在receiveAsFlow时debounce与flow差异

Kotlin管道Channel在receiveAsFlow时debounce与flow差异 import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import kotlinx.coroutine…

Nginx高级配置

目录 一、Nginx 第三方模块 1.1ehco 模块 二、变量 2.1 内置 2.2 自定义变量 三、nginx压缩功能 ​编辑四、https功能 一、Nginx 第三方模块 1.1ehco 模块 基于nginx 模块 ngx_http_stub_status_module 实现&#xff0c;在编译安装nginx的时候需要添加编译参数 --with-…

第7篇:ESP32连接按钮点亮LED无源喇叭播放声音

第1篇:Arduino与ESP32开发板的安装方法 第2篇:ESP32 helloword第一个程序示范点亮板载LED 第3篇:vscode搭建esp32 arduino开发环境 第4篇:vscodeplatformio搭建esp32 arduino开发环境 第5篇:doit_esp32_devkit_v1使用pmw呼吸灯实验 第6篇:ESP32连接无源喇叭播放音乐《涛声…

程序员自由创业周记#3:No1.作品

作息 如果不是热爱&#xff0c;很难解释为什么能早上6点自然醒后坐在电脑前除了吃饭一直敲代码到23点这个现象&#xff0c;而且还乐此不疲。 之前上班的时候生活就很规律&#xff0c;没想到失业后的生活比之前还要规律&#xff1b;记得还在上班的时候&#xff0c;每天7点半懒洋…

670. 最大交换

链接&#xff1a; ​​​​​​670. 最大交换 题解&#xff1a; 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 class Solution { public:int maximumSwap(int num) {if (num < 0) {return 0;}std::string str to_string(num);stack<int&g…

植物大战僵尸各种僵尸攻略(一)

前言 此文章为“植物大战僵尸”专栏中的004刊&#xff08;2023年9月第2刊&#xff09;&#xff0c;欢迎订阅。版权所有。 注意&#xff1a; 1.本博客适用于pvz无名版&#xff1b; 2.pvz指植物大战僵尸&#xff08;Plants VS Zonbies)&#xff1b; 3.本文以耗费低做标准&am…

JY901B智能9轴加速度计陀螺仪角度传感器

今日学习使用JY901B智能9轴加速度计陀螺仪角度传感器 本文会先使用上位机获取数据作演示&#xff0c;后介绍它的数据表发送原理。 文章提供详细的原理讲解&#xff0c;测试工程下载&#xff0c;代码讲解&#xff0c;本人有多注释的习惯&#xff0c;希望对大家有帮助。 我的J…

2021年09月 C/C++(六级)真题解析#中国电子学会#全国青少年软件编程等级考试

C/C++编程(1~8级)全部真题・点这里 第1题:双端队列 定义一个双端队列,进队操作与普通队列一样,从队尾进入。出队操作既可以从队头,也可以从队尾。编程实现这个数据结构。 时间限制:1000 内存限制:65535 输入 第一行输入一个整数t,代表测试数据的组数。 每组数据的第一…

Java学习之序列化

1、引言 《手册》第 9 页 “OOP 规约” 部分有一段关于序列化的约定 1&#xff1a; 【强制】当序列化类新增属性时&#xff0c;请不要修改 serialVersionUID 字段&#xff0c;以避免反序列失败&#xff1b;如果完全不兼容升级&#xff0c;避免反序列化混乱&#xff0c;那么请…

Sitecore站点更新License

一、简介 Sitecore 是一个基于ASP.NET 技术的 CMS 系统&#xff0c;它不仅具有传统 Web CMS 的所有功能&#xff0c;还集成了 Marketing 营销&#xff08;当然&#xff0c;这个功能价格不菲&#xff09;的功能&#xff0c;可以提供一个一站式的在线营销解决方案。对于 .NET 程…

Google Earth Engine 的缺点和限制

随着 Google Earth Engine 在地球科学和数据计算领域越来越流行&#xff0c;网上有很多介绍Google Earth Engine 的文章及 Google Earth Engine的追随者。Google Earth Engine确实是一款伟大的产品&#xff0c;我们应该为其点赞。但由于已经有太多人在热捧了&#xff0c;我这里…

在termux下安装pip

termux的包安装命令是pkg或者apt&#xff0c;在termux下安装python包&#xff0c;一般直接pip。 (本笔记适合初初接触termux或者太久没碰termux而遗忘的 coder 翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣…

C++ 浅拷贝和深拷贝

目录 1. 浅拷贝 2. 深拷贝 1. 浅拷贝 浅拷贝只是拷贝一个指针&#xff0c;并没有新开辟一个地址&#xff0c;拷贝的指针和原来的指针指向同一块地址&#xff0c;如果原来的指针所指向的资源释放了&#xff0c;那么再释放浅拷贝的指针的资源就会出现错误 对一个已知对象进行拷贝…

在VSCode上画UML的三个插件

2023年9月2日&#xff0c;周六晚上 因为写代理模式的博客时需要画UML&#xff0c;所以就在网上找了半天&#xff0c; 最后觉得VSCode上的这三个插件比较好用 目录 三个画UML的VSCode插件PlantUMLDraw.io IntegrationUMLet我个人推荐使用PlantUML 三个画UML的VSCode插件 Pla…

实战-支付漏洞

免责声明 本文发布的工具和脚本&#xff0c;仅用作测试和学习研究&#xff0c;禁止用于商业用途&#xff0c;不能保证其合法性&#xff0c;准确性&#xff0c;完整性和有效性&#xff0c;请根据情况自行判断。如果任何单位或个人认为该项目的脚本可能涉嫌侵犯其权利&#xff0c…

A 股个股资金流 API 数据接口

A 股个股资金流 API 数据接口 全量股票资金流数据&#xff0c;全量A股数据&#xff0c;最长30日历史数据 1. 产品功能 支持所有A股资金流数据查询&#xff1b;每日定时更新数据&#xff1b;支持多达 30 日历史数据查询&#xff1b;超高的查询效率&#xff0c;数据秒级返回&am…

springboot 与 Redis整合

SpringBoot 操作数据&#xff1a;Spring-data jpa jdbc mongodb redis! SpringData 也是和SpringBoot 齐名的项目&#xff01; 说明&#xff1a;在SpringBoot2.X 之后&#xff0c;原来使用的jedis被替换成了lettuce jedis&#xff1a; 采用的直连&#xff0c;多个线程操作的话&…