归并排序

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

划分步骤很简单:将当前数组分成两半(如果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             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;    //定义每部分参与比较数组的长度,初始长度为1    int mergeSize = 1;    //只要mergeSize小于N    while (mergeSize         int L = 0;        while (L             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         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)的排序快。

欢迎阅读我的其他Java基础文章

  • 为什么当初不好好学习算法
  • 我用线程池ThreadPoolExecutor处理任务和Redis做缓存查询,将程序效率提升了5倍!
  • 从一道面试题进入Java并发新机制---J.U.C
  • synchronized底层实现知多少?synchronized加锁还会升级?
  • 你知道Object o = new Object()在内存中占多少字节吗?
  • Java8新特性Stream还有这种操作?
  • 终于看懂别人的代码了!总结Java 8之Lambda表达式的写法套路

看完点赞,养成习惯。

举手之劳,赞有余香。

实现二分归并排序算法_如何实现归并排序?相关推荐

  1. python二分查找算法_如何使用python的二分查找算法

    如何使用python的二分查找算法 发布时间:2020-11-18 09:27:29

  2. 二分归并排序算法_第五篇排序算法|归并排序

    0x01,前言闲叙 最近几年很少看电视了,因为没时间看了,除了偶尔刷刷头条,基本上不会花大块的时间沉迷于电视剧,综艺,这或许就是短视频时代所带来的一些改变吧,我们都会深受其中. 0x02,先看下这篇文 ...

  3. java的归并排序算法_归并排序算法Java实现

    一. 算法描述 归并排序采用了分治策略(divide-and-conquer),就是将原问题分解为一些规模较小的相似子问题,然后递归解决这些子问题,最后合并其结果作为原问题的解. 归并排序将待排序数组 ...

  4. python实现二分查找算法_两种方法实现Python二分查找算法

    一. arr=[1,3,6,9,10,20,30] def findnumber(l,h,number): mid=(l+h)//2 if arr[mid]==number: print(" ...

  5. 算法笔记之2-路归并排序

    2-路归并排序 2-路归并排序算法简介: 2-路归并排序算法图解: 2-路归并排序代码实现(递归): 2-路归并排序代码实现(非递归): 2-路归并排序应用举栗

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

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

  7. 有序序列的二分查找、冒泡排序、归并排序算法实战解析

    本节开始讲解一下几个简单的算法,原理都在那本书上,大家自己看吧,我就不做搬运工了,这里不同的是,我把vector接口函数单独拿出来进行测试了,深深的体会到算法的奥妙之处,等你深入理解了你会情不自禁拍案 ...

  8. python实现冒泡排序算法的非递归版本_python排序算法速度比较:快速排序,归并排序,冒泡排序...

    前言 原理就不在这里说了,好多大神肯定比我这个初学者讲的好很多,推荐去B站看视频讲解,跟着手敲代码 为什么选这三个排序呢? 首先快排是必须掌握的 看看快排在最坏的情况下(O(n²)),且不使用辅助空间 ...

  9. C++ 不知算法系列之聊聊希尔、归并排序算法中的分治哲学

    1. 前言 排序算法中,冒泡.插入.选择属于相类似的排序算法,这类算法的共同点:通过不停地比较,再使用交换逻辑重新确定数据的位置. 希尔.归并.快速排序算法也可归为同一类,它们的共同点都是建立在分治思 ...

最新文章

  1. Python:机器视觉与Tesseract介绍
  2. 和12岁小同志搞创客开发:手撕代码,做一款温湿度检测器
  3. php session 过期,php session失效的原因
  4. Java和JavaScript区别与联系
  5. 七 web爬虫讲解2—urllib库爬虫—状态吗—异常处理—浏览器伪装技术、设置用户代理...
  6. 使用Docker构建你的第一个服务
  7. 为什么机器学习算法要与人类水平比较
  8. leetcode42 --- trap
  9. 最长递增子序列LIS再谈
  10. OpenCV-腐蚀cv::erode
  11. Django Model出现的中文问题
  12. 007-卷积神经网络03-前向传播-反向传播
  13. java concurrent 线程通信_java并发之线程间通信
  14. linux 搜索s开头的文件,Linux文件查找/内容搜索命令
  15. java程序定时运行
  16. 谈谈你对 Webpack 的理解
  17. 超市购物系统小票打印
  18. Date.getyear()、Date.getMonth()、Date.getDay() 已经作废,其他解决办法
  19. 【转】WebMagic-总体流程源码分析
  20. 《2019全球仓储自动化50强企业排行榜》

热门文章

  1. MFC中CString.Format的用法
  2. Remon Spekreijse CSerialPort串口类的修正版2014-01-10
  3. 309. Best Time to Buy and Sell Stock with Cooldown
  4. 【小技巧】【堆】【优先队列】优先队列初始化
  5. HDU-3998 Sequence LIS统计
  6. matlab 写excel 慢_我在12w+的Python库中,发现了让Excel快到起飞的秘密......
  7. 游戏服务器红点系统,Unity简易的红点系统RedPoint System
  8. 打架程序_学生打架不知道怎么处理?一般程序了解下,对你或许有帮助
  9. 浙江省高等学校计算机等级考试有什么用,06秋年秋浙江省高等学校计算机等级考试试卷(三级数据库技术及应用)...
  10. 普元连接mysql_普元EOS 案例 - 还有一行代码的个人空间 - OSCHINA - 中文开源技术交流社区...