目录

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

一、什么是归并排序?

归并排序,是创建在归并操作上的一种有效的排序算法。算法是采用分治法(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).

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

归并排序(递归+非递归)相关推荐

  1. 排序算法杂谈(三) —— 归并排序的非递归实现

    1. 递归 在众多排序算法中,归并排序(Merge Sort)和快速排序(Quick Sort)都是时间复杂度为 O(nlog2n) 的高效排序. 这两种排序有一种共性,就是运用到了递归的思想. 在程 ...

  2. 归并排序详解(递归+非递归)

    目录 归并排序 归并排序的思想 递归实现 非递归实现 归并排序 归并排序和之前讲的快速排序.希尔排序.堆排序一样,时间复杂度是O(N*logN). 它相对难理解一点,接下来我就从递归以及非递归两个方面 ...

  3. 一文彻底搞定二叉树的前序、中序、后序遍历(图解递归非递归)

    前言 大家好,我是bigsai,在数据结构与算法中,二叉树无论是考研.笔试都是非常高频的考点内容,在二叉树中,二叉树的遍历又是非常重要的知识点,今天给大家讲讲二叉树的层序遍历. 这部分很多人可能会但是 ...

  4. java 建树源码_Java实现的二叉树常用操作【前序建树,前中后递归非递归遍历及层序遍历】...

    import java.util.ArrayDeque; import java.util.Queue; import java.util.Stack; //二叉树的建树,前中后 递归非递归遍历 层序 ...

  5. 快排递归非递归python_Python递归神经网络终极指南

    快排递归非递归python Recurrent neural networks are deep learning models that are typically used to solve ti ...

  6. c语言折半查找递归程序,C语言数据结构中二分查找递归非递归实现并分析

    C语言数据结构中二分查找递归非递归实现并分析 前言: 二分查找在有序数列的查找过程中算法复杂度低,并且效率很高.因此较为受我们追捧.其实二分查找算法,是一个很经典的算法.但是呢,又容易写错.因为总是考 ...

  7. 二叉树的深度(前序 中序 后序 递归非递归搜素)、广度、搜索 C++

    a b c 使用 1 2 3 表示 /* 描述:二叉树的深度(前序 中序 后序 递归非递归搜素).广度.搜索 作者:jz 日期:20140819 */ #include<stdio.h> ...

  8. 二叉树——中序遍历(递归/非递归)

    中序遍历,即遍历顺序为:左节点.根节点.右节点. 二叉树节点: public class Node {public Node left;public Node right;public int val ...

  9. 树的深度 递归非递归实现

    树的深度 递归非递归实现 package com.dugstudio.SwordToOffer;import java.util.LinkedList;/*** 树的深度递归和非递归算法*/ clas ...

最新文章

  1. Spark1.0.0 开发环境高速搭建
  2. [转载]Python爬虫入门三之Urllib库的基本使用
  3. linux lvm 镜像,LVM实现逻辑卷镜像
  4. python方法_Python中的通用__eq __()方法
  5. linux移动文件 rf参数_linux下文件的复制、移动与删除命令为:cp,mv,rm
  6. 分享干货:靠刷算法题,真的可以刷进大厂吗?
  7. 响应式布局---菜单
  8. 超好用epub阅读器分享
  9. 教你如何下载并破解IAR
  10. 维宏控制卡四轴那个好_维宏维鸿四轴真四轴联动雕刻机运动控制卡说明书.doc...
  11. php在线翻译,PHP 在线翻译函数代码
  12. 科普下Tier1,Tier2,Tier3,Tier4 T1, T2, T3, T4
  13. android 上拉抽屉,Flutter上拉抽屉实现
  14. JAVA:18位身份证号码验证工具类(识别性别和生日、计算年龄)
  15. 图像处理——使用模板对矩阵进行中值滤波(中值滤波模板)
  16. 港科夜闻|罗康锦教授获委任为香港科大工学院院长
  17. jquery 遍历java对象_jquery遍历数组、对象
  18. 计算机b s架构模式图,深入理解B/S架构
  19. 搜索引擎优化-黑帽技术
  20. GBoost 和 xGBoost

热门文章

  1. 没钱去巴西? 谷歌地图街景带你游览比赛场馆
  2. AD导出PDF问题解决办法
  3. Linux文件数量统计命令
  4. bus hound 使用介绍
  5. 一致性哈希算法的原理与实现
  6. 计算机原理-浮点数存储
  7. 2018年又传喜报!热烈祝贺王家林大师大数据经典著作《Spark SQL大数据实例开发教程》 畅销书籍 出版上市!
  8. 2019年 网络空间安全国赛真题 赛题分析
  9. 按键精灵打怪学习-窗口绑定保护技能和点击技能
  10. VB中Recordset的clone方法