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

news/2024/5/8 13:37:21/文章来源:https://blog.csdn.net/weixin_70056514/article/details/130151214

目录

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

一、什么是归并排序?

归并排序,是创建在归并操作上的一种有效的排序算法。算法是采用分治法(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*=2gap *= 2;}free(tmp);tmp = NULL;
}

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

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

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

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

相关文章

epoll 反应堆模型(Libevent库核心思想)

epoll 反应堆模型是从 libevent 库里面抽取的核心代码。 epoll ET模式 非阻塞、轮询 void *ptr 代码流程 原来的代码&#xff1a; socket、bind、listen efd epoll_create 创建监听&#xff08;红黑树&#xff09; epoll_ctl 向树上添加一个监听 fd for(;;) { 满足数组…

4.12~4.13学习总结

File 相对路径和绝对路径的区别&#xff1a; 相对路径不带盘符&#xff0c;绝对路径带盘符 小知识点&#xff1a;1KB1024字节&#xff0c;1MB1024KB,1GB1024MB; File对象就表示一个路径&#xff0c;可也是文件的路径&#xff0c;也可以是文件夹的路径 这个路径可以是存在的也可…

MongoDB 介绍和基本操作

一、MongoDB数据库 1、MongoDB是一种非关系型数据库&#xff0c;是用C语言编写的。其特点是高性能、易部署、易使用&#xff0c;存储数据方便。 2、MongoDB特点&#xff1a; 面向集合存储&#xff0c;易于存储对象类型数据&#xff1b;支持动态查询&#xff0c;支持完全索引&…

计算机网络第1章(概述)

文章目录1.1、计算机网络在信息时代的作用1.2、因特网概述1、网络、互连网&#xff08;互联网&#xff09;和因特网2、因特网发展的三个阶段3、因特网的标准化工作4、因特网的组成1.3 三种交换方式1、电路交换&#xff08;Circuit Switching&#xff09;2、分组交换&#xff08…

JSON Web Tokens (JWT) — the only explanation you will ever need

本文摘抄自 Ariel Weinberger 博客JSON Web Tokens (JWT) — the only explanation you will ever need | by Ariel Weinberger | MediumJSON Web Tokens (JWT) — the only explanation you will ever need JSON Web Tokens are changing the world for the better. Acting as…

第1章-JVM与Java体系结构

1、本系列博客&#xff0c;主要是面向Java8的虚拟机。如有特殊说明&#xff0c;会进行标注。 2、本系列博客主要参考尚硅谷的JVM视频教程&#xff0c;整理不易&#xff0c;所以图片打上了一些水印&#xff0c;还请读者见谅。后续可能会加上一些补充的东西。 3、尚硅谷的有些视频…

国家出手管人工智能AI了

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 全球都在封杀AI&#xff0c;国家也出手了&#xff0c;人工智能AI的强监管来了!这次反应速度算是很快了。国家出手&#xff0c;AI必须管。 国家网信办拟针对生成式人工智能服务出台管理办法&#…

【Redis数据库】异地公网远程登录连接Redis教程

文章目录1. Linux(centos8)安装redis数据库2. 配置redis数据库3. 内网穿透3.1 安装cpolar内网穿透3.2 创建隧道映射本地端口4. 配置固定TCP端口地址4.1 保留一个固定tcp地址4.2 配置固定TCP地址4.3 使用固定的tcp地址连接转发自CSDN远程穿透的文章&#xff1a;公网远程连接Redi…

算法之归并排序

文章目录一、归并排序&#xff08;递归版&#xff09;二、归并排序&#xff08;非递归版&#xff09;一、归并排序&#xff08;递归版&#xff09; 归并排序思想&#xff1a;将数组划分为两个区间&#xff0c;左区间&#xff0c;右区间 然后对这两个区间内容进行排序 &#xff…

linux 修改主机名称

1、hostname命令进行临时更改 如果只需要临时更改主机名&#xff0c;可以使用hostname命令&#xff1a; sudo hostname <new-hostname> 例如&#xff1a; 只需重新打开session终端&#xff0c;就能生效&#xff0c; 但是&#xff0c;重启计算机后会回到旧的主机名。…

总结一下Redis的缓存雪崩、缓存击穿、缓存穿透

缓存是提高系统性能的一种常见手段&#xff0c;其中Redis是一种常用的高性能缓存数据库。但是在使用缓存时&#xff0c;可能会遇到一些问题&#xff0c;比如缓存击穿、缓存穿透、缓存雪崩等问题&#xff0c;本文将介绍这些问题的概念、原因以及解决方案。 缓存击穿 缓存击穿指…

深度学习中的目标识别

博主简介 博主是一名大二学生&#xff0c;主攻人工智能研究。感谢让我们在CSDN相遇&#xff0c;博主致力于在这里分享关于人工智能&#xff0c;c&#xff0c;Python&#xff0c;爬虫等方面知识的分享。 如果有需要的小伙伴可以关注博主&#xff0c;博主会继续更新的&#xff0c…

拓展系统命令

文章目录拓展系统命令使用方式拓展系统命令快速运行方法命令 - ZFASTRUN安全运行方法命令 - ZFASTSAFERUN快速运行Query方法命令 - ZFASTQUERY安全运行Query方法 命令 - ZSAFEQUARY防止调试时误将数据提交命令 - ZTRN在Terminal执行SQL语句命令 - ZSQL安全Global命令 - ZSAFEKI…

Windows命令提示符之常见命令--动态更新

序言&#xff1a; 在大家接触Windows电脑的过程中&#xff0c;一般是直接通过鼠标来进行操作&#xff0c;很少甚至没有用到过命令来执行操作&#xff0c;而想必大家都看过电影里面的黑客大神都是通过密密麻麻的指令来操作的&#xff0c;并且执行的速度也会比我们用鼠标块&…

二进制插入与查找组成一个偶数最接近的两个素数

二进制插入 链接&#xff1a;二进制插入_牛客题霸_牛客网 (nowcoder.com) 描述&#xff1a;给定两个32位整数n和m&#xff0c;同时给定i和j&#xff0c;将m的二进制数位插入到n的二进制的第j到第i位,保证n的第j到第i位均为零&#xff0c;且m的二进制位数小于等于i-j1&#xff…

在unreal中的基于波叠加的波浪水面材质原理和制作

关于水的渲染模型 如何渲染出真实的水体和模拟&#xff0c;是图形学&#xff0c;游戏开发乃至仿真领域很有意思的一件事 记得小时候玩《Command & Conquer: Red Alert 3》&#xff0c;被当时的水面效果深深震撼&#xff0c;作为一款2008年出的游戏&#xff0c;现在想起它…

没想到大厂Adobe还有这些“猫腻”!

北京时间周四晚间&#xff0c;图像及视频生产力工具大厂Adobe发布公告&#xff0c;宣布旗下的视频创作应用Premiere Pro将喜提一系列新的AI功能。这也是Adobe上个月发布AIGC创作功能“萤火虫”后的最新动作。综合Adobe的官方公告和演示视频&#xff0c;最大亮点就是基于文字的视…

什么是线性回归?线性回归有什么特征?

什么是线性回归 线性回归定义与公式 线性回归(Linear regression)是利用回归方程(函数)对一个或多个自变量(特征值)和因变量(目标值)之间关系进行建模的一种分析方式。 特点&#xff1a;只有一个自变量的情况称为单变量回归&#xff0c;多于一个自变量情况的叫做多元回归 线…

剑指 Offer (第 2 版)

&#xff08;简单&#xff09;剑指 Offer 03. 数组中重复的数字 找出数组中重复的数字。 在一个长度为 n 的数组 nums 里的所有数字都在 0&#xff5e;n-1 的范围内。数组中某些数字是重复的&#xff0c;但不知道有几个数字重复了&#xff0c;也不知道每个数字重复了几次。请…

python实现图像的平移、镜像、旋转(不调用CV库)

python实现图像的平移、镜像、旋转&#xff08;不调用CV库&#xff09; 老师布置的作业。。。。。 平移图像 图像的平移在几何变换中算是最简单的变换之一&#xff0c;话不多说&#xff0c;直奔主题 由图可知&#xff0c;在opencv中图像的原点一般为左上角&#xff0c;设初始…