[算法]归并排序(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.mzph.cn/web/50558.shtml

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

相关文章

Git基本原理讲解、常见命令、Git版本回退、Git抛弃本地分支拉取仓库最新分支、如何将本地文件推送至github、.gitignore文件的使用

借此机会写篇博客汇总一下自己去公司实习之后遇到的一些常见关于Git的操作。 Git基本认识 Git把数据看作是对小型文件系统的一组快照&#xff0c;每次提交更新&#xff0c;或在Git中保存项目状态时&#xff0c;Git主要对当时的全部文件制作一个快照并保存这个快照的索引。同时…

【ROS 最简单教程 002/300】ROS 环境安装 (虚拟机版): Noetic

&#x1f497; 有遇到安装问题可以留言呀 ~ 当时踩了挺多坑&#xff0c;能帮忙解决的我会尽力 &#xff01; 1. 安装操作系统环境 Linux ❄️ VM / VirtualBox Ubuntu20.04 &#x1f449; 保姆级图文安装教程指路&#xff0c;有经验的话 可以用如下资源自行安装 ITEMREFERENCE…

vue3实战(通用后台管理系统)问题总结

npm install less vue-router element-plus -s elementplus 路由引入组件第二种写法&#xff1a; 使用动态的import( )语法(推荐使用)&#xff08;路由懒加载&#xff09; component:()>import(路径)component:()>import(/views/Main.vue)打包之后的文件将会异常的大&a…

《昇思25天学习打卡营第25天|第28天》

今天是打卡的第二十八天&#xff0c;实践应用篇中的计算机视觉中Vision Transformer图像分类。 从Vision Transformer&#xff08;ViT&#xff09;简介开始了解&#xff0c;模型结构&#xff0c;模型特点&#xff0c;实验的环境准备和数据读取&#xff0c;模型解析&#xff08…

深入探索PHP框架:Symfony框架全面解析

1. 引言 在现代Web开发领域&#xff0c;PHP作为一种广泛使用的服务器端脚本语言&#xff0c;其框架的选择对于项目的成功至关重要。PHP框架不仅能够提高开发效率&#xff0c;还能确保代码的质量和可维护性。本文将深入探讨Symfony框架&#xff0c;这是一个功能强大且灵活的PHP…

Teamcenter RAC开发,创建Item的两种方式

1、如果描述不必填&#xff0c;采用胖客户端的创建方式 newItem itemType.create(newItemId, "", targetTypeComp.getTypeName(), item_name, // "test1", null, null2、如果描述必填&#xff0c;则需要采用SOA的创…

C++11中的右值引用以及移动构造等

目录 一、右值引用 1.左值引用和右值引用 2.左值引用与右值引用比较 3.右值引用使用场景和意义 1️⃣ 传返回值 2️⃣ STL中的应用 4.完美转发 模板中的&& 万能引用&#xff08;引用折叠&#xff09; 二、 新的类功能 1.默认成员函数 2.类成员变量初始化 3.…

线程池学习(一)

1.线程池有什么作用 降低资源消耗&#xff1a;通过池化技术重复利⽤已创建的线程&#xff0c;降低线程创建和销毁造成的损耗。 提⾼响应速度&#xff1a;任务到达时&#xff0c;⽆需等待线程创建即可⽴即执⾏。 提⾼线程的可管理性&#xff1a;线程是稀缺资源&#xff0c;如果…

ProxmoxPVE虚拟化平台--安装PVE虚拟机

Proxmox 虚拟机 Proxmox是一个基于Debian Linux和KVM的虚拟化平台&#xff0c;‌它提供了虚拟化的环境&#xff0c;‌允许用户在同一台物理机上运行多个虚拟机。‌Proxmox虚拟环境&#xff08;‌PVE&#xff09;‌是一个开源项目&#xff0c;‌由Proxmox Server Solutions Gmb…

Power Tower

Problem - D - Codeforces 牛客和codeforce都有 递归处理l,r&#xff0c;终点是lr && mod1 用扩展欧拉定理 // Problem: D. Power Tower // Contest: Codeforces - Codeforces Round 454 (Div. 1, based on Technocup 2018 Elimination Round 4) // URL: https://c…

【Socket 编程】应用层自定义协议与序列化

文章目录 再谈协议序列化和反序列化理解 read、write、recv、send 和 tcp 为什么支持全双工自定义协议网络计算器序列化和反序列化 再谈协议 协议就是约定&#xff0c;协议的内容就是约定好的某种结构化数据。比如&#xff0c;我们要实现一个网络版的计算器&#xff0c;客户端…

关于P2P(点对点)

P2P 是一种客户端与客户端之间&#xff0c;点对点连接的技术&#xff0c;在早前的客户端都是公网IP&#xff0c;没有NAT的情况下&#xff0c;P2P是较为容易实现的。 但现在的P2P&#xff0c;实现上面会略微有一些复杂&#xff1a;需要采取UDP打洞的技术&#xff0c;但UDP打出来…

asp.net mvc 三层架构开发商城系统需要前台页面代完善

一般会后端开发&#xff0c;都不太想写前台界面&#xff0c;这套系统做完本来想开源&#xff0c;需要前台界面&#xff0c;后台已开发&#xff0c;有需求的朋友&#xff0c;可以开发个前端界面完善一下&#xff0c;有的话可以私聊发给我啊

Redis(三)

1. java连接redis java提高连接redis的方式jedis. 我们需要遵循jedis协议。 引入依赖 <!--引入java连接redis的驱动--><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.3.1</version&g…

Android Framework 之AMS

它管理了系统的四大组件:Activity、Service、ContentProvider、Broadcast。 它除了管理四大组件外&#xff0c;同时也负责管理和调度所有的进程 AMS相关目录结构 AMS代码主要在下面几个目录(AndroidQ上AMS相关部分功能移到了wm下)&#xff1a; frameworks/base/core/java/andro…

记录|LabVIEW从0开始

目录 前言一、表达式节点和公式节点二、脚本与公式2.1 公式 三、Excel表格3.1 位置3.2 案例&#xff1a;波形值存入Excel表中3.3 案例&#xff1a;行写入&#xff0c;列写入 四、时间格式化4.1 获取当前时间4.2 对当前时间进行格式化 更新时间 前言 参考视频&#xff1a; LabVI…

【STL】之 vector 使用方法及模拟实现

前言&#xff1a; 本文主要讲在C STL库中vector容器的使用方法和底层的模拟实现~ 成员变量的定义&#xff1a; 对于vector容器&#xff0c;我们首先采用三个成员变量去进行定义&#xff0c;分别是&#xff1a; private:iterator _start; // 指向数据块的开始iterator _finish…

React类组件生命周期与this关键字

类组件生命周期 参考链接 一图胜千言&#xff08;不常用的生命周期函数已隐藏&#xff09; 代码&#xff1a; //CC1.js import { Component } from "react";export default class CC1 extends Component {constructor(props) {super(props);console.log("con…

【Vue3】watchEffect

【Vue3】watchEffect 背景简介开发环境开发步骤及源码 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗的日子。本文…

C++初学(7)

7.1、字符串 字符串是存储在内存的连续字节中的一系列字符。C处理字符串的方式有两种&#xff0c;第一种是来自C语言&#xff0c;常被称为C风格字符串&#xff0c;另一种则是基于string类库的方法。 存储在连续字节中的一系列字符意味着可以将字符存储在char数组中&#xff0…