都2023年了,听说你还不会归并排序?手把手教你手写归并排序算法

计算机考研408真题解析(2023-10 排序算法稳定性深度解析) 本文针对2023计算机考研408真题中关于排序算法稳定性的考查,深入剖析了希尔排序、归并排序、快速排序、堆排序和基数排序的稳定性特性。通过C语言代码实现与测试,直观展示了不稳定排序算法的原理,旨在帮助读者透彻理解算法稳定性,并有效应对相关考题。 阅读详情

👇👇关注后回复 “进群” ,拉你进程序员交流群👇👇

作者丨超悦人生

来源丨超悦编程

本文介绍了归并排序的基本思想,递归方法的一般写法,最后一步步手写归并排序,并对其性能进行了分析。

基本思想

归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。即先使每个子序列有序,再将已有序的子序列合并,得到完全有序的序列。这里给出一种递归形式的归并排序实现。

递归方法的一般写法

递归方法的书写主要有三步:

  1. 明确递归方法的功能边界;

  2. 得到递归的递推关系;

  3. 给定递归的终止条件。

递归方法均可按照这三步进行,切忌不要陷入递归实现的细节中。下面以归并排序算法的书写为例,来谈一下递归方法的具体写法。

手写归并排序

首先,明确递归方法的功能,这里我们定义方法的功能为,给定一个数组及左右边界,方法完成数组边界内元素的排序,如下:

private static void mergeSort(int[] arr,int left,int right);

先假设我们已经有了这么一个方法,不用管具体的实现。
接着,寻找递推关系,什么是递推关系呢?就是如何由子问题的求解,来得到原问题的求解,还是举例说明,有如下的数组
 

f8217534c3f44c750e7a977ba9eeb642.png
原始数组

我们将其拆分为左右两部分,如下
6032efcf262d36e41ac4972409d035f3.png
拆分数组

递推关系就是,假如左右两部分都已经有序了,如何使整个数组有序?这个问题其实就是给定了一个数组,数组的左半部分有序,右半部分也有序,如何使整个数组有序?
首先,定义两个指针,分别指向左侧部分起始位置和右侧部分起始位置,同时创建一个辅助数组和指向其初始位置的辅助指针
078efa2ffde4ff3056c0d9cd8314e0ed.png
定义指针及辅助数组

接着比较,左指针和右指针所对应的元素的大小,较小的元素填充至辅助数组,同时其对应的指针和辅助指针均加1,如下:
568ada7b1b46a5a482a95a3acfff913b.png
比较并填充辅助数组

依次进行,直至某左指针指向中间位置或者右指针指向数组的末尾,此时要将将剩余的元素填充至辅助数组。所有的元素填充完成后,再将辅助数组中的元素填充回原数组即可。具体的代码如下:
/**
     *
     * @param arr 要合并的数组
     * @param left 左边界
     * @param mid 中间的分界
     * @param right 右边界
     */
    private static void merge(int[] arr,int left,int mid,int right){
        int[] helpArr = new int[right - left + 1];//首先定义一个辅助数组
        int lPoint = left;//左指针
        int rPoint = mid  + 1;//右指针
        int i = 0;//辅助指针
        while(lPoint <= mid && rPoint <= right){//比较并填充辅助数组
            if(arr[lPoint] <=  arr[rPoint])
                helpArr[i++] =  arr[lPoint++];
            else
                helpArr[i++] =  arr[rPoint++];
        }
        while(lPoint <= mid){//将剩余元素填充至辅助数组
            helpArr[i++] =  arr[lPoint++];
        }
        while(rPoint <= right){
            helpArr[i++] =  arr[rPoint++];
        }
        for(int j = 0;j < helpArr.length;j ++){//将辅助数组中的元素回填至原数组
            arr[left + j] = helpArr[j];
        }
    }

最后,确定终止条件,一般是数组为空或者数组中只有一个元素,返回即可。
现在我们可以写出整个归并排序的代码了,如下:

private static void mergeSort(int[] arr,int left,int right){
        if(arr == null || right == left)//终止条件
            return ;
        int mid = left + (right - left) / 2;//确定分割的边界
        mergeSort(arr,left,mid);//对左半部分调用递归方法,使其有序
        mergeSort(arr,mid + 1,right);//对右半部分调用递归方法,使其有序
        merge(arr,left,mid,right);//合并左右两部分,使整个数组有序
    }

为了保证形式的统一,再对函数进行一下封装,如下,这就是我们的归并排序了。

/**
     * 归并排序算法
     * @param arr
     */
    public static void mergeSort(int[] arr){
        mergeSort(arr,0,arr.length - 1);//调用写好的递归版归并排序方法
    }

至此,我们便完成了归并排序算法的代码实现。

性能分析

在分析归并排序算法性能之前,先介绍几个基础的概念。
 时间复杂度:一个算法执行所消耗的时间;
空间复杂度:运行完一个算法所需的内存大小;
原地排序:在排序过程中不申请多余的存储空间,只利用原来存储待排数据的存储空间进行比较和交换的数据排序。
非原地排序:需要利用额外的数组来辅助排序。
稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 仍然在 b 的前面,则为稳定排序。
非稳定排序:如果 a 原本在 b 的前面,且 a == b,排序之后 a 可能不在 b 的前面,则为非稳定排序。
下面我们分析下归并排序算法的性能。
首先是时间复杂度。归并排序算法在排序时首先将问题进行分解,然后解决子问题,再合并,所以总时间=分解时间+解决子问题时间+合并时间。分解时间就是把一个数组分解为左右两部分,时间为一常数,即O(1);解决子问题时间是两个递归方法,把一个规模为n的问题分成两个规模分别为n/2的子问题,时间为2T(n/2);合并时间复杂度为O(n)。所以总时间T(n)=2T(n/2)+O(n)。这个递归问题的时间复杂度可以用下面的公式来计算

77bb8e1d0d579dffe08f38220addc98c.png
递归函数的时间复杂度计算公式

这个公式可针对形如:T(n) = aT(n/b) + f(n)的递归方程进行时间复杂度求解。带入可知,归并排序的时间复杂度为O(nlogn)。此外在最坏、最佳、平均情况下归并排序时间复杂度均为O(nlogn)。
空间复杂度分析:在排序过程中使用了一个与原数组等长的辅助数组,估空间复杂度为O(n)。
稳定性分析:由排序过程可以知道,归并排序是一种稳定排序。
是否原地排序:排序过程中用到了辅助数组,所以是非原地排序。
本文源码已同步至github,

地址:

https://github.com/zhanglianchao/AllForJava/tree/master/src/algorithm/sort

-End-

最近有一些小伙伴,让我帮忙找一些 面试题 资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!

57b282e6b1a27db50ca533321f32a9e4.png

点击👆卡片,关注后回复【面试题】即可获取

在看点这里110dd9b1a2521ab888679482b8a614df.gif好文分享给更多人↓↓

暨南大学2023830答案 暨南大学2023830真题答案【newbing+本人核对版】 阅读详情

相关推荐

2023GESP12月认证C++五级真题解析

例如,如果a=4,那么4是最⼩的幸运数,而1不是,但我们可以连续对1做3次+1操作,使其变为4,所以我们可以说,1幸运化后的结果是 4。【题目大意】给一个完全平方数的标准,推导出哪些是超级幸运数,然后判断N个数字中,哪些是超级幸运数,是的输出“lucky”,不是的输出离该数字最近的比它大的数字。前一个数字,下标是j的数字是偶数,后面的数字下标是j+1的是奇数,按照要求,偶数在奇数的后面,要交换。A函数时间复杂度是O(n/2),B函数算法是O(sqrt(n)),大部分情况后者是优的,值更小。

lan_in的博客 1270

归并排序 java_手把手你写归并排序算法 (Java代码)

本文介绍了归并排序的基本思想,递归方法的一般写法,最后一步步手写归并排序,并对其性能进行了分析。基本思想归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。即先使每个子序列有序,再将已有序的子序列合并,得到完全有序的序列。这里给出一种递归形式的归并排序实现。递归方法的一般写法递归方法的书写主要有三步:明确递归方法的功能边界;得到递归的递推关系;给定递归...

weixin_39684052的博客 318

【愚公系列】十一大排序算法(七)-归并排序

排序算法是一种将一组数据按照特定的规则进行排列的方法。排序算法通常用于对数据的处理,使得数据能够更容易地被查找、比较和分析。下面是常见的11种排序算法:冒泡排序(Bubble Sort):比较相邻的元素,如果前面的元素大于后面的元素,就交换这两个元素的位置。时间复杂度为O(n^2)。选择排序(Selection Sort):在未排序的数据中找到最小(大)的元素,放置在已排序的数据末尾。时间复杂度为O(n^2)。

愚公智库 48万+

手写归并排序算法

归并排序算法思路 从小到大排序时 归并排序算法原理: 归并排序利用了分治的思想,采用递归来实现 如果要排序一个数组,先把数组从中间分成前后2部分,然后,对前后两部分分别排序 再将排好序的两部分合并在一起,这样整个数组就有序了。 递推公式: 1. mergeSort(p,r) = merge(mergeSortCore(p,q),mergeSortCore(q+1,r)); 2. q = (p+r)/2; 3. 终止条件:p>=r 不用继续分解 4. p: 每段的起始下标 5. r: 每段的末尾下标

腾蛟起凤吕学士 1170

算法手写归并排序

我们将数组一直分割,然后排序之后合并,如此,得到递推关系,具体排序方法,对左右两个数组的排序,我们只用定义两个指针,分别指向左侧部分的起始位置和右侧部分的起始位置,同时建立一个空数组和一个辅助指针,比较左指针和右指针所对应的元素的大小,较小的元素填充至空数组,同时其对应的指针和辅助指针均加1。merge函数是建立一个新的数组,存放比较后的数组,最后将其放回到nums数组之中。具体实现还是挺简单的。首先是将需要排序的数组分割开来,拆分为左右两部分,分解为两个子问题。2,得到递归的递推关系。

qq_52485934的博客 258

归并排序(面试手写版)

#include &lt;iostream&gt; #include &lt;vector&gt; using namespace std; void merge(vector&lt;int&gt;&amp; A, vector&lt;int&gt; L, vector&lt;int&gt; R) { int l = L.size(); int r = R.size(); ...

Nick 908

手写归并排序

public class MergeSort { public void mergeSort(int[] arr){ if(arr==null||arr.length<2){ return; } process(arr,0,arr.length-1); } private void process(int[] arr, int l, int r) { if(l==r){

qq_45283095的博客 413

CCF-GESP 等级考试 20239月认证C++五级真题解析

CCF-GESP 等级考试 20239月认证C++五级真题解析

yuyanjingtao的博客 1940

归并排序(看了就会)

归并排序,是创建在归并操作上的一种有效的排序算法。上图中首先把一个未排序的序列从中间分割成2部分,再把2部分分成4部分,依次分割下去,直到分割成一个一个的数据,再把这些数据两两归并到一起,使之有序,不停的归并,最后成为一个排好序的序列。不管元素在什么情况下都要做这些步骤,所以花销的时间是不变的,所以该算法的最优时间复杂度和最差时间复杂度及平均时间复杂度都是一样的为:O( nlogn )归并排序算法中,归并最后到底都是相邻元素之间的比较交换,并不会发生相同元素的相对位置发生变化,故是稳定性算法

Circ 2万+

CCF-GESP 等级考试 20239月认证C++六级真题解析

CCF-GESP 等级考试 20239月认证C++六级真题解析

yuyanjingtao的博客 2030

有需要2025参加蓝桥杯比赛的同学往下看!!!

除了常见的枚举、排序(如归并排序、快速排序、桶排序、堆排序、基数排序等)和搜索(如剪枝、双向BFS、记忆化搜索、迭代加深搜索、启发式搜索等)之外,还增加了对贪心算法的考察。20242023的基础上,增加了对高级动态规划、高级数据结构、图论、计算几何、概率论与博弈论等知识点的考察,对选手的知识储备和解题能力提出了更高的要求。(一)基础算法基础算法是竞赛的基石,包括枚举、排序(如冒泡排序、选择排序、插入排序等)、搜索(如BFS和DFS)、贪心算法以及模拟。(六)二分查找二分查找算法是竞赛中的常见考点。

DaiZongFuUp的博客 3971

2023 9 月认证 C++ 六级真题解析

全班所有同学之间都会进行握手,因为每位同学来到室时,都会发现他的学号 是当前室里最大的,所以他需要和室里的每位其他同学进行握手。左右子树的层差小于等于。有 n 个学生,假设第 i 个学生进入师,找出前 1~i-1 个学生,哪些人的学号比 自己小,小的需要握手,贡献值加 1。0 号同学进入室,此时室里有 1,2,3 号同学。输入 n 个整数,每个数字分别和前面的数字进行比较,如果大于,贡献值加 1。【解析】本题考察的知识点是数据结构中的哈夫曼,哈夫曼树左边的边权是用 0 来表示,右边的边权值是。

weixin_42358969的博客 1587

CCF-GESP 等级考试 202312月认证C++五级真题解析

CCF-GESP 等级考试 202312月认证C++五级真题解析

yuyanjingtao的博客 1835

2023 12 月认证 C++ 五级真题解析

快速排序是选 定一个数字,每次把比它小的放在左边,比元素大的放在右边,不能确定最值。⽤于将字符串保存到带头节点的双向链表中,并对重复的串 计数,然后将最新访问的串的节点放在链头便于查找。【题目大意】给一个完全平方数的标准,推导出哪些是超级幸运数,然后判断 N 个数字中,哪些是超级幸运数,是的输出“每次保存对应位和的最低位数字,去掉最低位 数字后,保持进位,循环执行。冒泡排序,相邻的数据交换,而且修改节点链 的操作不会改变复杂度。fiboA 是很好理解的,但是执行效率不高,有的计算是重复的,导致效 率低。

weixin_42358969的博客 1202

2023第十四届蓝桥杯软件赛省赛总评

第十四届蓝桥杯软件赛总评

罗勇军的博客 1万+

深入浅出归并排序:从初始归并段说起

归并排序是一种采用分治思想的排序算法,通过将数据拆分为初始归并段(有序子序列)并逐步合并实现整体排序。其过程包括分解(递归二分)和合并(两两归并)两个阶段,时间复杂度稳定为O(nlogn)。算法优点包括稳定性和一致性,缺点是需要额外存储空间(O(n))。实际应用中需注意缓冲区管理,如真题案例展示的初始归并段划分与合并策略。

weixin_63281888的博客 702

基于元素小组的归并排序算法

本文介绍了一种针对固定大小元素小组的归并排序算法,适用于需要根据小组的第一个元素进行排序的场景,如存储多条线段信息。通过使用单个列表而非多个数组或对象来存储线段属性,可有效节省内存并简化遍历操作。文章详细阐述了归并排序的原理,并提供了一个抽象类 `GroupMergeSort` 和其实现子类 `VScanLineGroupMergeSort`,后者支持升序排序。通过示例代码展示了如何实现和测试该算法

我只管埋头前行,剩下的交给时间。 402

数据结构算法】十大经典排序算法-归并排序

归并排序稳定且适用于各种数据类型。具有稳定的时间复杂度O(n log n),适用于大规模数据排序。分治思想使其易于理解和实现,同时也为优化提供了空间。

HelloCode 916
上一篇: 2022全球程序员薪资排行:中国倒数
下一篇: 因为是程序员,被亲戚鄙视了!
程序员大咖
博客等级 码龄9年 3599粉丝 218原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值