归并排序的详解!

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


 

前序:

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


归并排序的介绍 

归并排序(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.hqwc.cn/news/95336.html

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

相关文章

Lnmp架构

关闭防火墙 安装依赖包 yum -y install pcre-devel zlib-devel gcc gcc-c make 创建运行用户、组 编译安装Nginx 让系统识别nginx的操作命令 添加Nginx系统服务 vim /lib/systemd/system/nginx.service 编译安装mysql 安装Mysql环境依赖包 创建运行用户 编译安装 cd /opt …

香橙派Orangepi Zero2 刷机步骤

目录 1.香橙派Orangepi Zero2简介 2.刷机 2.1物料准备 2.2 格式化SD卡 2.3 烧录镜像到SD卡 2.4 安装SD卡到Orangepi 2.5 连接Pi电源 2.6 MobaXterm 串口登陆Orangepi 2.6.1 连线示意图 2.6.2 MobaXterm 使用 2.6.3修改登陆密码 2.6.4 网络配置 2.7 SSH登陆开发版…

微信小程序请求接口返回的二维码(图片),本地工具和真机测试都能显示,上线之后不显示问题

请求后端接口返回的图片&#xff1a; 页面展示&#xff1a; 代码实现&#xff1a; :show-menu-by-longpress"true" 是长按保存图片 base64Code 是转为base64的地址 <image class"code" :src"base64Code" alt"" :show-menu-by-long…

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

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

【Axure高保真原型】多图表动态切换

今天和大家分享多图表动态切换的原型模板&#xff0c;点击不同的图标可以动态切换对应的表&#xff0c;包括柱状图、条形图、饼图、环形图、折线图、曲线图、面积图、阶梯图、雷达图&#xff1b;而且图表数据可以在左侧表格中动态维护&#xff0c;包括增加修改和删除&#xff0…

Nginx配置及优化3

Nginx配置及优化3 一、网页状态页二、nginx第三方模块2.1、echo模块 三、变量3.1、内置变量3.1.1、常用的内置变量3.1.2、举个例子 3.2、自定义变量 四、自定义访问日志优化4.1、自定义访问日志的格式4.2、自定义json格式日志 五、nginx压缩功能六、HTTPS功能6.1、nginx的HTTPS…

Matlab(画图初阶)

目录 1.plot()函数 2. hold(添加新绘图是否保留旧绘图) 3. Plot Style 3.1 线型 3.2 标记 3.3 颜色 ​编辑 4. legend() 5.X 、Y and Title&#xff1f; 6. Text()和annotation() 7.line(创建基本线条) 7.1 基本语法 7.2 指定线条属性 7.3 更改线条属性 8.图像属性 8.1 …

VMware 安装 Centos7 超详细过程

CentOS系统&#xff0c;安装教程可参考以下&#xff1a; 哪些模型需要在Linux下运行&#xff0c;需提前预装Linux系统呢&#xff0c;评论区讨论吧 比如Noah-MP 5.0模型 1.软硬件准备 软件&#xff1a;推荐使用 VMware&#xff0c;我用的是 VMware 12 镜像&#xff1a;CentO…

一个集成的BurpSuite漏洞探测插件1.1

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

Angular安全专辑之三 —— 授权绕过,利用漏洞控制管理员账户

这篇文章是针对实际项目中所出现的问题所做的一个总结。简单来说&#xff0c;就是授权绕过问题&#xff0c;管理员帐户被错误的接管。 详细情况是这样的&#xff0c;我们的项目中通常都会有用户身份验证功能&#xff0c;不同的用户拥有不同的权限。相对来说管理员账户所对应的…

美创科技一体化智能化公共数据平台数据安全建设实践

公共数据是当今政府数字化转型的关键要素和未来价值释放的核心锚点&#xff0c;也是“网络强国”、“数字中国”的战略性资源。 作为数字化改革先行省份&#xff0c;近年来&#xff0c;浙江省以一体化智能化公共数据平台作为数字化改革的支撑总平台&#xff0c;实现了全省公共数…

JSX底层渲染机制

JSX底层渲染机制 一,.步骤 1.把我们写的jsx语法编译为虚拟DOM【virtualDOM】 虚拟DOM对象&#xff1a;框架自己内部构建的一套对象体系&#xff08;对象的相关成员都是React内部绑定的&#xff09;&#xff0c;基于这些属性描述出我们所构建视图中的DOM接的相关特征 1基于ba…