排序算法 —— 归并排序(理论+代码)

目录

1.归并排序的认识

归并排序的思想

归并排序动图演示 

2.归并排序的递归实现

归并排序的遍历方式

归并排序的归并流程

归并排序的递归代码实现

3.归并排序的非递归实现

非递归实现分析

边界分析

非递归实现代码

4.归并排序总结

时间复杂度

空间复杂度

稳定性


1.归并排序的认识

归并排序的思想

归并排序是一种建立在归并两个有序数组操作上的排序方法,该排序算法是分治的一个典型应用。其基本思想是:

  • 先将待排序的有序序列的每个元素组成的数组看成是一个待归并的数组。
  • 依次将效相邻的两个数组进行二路归并操作,合成一个更大有序的数组。
  • 然后将得到的更大的且相邻的有序数组再归并成更大的有序数组,直到将整个数组的元素贵宾成有序的。

归并排序动图演示 

2.归并排序的递归实现

归并排序的遍历方式

从上图可以看出,归并排序也是一种类似于二叉树结构的排序算法,所以,归并排序也可以根据二叉树的遍历来实现,那我们采用二叉树的前序、中序还是后续来实现呢?

我们可以分析一下:

  • 归并排序归并的是两个待排序的序列。
  • 如果我们使用前序遍历,第一个待排序的序列就是整个数组中的元素,此时只有一个待排序的序列,无法进行归并。
  • 如果我们使用中序遍历,排完左边的子序列之后,就需要排根,此时的根是两个子序列并集,子序列不符合要求,无法进行排序。
  • 如果我们使用后续遍历,将待排序的序列分为左右子序列之后,再将左右子序列归并,符合要求。

所以,归并排序的递归实现,我们采用后续遍历。

归并排序的归并流程

我们再来看一看归并排序的归并过程:

首先是一个元素和一个元素归并,归并完成之后,两个元素和两个元素归并,然后,四个元素和四个元素归并;不管是几个元素和几个元素归并,核心都是左右两个有序序列进行归并。所以,我们需要先将待排序序列分成一个元素和一个元素归并的情况,然后是两个元素和两个元素归并……

我们可以通过递归来划分左右子序列,代码如下所示:

  • 当 end < begin 的时候,区间不存在,函数退出;当end==begin的时候,此时计算出的mid和end、begin是一样的,说明左右子序列相同,并且只有一个元素,不需要归并。
  • 因为我们的代码中是通过 (end+begin) / 2 来计算mid的,递归到叶子结点的时候,左右子序列一定只有一个值。
  • 于是我们就可以先将一个元素和一个元素进行归并,值得一提的是,一个元素直接就是有序,符合归并有序数组的前提。一个元素和一个元素归并完之后就获得了一个具有两个元素的有序序列。
  • 然后在重复该过程,直到待排序序列有序。

归并排序的递归代码实现

前面说过,我们可以根据begin和end以及begin和end的中间值mid,划分出左子序列和右子序列,然后归并左右子序列即可。

需要注意的是:归并过程需要借助新的数组来完成,这样实现起来比较方便。

void _MergeSort(int* a, int* tmp, int begin, int end)
{if (end <= begin)return;int mid = (end + begin) / 2;// [begin, mid][mid+1, end] _MergeSort(a, tmp, begin, mid); //递归左子树  _MergeSort(a, tmp, mid+1, end); //递归右子树 // 执行归并过程 // 归并到tmp数据组,再拷贝回去// a->[begin, mid][mid+1, end]->tmpint begin1 = begin, end1 = mid;int begin2 = mid+1, end2 = end;int index = begin;while (begin1 <= end1 && begin2 <= end2) // 选小的尾插 {if (a[begin1] < a[begin2]){tmp[index++] = a[begin1++];}else{tmp[index++] = a[begin2++];}}while (begin1 <= end1) //左区间有剩余元素,尾插左区间的剩余元素 {tmp[index++] = a[begin1++];}while (begin2 <= end2) //右区间有剩余元素,尾插右区间的剩余元素 {tmp[index++] = a[begin2++];}// 拷贝回原数组memcpy(a + begin, tmp + begin, (end - begin + 1)*sizeof(int));
}void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}_MergeSort(a, tmp, 0, n - 1);free(tmp);
}

3.归并排序的非递归实现

非递归实现分析

用非递归实现归并排序,和递归实现归并排序相比,不需要进行递归分解,但是同样需要划分左右子序列,只是划分的方式不一样,在非递归中,我们通过一个变量gap来实现,gap为几,子序列中就有几个元素(元素不够gap个的情况后面分析)

注意:用非递归代码实现归并排序,我们同样需要借助一个新的数组空间来完成。

非递归实现归并排序步骤如下:

  1. 开辟新的数组空间。
  2. 定义gap,gap初识为1,表示一个元素和一个元素进行归并。
  3. 将结果归并到新数组中,然后拷贝回原数组。
  4. gap增大为原来的两倍,继续归并到新数组中,归并完之后拷贝回原数组。
  5. 直到gap >= 数据个数,表示不能划分出两组用来归并的子序列了,整个归并排序排完,待排序序列变得有序。

边界分析

我们划分左右子序列边界的时候,左子序列的边界是 [begin1,end1],右子序列边界为[begin2,end2];我们使用变量i来遍历数组,于是四个边界的计算方式如下:

  • begin1 = i   end1 = i+gap-1;begin2 = i+gap  end2 = i+2*gap-1;
  • 变量i的变化为 i += 2*gap。

下图演示了边界控制:

下面,我们讨论越界的情况:

  • 对于begin1来说,begin1 = i,i由我们控制的,i < n(数据个数),所以,begin1是不会越界的。
  • end1,begin2,end2都在i的基础上增加了值,所以,这三个变量是可能会越界的。
  • 当end1越界时,也就是end1 >= n,说明左区间越界,此时,肯定不存在右区间,所以这一趟是无法归并的,直接跳出循环即可。
  • 当begin2越界时,也就是begin2 >= n,说明用于归并的右区间不存在,这种情况和end1越界是一样的,直接跳出循环。
  • 当end2越界,也就是end2 >= n说明右区间存在,但是右区间越界了,修正一下就好了,也就是将end2的赋值为n-1。

边界情况弄清楚之后,代码就很好写了。

非递归实现代码

void MergeSortNonR(int* a, int n)
{// 开辟新的数组空间int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}int gap = 1;while (gap < n){for (int i = 0; i < n; i += 2 * gap){int begin1 = i, end1 = i + gap - 1;int begin2 = i + gap, end2 = i + 2 * gap - 1;// [begin1,end1] [begin2,end2] 归并// 如果第二组不存在,这一组不用归并了if (begin2 >= n){break;}// 如果第二组的右边界越界,修正一下if (end2 >= n){end2 = n - 1;}int index = i;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2]){tmp[index++] = a[begin1++];}else{tmp[index++] = a[begin2++];}}while (begin1 <= end1){tmp[index++] = a[begin1++];}while (begin2 <= end2){tmp[index++] = a[begin2++];}// 拷贝回原数组memcpy(a + i, tmp + i, (end2-i+1) * sizeof(int));}// gap每次扩大二倍 gap *= 2;}free(tmp);
}

4.归并排序总结

时间复杂度

归并排序的时间复杂度分析类似于快速排序,同样属于二叉树结构的排序方式,每一层遍历N次,树的高度为logN。所以,归并排序的时间复杂度为N*logN。

空间复杂度

在归并排序的过程中,无论是递归代码还是非递归代码,都开辟了额外的数组空间,所以,时间复杂度是O(N)。

稳定性

归并排序采用在有序数组中,元素两两比较的方式,可以做到稳定。

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

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

相关文章

Postman使用-基础篇

前言 本教程将结合业界广为推崇和使用的RestAPI设计典范Github API&#xff0c;详细介绍Postman接口测试工具的使用方法和实战技巧。 在开始这个教程之前&#xff0c;先聊一下为什么接口测试在现软件行业如此重要&#xff1f; 为什么我们要学习Postman&#xff1f; 现代软件…

ubuntu 安装keepalived+haproxy

一、安装keepalived sudo apt update sudo apt install keepalived sudo systemctl start keepalived sudo systemctl enable keepalived sudo systemctl status keepalived#配置Keepalived sudo cp /etc/keepalived/keepalived.conf.sample /etc/keepalived/keepalived.conf …

Jmeter 实战 JDBC配置

​ JDBC JDBC&#xff08;Java Database Connectivity&#xff09;是一种用于执行SQL语句的Java API。通过这个API&#xff0c;可以直接连接并执行SQL脚本&#xff0c;与数据库进行交互。 使用JMeter压力测试时&#xff0c;操作数据库的场景 在使用JMeter进行接口压力测试时…

大规模多传感器滑坡检测数据集,利用landsat,哨兵2,planet,无人机图像等多种传感器采集数据共2w余副图像,mask准确标注滑坡位置

大规模多传感器滑坡检测数据集&#xff0c;利用landsat&#xff0c;哨兵2&#xff0c;planet&#xff0c;无人机图像等多种传感器采集数据共2w余副图像&#xff0c;mask准确标注滑坡位置 大规模多传感器滑坡检测数据集介绍 数据集概述 名称&#xff1a;大规模多传感器滑坡检测…

R语言建模线性回归

一、 a. # 给定的 (x, y) 数据 x <- c(2, 9, 10, 7) y <- c(3, 13, 12, 11)# 线性回归模型 y a bx model1 <- lm(y ~ x) summary(model1) # 查看回归结果# 提取系数 a 和 b a <- coef(model1)[1] b <- coef(model1)[2]# 预测值 y_pred <- predict(mode…

数据字典是什么?和数据库、数据仓库有什么关系?

一、数据字典的定义及作用 数据字典是一种对数据的定义和描述的集合&#xff0c;它包含了数据的名称、类型、长度、取值范围、业务含义、数据来源等详细信息。 数据字典的主要作用如下&#xff1a; 1. 对于数据开发者来说&#xff0c;数据字典包含了关于数据结构和内容的清晰…

【C++篇】探索STL之美:熟悉使用String类

CSDN 文章目录 前言 &#x1f4ac; 欢迎讨论&#xff1a;如果你在学习过程中有任何问题或想法&#xff0c;欢迎在评论区留言&#xff0c;我们一起交流学习。你的支持是我继续创作的动力&#xff01; &#x1f44d; 点赞、收藏与分享&#xff1a;觉得这篇文章对你有帮助吗&…

基于SSM+微信小程序的家庭记账本管理系统(家庭1)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 1、管理员端功能有首页、个人中心、用户管理&#xff0c;消费详情管理、收入详情管理、系统管理等。 2、用户端功能有首页、消费详情、收入详情、论坛信息、我的等功能。 2、项目技术 …

python机器人编程——用python调用API控制wifi小车的实例程序

目录 一、前言二、一个客户端的简单实现2.1 首先定义一个类及属性2.2 其次定义连接方法2.3 定义一些回调函数2.4 定义发送小车指令方法2.5 定义一个正常关闭方法 三、python编程控制小车的demo实现四、小结PS.扩展阅读ps1.六自由度机器人相关文章资源ps2.四轴机器相关文章资源p…

【保姆级教程】DolphinScheduler本地部署与远程访问详细步骤解析

文章目录 前言1. 安装部署DolphinScheduler1.1 启动服务 2. 登录DolphinScheduler界面3. 安装内网穿透工具4. 配置Dolphin Scheduler公网地址5. 固定DolphinScheduler公网地址 前言 本篇教程和大家分享一下DolphinScheduler的安装部署及如何实现公网远程访问&#xff0c;结合内…

【牛客刷题】笔记2

目录 1、单词搜索 2、岛屿数量 2.1 DFS 2.2 BFS 3、腐烂的橘子 1、单词搜索 单词搜索_牛客题霸_牛客网 (nowcoder.com) 这道题我们就是先遍历数组board&#xff0c;若遇到了与word[0]相等的字符&#xff0c;则以这个字符为起点进行搜索&#xff0c;搜索可以是dfs&#x…

#每日一题#自动化 2024年10月

#每日一题#自动化 2024年10月 1、深拷贝和浅拷贝的区别是什么&#xff1f; 参考答案&#xff1a; 深拷贝是将对象本身复制给另一个对象。这意味着如果对对象的副本进行更改时不会影响原对象。在 Python 中&#xff0c;我们使用 deepcopy&#xff08;&#xff09;函数进行深拷贝…

MyBatis-Plus(二):resultType 的选择——int 与 java.lang.Integer 的区别

resultType 的选择——int 与 java.lang.Integer 的区别 1、概述2、resultType介绍2.1、使用int2.2、使用java.lang.Integer 3、如何选择4、总结 大家好&#xff0c;我是欧阳方超&#xff0c;可以扫描下方二维码关注我的公众号“欧阳方超”&#xff0c;后续内容将在公众号首发。…

蘑菇分类识别数据集(猫脸码客 第222期)

蘑菇分类识别文本/图像数据集 蘑菇&#xff0c;作为一种广泛分布于全球的真菌&#xff0c;隶属于伞菌目伞菌亚门蘑菇科蘑菇属&#xff0c;拥有众多别名&#xff0c;如白蘑菇、洋蘑菇等。其不仅是世界上人工栽培最广泛、产量最高、消费量最大的食用菌品种之一&#xff0c;还在许…

Java程序设计:spring boot(8)——API ⽂档构建⼯具 - Swagger2

目录 1 环境整合配置 2 Swagger2 常⽤注解说明 2.1 Api 2.2 ApiOperation 2.3 ApiImplicitParams 2.4 ApiResponses 2.5 ApiModel 3 用户模块注解配置 3.1 Controller 使用注解 3.2 JavaBean 使用注解 4 Swagger2 接⼝⽂档访问 由于 Spring Boot 能够快速开发、便捷…

理解JVM

文章目录 前言一、JVM 内存区域划分二、JVM 中类加载的过程a.类加载的基本流程&#xff08;熟练背诵&#xff09;b.双亲委派模型 三、JVM 中的垃圾回收机制&#xff08;GC&#xff09;1.找到垃圾2.如何回收垃圾&#xff1f; 总结 前言 JVM 内部涉及到的内容是非常广泛的。咱们…

【Qt】Qt的介绍——Qt的概念、使用Qt Creator新建项目、运行Qt项目、纯代码方式、可视化操作、认识对象模型(对象树)

文章目录 Qt1. Qt的概念2. 使用Qt Creator新建项目3. 运行Qt项目3.1 纯代码方式实现3.2 可视化操作实现 4. 认识对象模型&#xff08;对象树&#xff09; Qt 1. Qt的概念 Qt 是一个跨平台的 C 图形用户界面应用程序开发框架。它是软件开发者提供的用于界面开发的程序框架&#…

PCC Net模型实现行人数量统计

关注底部公众号&#xff0c;回复暗号&#xff1a;13&#xff0c;免费获取600多个深度学习项目资料&#xff0c;快来加入社群一起学习吧。 项目简介 PCC Net是一种用于拥挤场景下行人计数的深度学习模型。该项目的目标是利用神经网络&#xff0c;准确地统计给定区域内的行人数…

Visual Studio Code

代码自动保存 打开设置搜索auto save&#xff0c;设置为afterDelay 设置延迟时间&#xff0c;单位是毫秒 启用Ctrl鼠标滚轮对字体进行缩放 搜索Mouse Wheel Zoom&#xff0c;把该选项勾选上即可

Linux文件的查找和打包以及压缩

文件的查找 文件查找的用处&#xff0c;在我们需要文件但却又不知道文件在哪里的时候 文件查找存在着三种类型的查找 1、which或whereis&#xff1a;查找命令的程序文件位置 2、locate&#xff1a;也是一种文件查找&#xff0c;但是基于数据库的查找 3、find&#xff1a;针…