[算法]归并排序(C语言实现)

一、归并排序的定义      

          归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。 

二、归并排序的算法原理

        归并排序的算法可以用递归法和非递归法来实现,在理解的角度来看,归并排序就是一种递归排序。其将一个数组分成均匀的两份小的数组,然后将其分成的两份各自再分,得到四份小的数组,如此重复,直到所分成的小数组没有元素或者只有一个元素为止,这就是分而治之的。当们把数组分好后,再依次进行归并(将两个有序的小数组归并成一个有序数组),只有一个元素的小数组视为有序,第一次归并后,每个有序数组的元素个数就从一变成了二,再次归并有序数组,每个有序数组的个数就从二变成了四,如此重复,直到有序的数组个数等于原数组的个数,此时整个数组完全有序,完成了排序的工作。

下面是归并排序分而治之的演示图:

        上面这个图长得非常像一个二叉树,其实就是回溯这个二叉树进行归并的过程。而归并两个有序数组的方法非常简单,这里就不进行赘述。下面我来使用递归的方法和非递归的方法来实现递归排序。 

归并排序的动态示意图:

三、归并排序的递归法实现

        递归的方法实现归并排序很简单,就是不断递归左子树和右子树进行归并排序,当左右子树都有序后,就对左子树和右子树进行归并排序即可。为了简单起见,排序整数数组。

具体代码如下:

//归并排序递归法
void _MergeSort(int* arr,int* pTempArr,int leftIndex,int rightIndex)
{//如果区间内没有元素,停止递归//如果区间内只有一个元素,则视为有序,停止递归if (leftIndex >= rightIndex)return;//将当前区间均分为两个区间 //[leftIndex,midIndex]和[midIndex + 1,rightIndex]int midIndex = (leftIndex + rightIndex) / 2;//递归左右子树使左右子树有序,当左右子树有序时进行递归排序_MergeSort(arr, pTempArr, leftIndex, midIndex);//递归左子树_MergeSort(arr, pTempArr, midIndex + 1, rightIndex);//递归右子树//到了这里,说明左右子树有序了//归并两个有序数组[leftIndex,midIndex]和[midIndex + 1,rightIndex]int key = leftIndex;int index1 = leftIndex;int index2 = midIndex + 1;while(index1 <= midIndex && index2 <= rightIndex){if (arr[index1] <= arr[index2])pTempArr[key++] = arr[index1++];elsepTempArr[key++] = arr[index2++];}//归并剩余的元素while(index1 <= midIndex){pTempArr[key++] = arr[index1++];}while (index2 <= rightIndex){pTempArr[key++] = arr[index2++];}//将归并好的有序数据转移到待排序的数组中memcpy(arr + leftIndex, pTempArr + leftIndex, sizeof(int) * (rightIndex - leftIndex + 1));
}//归并排序递归法实现
void MergeSort(int* arr,int nums)//传入数组和数组的大小
{//为归并两个有序数组临时开辟所需要的空间int* pTempArr = (int*)malloc(sizeof(int) * nums);//对数组进行归并排序_MergeSort(arr, pTempArr, 0, nums - 1);//释放临时数组的空间free(pTempArr);
}

四、归并排序的非递归方法实现 

        归并排序推荐使用非递归的方法来实现的,因为递归会出现栈溢出的问题,而非递归的方法就不用在意这个问题,迭代不需要像递归那样开辟大量栈空间。但是非递归的方法实现起来有一点的困难。

在上面的分而治之的图中:

        迭代的方式不需要分而治之,实现归并排序可以跳过的过程,直接,我们从图中可以看到,第一层有序数组进行归并排序时,每个有序数组的元素个数为1;当第二层有序数组进行归并排序时,每个有序数组的元素个数为2;当第三层进行归并排序时,每个有序数组的元素个数为4,我们发现,每次归并排序完成后,其每个将要归并的有序数组的元素个数是上一层的两倍,于是我们可以使用迭代的方式进行递归。

下面我将画图来演示这个过程: 

        归并排序的非递归法需要注意的是数组最后的几组有序数组元素的归并,视情况进行特殊处理。

其情况有以下几种:

1、倒数第二组有序数组和倒数第一组有序数组匹配进行归并。

此时又分为两种情况:

倒数第一组有序数组的个数和倒数第二组的有序数组的个数相同,归并的数组大小是对称的。

倒数第一组有序数组的个数比倒数第二组有序数组的个数少,归并的数组大小不是对称的

2、倒数第二组有序数组和倒数第三组有序数组匹配进行归并,而倒数第一组有序数组没有可以匹配归并的有序数组,此时倒数第一组有序数组不需要进行归并操作。

具体代码如下: 

//归并两个有序数组到新数组中
void MergeArray(int* pTempArr, int* arr,int leftIndex, int midIndex, int rightIndex)
{//归并有序数组[leftIndex,midIndex]和[midIndex + 1,rightIndex]int index1 = leftIndex;int index2 = midIndex + 1;int key = leftIndex;while (index1 <= midIndex && index2 <= rightIndex){if (arr[index1] <= arr[index2])pTempArr[key++] = arr[index1++];elsepTempArr[key++] = arr[index2++];}while (index1 <= midIndex){pTempArr[key++] = arr[index1++];}while (index2 <= rightIndex){pTempArr[key++] = arr[index2++];}
}//归并排序非递归
void MergeSortNonR(int* arr,int nums)
{//为归并有序数组临时开辟所需要的空间int* pTempArr = (int*)malloc(sizeof(int) * nums);int gap = 1; //每组有序数组的元素个数while (gap < nums){int leftIndex = 0; //归并的有序数组的起始位置(下标的左边界)// 每一层归并后的有序数组下标的分组// 0 1 2 3 4 5 6 7 8 9 10// [0 1] [2 3] [4 5] [6 7] [8 9] 10// [0 1 2 3] [4 5 6 7] [8 9 10]// [0 1 2 3 4 5 6 7] [8 9 10]// [0 1 2 3 4 5 6 7 8 9 10]// nums - leftIndex 得到leftIndex以及往后的元素的个数// nums - leftIndex >= 2 * gap 可以保证归并到的有序数组都是对称的while (nums - leftIndex >= 2 * gap){int rightIndex = leftIndex + 2 * gap - 1;int midIndex = (leftIndex + rightIndex) / 2;//归并有序数组[leftIndex,midIndex]和[midIndex + 1,rightIndex]MergeArray(pTempArr, arr, leftIndex, midIndex, rightIndex);//将归并好的元素转移到待排序的数组中memcpy(arr + leftIndex, pTempArr + leftIndex, sizeof(int) * (rightIndex - leftIndex + 1));//归并下两组有序数组leftIndex += 2 * gap; }//处理不对称的归并和没有归并的有序数组可以匹配的情况//如果满足 nums - leftIndex > gap //说明leftIndex以及后面的元素个数大于gap个//需要进行归并排序,只不过归并排序的区间并不对称if (nums - leftIndex > gap){//这里分成的两个区间由于不对称,所以不能使用左右下标相除的方法算出中间下标//归并排序[leftIndex,leftIndex + gap - 1] [leftIndex + gap,nums - 1]MergeArray(pTempArr, arr, leftIndex, leftIndex + gap - 1, nums - 1);memcpy(arr + leftIndex, pTempArr + leftIndex, sizeof(int) * (nums - leftIndex));}//如果剩余元素不足gap个,不需要进行归并排序//进行下一层的归并排序gap *= 2;}//释放临时数组的空间free(pTempArr);
}

五、总结 

        归并排序的时间复杂度为O(nlogn),空间复杂度为O(N),因为归并有序数组需要额外开辟空间,所以其排序的性能仅次于快排,但是归并排序稳定。

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

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

相关文章

介绍一下TCP/IP 模型和 OSI 模型的区别

OSI 模型是由国际标准化组织制定的一个用于计算机或通信系统间互联的标准体系&#xff0c;一共有七层&#xff0c;由上而下分别为应用层&#xff0c;表示层&#xff0c;会话层&#xff0c;传输层&#xff0c;网络层&#xff0c;数据链路层和物理层&#xff0c;虽然 OSI 模型理论…

华为网络模拟器eNSP安装部署教程

eNSP是图形化网络仿真平台&#xff0c;该平台通过对真实网络设备的仿真模拟&#xff0c;帮助广大ICT从业者和客户快速熟悉华为数通系列产品&#xff0c;了解并掌握相关产品的操作和配置、提升对企业ICT网络的规划、建设、运维能力&#xff0c;从而帮助企业构建更高效&#xff0…

Geoscene Pro的数据管理

GeoScene Pro是为新一代WebGIS平台而全新打造的一款具有高效、强大生产力且为全面国产的的高级桌面应用程序&#xff0c;可以对来自本地、GeoScene Online、或者GeoScene Portal的数据进行可视化、编辑、分析&#xff0c;可以同时在2D和3D中制作内容&#xff0c;并发布为要素服…

医疗器械维修行业发展及趋势

医疗器械维修的前景是广阔的。‌ 随着医疗技术的不断发展和进步&#xff0c;‌医疗器械的种类和数量持续增加&#xff0c;‌对专业维修人员的需求也在不断上升。‌无论是医院、‌诊所等医疗机构&#xff0c;‌还是医疗器械生产企业、‌销售企业等&#xff0c;‌都需要专业的维修…

Spark+实例解读

第一部分 Spark入门 学习教程&#xff1a;Spark 教程 | Spark 教程 Spark 集成了许多大数据工具&#xff0c;例如 Spark 可以处理任何 Hadoop 数据源&#xff0c;也能在 Hadoop 集群上执行。大数据业内有个共识认为&#xff0c;Spark 只是Hadoop MapReduce 的扩展&#xff08…

C语言常见字符函数和字符串函数精讲

目录 引言 一、字符函数 1.字符分类函数 2.字符转换函数 二、字符串函数 1.gets、puts 2.strlen 3.strcpy 4.strncpy 5.strcat 6.strncat 7.strcmp 8.strncmp 9.strstr 10.strchr 11.strtok 12.strlwr 13.strupr 引言 在C语言编程中&#xff0c;字符函数…

Rancher 快照备份至 S3 及备份恢复

AWS S3&#xff08;Simple Storage Service&#xff09;是亚马逊云服务提供的一种高度可扩展、安全且经济高效的对象存储服务。它允许用户在任何位置存储和检索任意数量的数据,非常适合存储和分发静态文件、备份数据以及作为数据湖的存储层。 集群备份 一、创建S3桶 1、登录…

PyTorch学习(1)

PyTorch学习&#xff08;1&#xff09; CIFAR-10数据集-图像分类 数据集来源是官方提供的&#xff1a; torchvision.datasets.CIFAR10()共有十类物品&#xff0c;需要用CNN实现图像分类问题。 代码如下&#xff1a;(CIFAR_10_Classifier_Self_1.py) import torch import t…

【Linux】玩转操作系统,深入刨析进程状态与调度机制

目录 1. 进程排队2. 进程状态的表述2.1. 进程状态2.2 运行状态2.3. 阻塞状态2.4. 挂起状态 3. Linux下具体的进程状态3.1. 运行状态R3.2. 可中断睡眠状态S3.3. 不可中断睡眠状态D3.4. 停止状态T3.5. 死亡状态X3.6. 僵尸状态Z 4. 孤儿进程5. 优先级6. Linux的调度与切换6.1. 四个…

打破自闭症束缚:儿童康复案例揭秘

在浩瀚的康复领域中&#xff0c;有这样一所机构&#xff0c;它如同温暖的阳光&#xff0c;穿透自闭症的阴霾&#xff0c;为无数家庭带来了希望与光明。这&#xff0c;就是星启帆——国内规模较大的全寄宿制自闭症儿童康复机构&#xff0c;一个专注于中重度广泛性发育障碍儿童康…

ffmpeg更改视频的帧率

note 视频帧率调整 帧率(fps-frame per second) 例如&#xff1a;原来帧率为30&#xff0c;调整后为1 现象&#xff1a;原来是每秒有30张图像&#xff0c;调整后每秒1张图像&#xff0c;看着图像很慢 实现&#xff1a;在每秒的时间区间里&#xff0c;取一张图像…

MySQL之视图和索引

新建数据库 插入数据 处理表 1. 2. 3. mysql> alter table sc add unique index SC_INDEX (sno asc,cno asc); 4. mysql> create view stu_info as select student.sno,ssex,sc.cno,score from student join sc on student.snosc.sno; 5. mysql> drop index S…

JavaScript——变量与运算符、输入输出、判断、循环

文章目录 前言概述使用 js从文件引入 js 代码importjs 的作用变量计算输入格式化输出保留小数向上取整&#xff0c;向下取整条件判断循环总结 前言 为了监督自己的进度&#xff0c;把学习任务一点点都写出来&#xff0c;写多少就算多少&#xff0c;不求完美&#xff0c;只求完…

Adobe正通过数字体验改变世界

在当今这个数字化飞速发展的时代&#xff0c;Adobe公司正以其创新的技术和卓越的产品引领着创意设计领域的变革。从Adobe发布的生成式AI工具&#xff08;Adobe Firefly&#xff09;&#xff0c;到Illustrator和Photoshop的新AI功能&#xff0c;再到广受认可的Adobe国际认证&…

架构师第二周作业

目录 1.总结Dockerfile的指令和Docker的网络模式 1.1 Dockerfile指令 1.1.1 FROM &#xff1a;指定基础镜像&#xff0c;必须放在Dockerfile文件第一个非注释行 1.1.2 LABEL : 指定镜像元数据&#xff0c;如&#xff1a;镜像作者等 1.1.3 RUN &#xff1a;执行shell命令 1…

Python编程入门指南:从基础到高级

Python编程入门指南&#xff1a;从基础到高级 一、Python编程语言简介 1. Python是什么&#xff1f; Python是一门广泛使用的计算机程序编程语言&#xff0c;由荷兰人吉多范罗苏姆&#xff08;Guido van Rossum&#xff09;于1991年首次发行。Python是一种解释型、交互式、面…

抖音短视频seo矩阵系统源代码搭建---基于PHP语言开发部署

随着短视频市场的爆发式增长&#xff0c;越来越多的企业开始寻求在短视频领域建立自己的品牌形象&#xff0c;增加用户粘性和获取更多流量。为此&#xff0c;一套高效的抖音短视频seo矩阵系统源代码显得尤为重要。本文将介绍基于PHP语言的抖音短视频seo矩阵系统源代码开发&…

数据结构(5):树和二叉树

1 树的定义 1.1 树的基本概念 分支可以称为边&#xff0c;结点可以用于存放数据结构。 除了根节点&#xff0c;其他节点只有一个前驱&#xff01;&#xff01;&#xff01;&#xff01; 互不相交也就是 只有一个前驱结点&#xff01; 树应用的很广的 1.2 结点之间的关系 直接…

Infuse Pro for Mac全能视频播放器

Mac分享吧 文章目录 效果一、下载软件二、开始安装1、双击运行软件&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕2、应用程序显示软件图标&#xff0c;表示安装成功 三、运行测试安装完成&#xff01;&#xff01;&#xff01; 效果 一、下载软件 下载软件…

什么是公司自建企业邮箱?自建企业邮箱有什么用?

什么是公司自建企业邮箱&#xff1f;公司自建企业邮箱有什么用途&#xff1f;一是品牌统一&#xff1b;二是安全性增强&#xff1b;三是定制化功能&#xff1b;四是控制与灵活性等等。哪些企业适合自建企业邮箱呢&#xff1f;本篇文章将为您一一解释。 一、什么是公司自建企业…