归并排序

归并排序是分而治之的排序算法。

划分步骤很简单:将当前数组分成两半(如果N是偶数,则将其完全平等,或者如果N是奇数,则一边稍大于一个元素),然后递归地对这两半进行排序。

递归写法

归并排序递归写法的思想是,设定一个函数,函数实现的目的是让int[] arr在L ~ R位置上有序,处理过程是从L ~ R上找一个中间位置M,递归调用该函数,让int[] arr的L ~ M上有序,M+1 ~ R上有序,每一次不能往下递归了,便调用归并的方法将左右两边的数组合并成一个数组,到最后整个数组便有序了。

因此,归并排序使用递归方法实现的方法是:整体是递归,左边排好序+右边排好序+merge让整体有序

伪代码理解这一过程:

将每个元素拆分成大小为1的部分递归地合并相邻的两个数组分区i = 左侧开始项指数 到 右侧最后项指数 的遍历(两端包括)如果左侧首值 <= 右侧首值拷贝左侧首项的值否则: 拷贝右侧部分首值将元素拷贝进原来的数组中

代码实现:

public class MergeSort {public static void main(String[] args) {int[] arr = {18, 15, 13, 17, 6, 20, 15, 9};System.out.println("排序前:" + Arrays.toString(arr));mergeSort(arr);System.out.println("排序后:" + Arrays.toString(arr));}public static void mergeSort(int[] arr) {if (arr == null || arr.length < 2) {return;}process(arr, 0, arr.length - 1);}public static void process(int[] arr, int L, int R) {if (L == R) {return;}int M = L + ((R - L) >> 1);System.out.println("递归调用 L--M--R:" + L + "--" + M + "--" + R);//左边数组递归process(arr, L, M);//右边数组递归process(arr, M + 1, R);merge(arr, L, M, R);}public static void merge(int[] arr, int L, int M, int R) {System.out.println("开始归并 arr[" + L + "~" + M + "]和arr[" + (M + 1) + "~" + R + "]两部分数组");//申请一个和arr长度一样的辅助数组int[] help = new int[R - L + 1];//比较两组数组,谁小先拷贝谁到辅助数组,拷贝之后移动数组指针//定义数组指针,LP表示左部分数组指针,RP表示右部分数组指针,i表示辅助数组的指针int LP = L;int RP = M + 1;int i = 0;//左右两边数组均不能越界while (LP <= M && RP <= R) {help[i++] = arr[LP] <= arr[RP] ? arr[LP++] : arr[RP++];}//任何一边的数组要越界了,就把该部分的数写到help数组while (LP <= M) {help[i++] = arr[LP++];}while (RP <= R) {help[i++] = arr[RP++];}//写回到原数组for (i = 0; i < help.length; i++) {arr[L + i] = help[i];}}
}

小技巧:

  • 将一个int类型的数乘以2,可以使用位运算<<左移1位
  • int类型的数除以2,位运算>>右移1位

别问为什么,问就是位运算就是快!

运行结果:

排序前:[18, 15, 13, 17, 6, 20, 15, 9]
递归调用 L--M--R:0--3--7
递归调用 L--M--R:0--1--3
递归调用 L--M--R:0--0--1
开始归并 arr[0~0]和arr[1~1]两部分数组
递归调用 L--M--R:2--2--3
开始归并 arr[2~2]和arr[3~3]两部分数组
开始归并 arr[0~1]和arr[2~3]两部分数组
递归调用 L--M--R:4--5--7
递归调用 L--M--R:4--4--5
开始归并 arr[4~4]和arr[5~5]两部分数组
递归调用 L--M--R:6--6--7
开始归并 arr[6~6]和arr[7~7]两部分数组
开始归并 arr[4~5]和arr[6~7]两部分数组
开始归并 arr[0~3]和arr[4~7]两部分数组
排序后:[6, 9, 13, 15, 15, 17, 18, 20]

递归函数调用过程,我画了个简图以助理解:

拿代码中的数组分析,过程大概就是这样子滴:

非递归写法

任何递归写法都能转换成非递归写法。

直接上代码:

public static void mergeSort2(int[] arr) {if (arr == null || arr.length < 2) {return;}//数组长度int N = arr.length;//定义每部分参与比较数组的长度,初始长度为1int mergeSize = 1;//只要mergeSize小于Nwhile (mergeSize < N) {int L = 0;while (L < N) {int M = L + mergeSize - 1;if (M >= N) {break;}int R = Math.min(M + mergeSize, N - 1);merge(arr, L, M, R);L = R + 1;}// 为什么需要这个?主要是为了防止溢出,int的最大值是21亿多(2^31-1),// 假如此时mergeSize是20亿,运行下面mergeSize*2的时候就会溢出if (mergeSize > N / 2) {break;}mergeSize <<= 1;}
}

其中的merge方法,还是前面递归方式调用的merge:

public static void merge(int[] arr, int L, int M, int R) {System.out.println("开始归并 arr[" + L + "~" + M + "]和arr[" + (M + 1) + "~" + R + "]两部分数组");//申请一个和arr长度一样的辅助数组int[] help = new int[R - L + 1];//比较两组数组,谁小先拷贝谁到辅助数组,拷贝之后移动数组指针//定义数组指针,LP表示左部分数组指针,RP表示右部分数组指针,i表示辅助数组的指针int LP = L;int RP = M + 1;int i = 0;//左右两边数组均不能越界while (LP <= M && RP <= R) {help[i++] = arr[LP] <= arr[RP] ? arr[LP++] : arr[RP++];}//任何一边的数组要越界了,就把该部分的数写到help数组while (LP <= M) {help[i++] = arr[LP++];}while (RP <= R) {help[i++] = arr[RP++];}//写回到原数组for (i = 0; i < help.length; i++) {arr[L + i] = help[i];}
}

运行结果:

排序前:[18, 15, 13, 17, 6, 20, 15, 9]
开始归并 arr[0~0]和arr[1~1]两部分数组
开始归并 arr[2~2]和arr[3~3]两部分数组
开始归并 arr[4~4]和arr[5~5]两部分数组
开始归并 arr[6~6]和arr[7~7]两部分数组
开始归并 arr[0~1]和arr[2~3]两部分数组
开始归并 arr[4~5]和arr[6~7]两部分数组
开始归并 arr[0~3]和arr[4~7]两部分数组
排序后:[6, 9, 13, 15, 15, 17, 18, 20]

这里只是归并排序的非递归写法,思想也是分而治之!关键还是merge方法。

针对代码中的数组int[] arr={18, 15, 13, 17, 6, 20, 15, 9},其排序过程动图演示:

归并排序的时间复杂度

Level 0:2 ^ 0 = 1次调用merge( ) 和 N / 2 ^ 1个元素,时间:O(2 ^ 0 x 2 x N / 2 ^ 1)= O(N)

Level 1:2 ^ 1 = 2次调用 merge( ) 与N / 2 ^ 2个元素,O(2 ^ 1 x 2 x N / 2 ^ 2)= O(N)

Level 2:2 ^ 2 = 4次调用merge( ) 与N / 2 ^ 3个元素,O(2 ^ 2 x 2 x N / 2 ^ 3)= O(N)…

Level(log N):2 ^(log N-1)(或N / 2)次调用merge( ) ),其中N / 2 ^ log N(或1)个元素,O(N),
有 log(N) 个层,每层都执行O(N)次工作,因此总体时间复杂度为 O(NlogN)

拓展:

递归,计算时间复杂度有一个Master公式:
形如

T(N) = a * T(N/b) + O(N^d)(其中的a、b、d都是常数)

的递归函数,可以直接通过Master公式来确定时间复杂度。

  • 如果 log(b,a) < d,复杂度为O(N^d)
  • 如果 log(b,a) > d,复杂度为O(N^log(b,a))
  • 如果 log(b,a) == d,复杂度为O(N^d * logN)

我们的归并排序可以用下面的公式来计算:

T(N) = 2*T(N/2) + O(N^1)

根据master可知推导出时间复杂度为O(N×logN)

另外,merge过程需要辅助数组,所以额外空间复杂度为O(N)

归并排序的实质是把比较行为变成了有序信息并传递,比O(N^2)的排序快。

排序算法-归并排序的实现与时间复杂度分析相关推荐

  1. python排序算法——归并排序(附代码)

    python排序算法 --归并排序 文章目录 python排序算法 --归并排序 一.前言 二.算法描述 三.代码实现 总结 一.前言 相关知识来自<python算法设计与分析>.初级排序 ...

  2. 十大经典排序算法-归并排序算法详解

    十大经典排序算法 十大经典排序算法-冒泡排序算法详解 十大经典排序算法-选择排序算法详解 十大经典排序算法-插入排序算法详解 十大经典排序算法-希尔排序算法详解 十大经典排序算法-快速排序算法详解 十 ...

  3. 经典排序算法 - 归并排序Merge sort

    经典排序算法 - 归并排序Merge sort 原理,把原始数组分成若干子数组,对每一个子数组进行排序, 继续把子数组与子数组合并,合并后仍然有序,直到全部合并完,形成有序的数组 举例 无序数组[6 ...

  4. 排序算法-归并排序的时间复杂度分析

    归并排序,其实就是递归+合并. 归并排序将数组取中间分为两部分,两个子数组分别各自再从中间分为两个子数组,一直分下去直到不能再分.分完之后,再按照子数组大小合并为为一个有序数组,然后层层向上合并,直到 ...

  5. 十大排序算法(C++)(时间复杂度O(nlogn)篇:希尔排序、堆排序、快速排序、归并排序)

    希尔排序 希尔排序本质上是对插入排序的一种优化,它既有插入排序的简单,同时也解决了插入排序每次只交换相邻两个元素的缺点.插入排序过程如下: 1.将数组按照一定的间隔分为多个子数组(每跳跃一定间隔取一个 ...

  6. 排序算法 —— 归并排序

    归并排序算法 1.划分问题:把序列分成元素个数尽量相等的两半. 2.递归求解:把两半元素分别排序. 3.合并问题:把两个有序表合并成一个. 归并排序是建立在归并操作上的一种有效的排序算法.该算法是采用 ...

  7. 【算法】排序算法——归并排序

    [fishing-pan:https://blog.csdn.net/u013921430转载请注明出处] 前言        归并排序是分治法在排序问题上的运用,因此为了更好地了解归并排序,首先了解 ...

  8. 十大排序算法JS实现以及复杂度分析

    文章目录 十大排序算法概述 应用场景 代码实现 一.冒泡排序 二.选择排序 三.插入排序 四.希尔排序 五.归并排序 六.快速排序 七.堆排序 八.计数排序 九.桶排序 十.基数排序 十大排序算法概述 ...

  9. 归并算法Python3.7实现及时间复杂度分析

    归并排序是由冯诺依曼首次提出,该算法采取的是分而治之(Divide and Conquer)的思想,速度仅次于快速排序且为稳定算法,适合的排序情况为总体无序,而各子集相对有序. 原理:其思想就是分而治 ...

最新文章

  1. 【ES6】JS第7种数据类型:Symbol
  2. node.js 没有转发_如何用Nodejs编写一个定时消息提醒应用?
  3. sql having是什么意思_sql之汇总查询
  4. 浏览器内存不足导致页面崩溃_深度精读:浏览器渲染原理 [8000字图文并茂]
  5. idea从安装到项目启动
  6. HDCVI——一种创新性的高清视频传输方案
  7. java递归遍历xml所有元素_Java学习之Xml系列二:xml按条件查询、xml递归遍历所有元素和属性...
  8. 开发人员眼中最好的代码编辑器是谁?
  9. git小技巧之分支、关联远程仓库、回滚、解决.gitignore不生效等
  10. 【数字信号调制】基于matlab二进制数字振幅调制与解调(2ASK)【含Matlab源码 996期】
  11. 各地少先队深入开展红领巾心向党主题教育-少先队-红领巾心向党-主题教育
  12. Unity3D脚本学习1
  13. IE浏览器右下角小广告怎么去除
  14. python数据清洗入门教程(完整版)
  15. win10下安装Elasticsearch和kibana教程
  16. sci结构及写作顺序
  17. 中职计算机基础辅导书,中职计算机基础 (938)(16页)-原创力文档
  18. docker部署consol 集群
  19. deepstream c++ rtsp流输出
  20. 照片整理工具(日历相册, 重复文件清理, 手机照片同步, 图片尺寸缩减)

热门文章

  1. SSM框架个人学习汇总
  2. linux下md5加密(使用openssl库C实现)
  3. Vue生命周期函数异步获取数据时的问题
  4. linux smartctl 命令,Linux smartctl命令
  5. 2021新人学习UI设计如何快速入门?
  6. Photoshop | 关于「色阶」
  7. centos9 NFS
  8. php 负载均衡文件上传,nginx负载均衡同步上传文件的问题
  9. C++11之继承构造函数(using 声明)
  10. Opencv 关于Grabcut算法