实现二分归并排序算法_如何实现归并排序?

归并排序

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

划分步骤很简单:将当前数组分成两半(如果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]

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

ffa64b12eade7022b082e759d9bca5d8.png
递归函数调用过程

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

66ee93f4ea8293d8494506feb89d02b7.png
递归归并排序图解

非递归写法

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

直接上代码:

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},其排序过程动图演示:

5705024ef43e2df84ba94b1b3a318393.gif
归并排序动态演示

归并排序的时间复杂度

df935bff55e38e7e3ee002d1269dfeb7.png
归并排序时间复杂度分析

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表达式的写法套路

看完点赞,养成习惯。

举手之劳,赞有余香。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/437048.shtml

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

相关文章

通俗解释非对称加密

通俗解释 对称加密 对于不了解非对称加密的同学&#xff0c;应该只知道“对称加密”。这个对称是指什么呢&#xff1f; 加密必然要解密。如果加密和解密用的是同一串字符串&#xff0c;也就是密码&#xff0c;那么这就称为对称的。新词汇而已&#xff0c;为了和非对称加密相区…

【转】解决WCF大数据量传输 ,System.Net.Sockets.SocketException: 远程主机强迫关闭了一个现有的连接...

开发中所用的数据需要通过WCF进行数据传输&#xff0c;结果就遇到了WCF大量传输问题 也就是提示System.Net.Sockets.SocketException: 远程主机强迫关闭了一个现有的连接 网上解决方案都是千篇一律互相转发的&#xff0c;并且没有明确的解决方案或者按照&#xff0c;各个博客中…

【手算】逆序数树形计算方法

计算过程&#xff1a; 给定一列数&#xff0c;从左向右&#xff0c;依次将每个数拿出来&#xff0c;作为一棵树的树根。从剩下的数中&#xff0c;找比此数小的数&#xff0c;作为子节点加入到树中。重复1、2&#xff0c;直到到达最后一个数停止。 数出二级节点&#xff0c;即…

python 惰性_Django中的“惰性翻译”方法的相关使用

使用 django.utils.translation.gettext_lazy() 函数&#xff0c;使得其中的值只有在访问时才会被翻译&#xff0c;而不是在 gettext_lazy() 被调用时翻译。例如&#xff1a;要翻译一个模型的 help_text&#xff0c;按以下进行&#xff1a;from django.utils.translation impor…

【转】SVN冲突出现场景

如今是一个团结协作的时代&#xff0c;开发一个系统&#xff0c;往往会多人协作共同完成。版本管理是必不可少的&#xff0c;常用的软件有Git&#xff0c;SVN等。今天说一下&#xff0c;SVN管理版本时&#xff0c;如果出现冲突后&#xff0c;如何快速解决冲突。 首先说明一个问…

Qt Creator和Visual Studio双环境开发技能攻略

前言 刚接触Qt开发时&#xff0c;大多数同学的第一行Qt代码是在Qt Creator中写下的。 众所周知&#xff0c;Qt Creator是Qt官方提供的Qt集成开发环境1&#xff0c;提供了对Qt应用开发的全过程支持。也就是说&#xff0c;使用Qt Creator做Qt开发是完全够用的。不管是在Windows…

大疆无人机高程不准_大疆消费级无人机快速倾斜摄影测量实践

摘要&#xff1a;分别用 RTK两种测量模式测定像控点&#xff0c;用大疆消费级无人机 Phantom&#xff14;PRO 开展倾斜摄影测量&#xff0c;最后进行成果检验。结果表明&#xff1a;RTK 电台模式(不做坐标校正&#xff0c;精度低)、RTK&#xff0d;CORS 网络模式测定像控点都能…

【转】CLR Profiler 性能分析工具 (转)

最近关注程序性能的优化方面&#xff0c;本想写篇CLRProfiler的简要文档&#xff0c;看到pengpeng已写好&#xff0c;那就转载好了&#xff0c;偷偷懒。 .Net的尽管拥有非常强大调试器&#xff0c;但也有很多局限性&#xff0c;只限于CLR之上&#xff0c;无法得到CLR的具体细节…

Qt学习资料汇总

1. 在线资源 脚本之家在线手册&#xff1a;Qt 快速入门系列教程W3CSchool&#xff1a;Qt 学习之路 2. 书籍资源 链接&#xff1a;https://pan.baidu.com/s/12zoOQykCA-J8x_1FVahFWA 提取码&#xff1a;关注公众号“Qt未来工程师”&#xff0c;后台回复“书籍”获取提取码。 …

魔百盒哪款型号配置高_松下负离子吹风机怎么样哪款好?推荐型号?2020年9月松下负离子电吹风选购攻略...

先说结论&#xff0c;方便时间不多的朋友&#xff0c;松下&#xff08;panasonic&#xff09;负离子吹风机怎么样哪款好&#xff1f;推荐型号&#xff1f;1、EH-ENE2-A405——松下&#xff08;panasonic&#xff09;负离子电吹风机推荐EH-ENE2-A405。带负离子功能&#xff0c;1…

【转】VS编译环境命令窗口中的命令

先描述一下VS中的几个窗口 命令窗口&#xff08;Command Window&#xff09; 利用命令窗口&#xff0c;可以查询并设置变量的值来控制程序的执行和输出&#xff0c;也可以在命令窗口中编写很多小的代码段来改变程序的输出。当程序处于中断模式时&#xff0c;命令窗口只允许输…

【软技能】代码先放一放,学习一下工程的概念

前言 本篇文章适合于以下同学阅读 接触编程时间不长的初学者&#xff1b;经常需要编程&#xff0c;但是对工程的概念相对模糊的同学。 初步认识工程 工程的概念大家或多或少都有接触过。比如在开发环境&#xff08;如Qt Creator&#xff09;里编写一个软件&#xff0c;我们…

python程序设计搜题软件_智慧职教APPPython程序设计基础答案搜题公众号

智慧职教APPPython程序设计基础答案搜题公众号更多相关问题暑淫证与火热证的共同点是A&#xff0e;都有明显的季节性B&#xff0e;脉数有力C&#xff0e;发热神昏D&#xff0e;舌红绛每种生物都是有自己特有的一套遗传密码。()患者&#xff0c;女&#xff0c;34岁。营业员&…

【转】WebAPI ASP.Net 发布部署中常见的两个错误未能找到 CodeDom未能加载System.Web.WebPages.Razor

未能加载文件或程序集“System.Web.WebPages.Razor, Version3.0.0.0, Culturene 未能找到 CodeDom 提供程序类型“Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft... 在包管理器中异常安装包MVC5 及 CodeDom&#xff0c;重新编译部署即可…

QSplitter设置宽高比例的正确方法

问题 实际开发中&#xff0c;需要经常调整QSplitter中子窗口的宽度/高度比例。对水平QSplitter来说&#xff0c;是宽度比例&#xff1b;对垂直QSplitter来说&#xff0c;是高度比例。下面以水平QSplitter为例说明。 我们首先应该会想起看看QSplitter是不是有设置比例的函数。…

我要回家软件_工具用的好,下班回家早,推荐几个堪称神器的高效率软件

工具用的好&#xff0c;下班回家早&#xff0c;别人已经早早下班&#xff0c;你还在哼哧哼哧的加班&#xff0c;有时候并不是你比别人慢&#xff0c;而是你没有找到提高工作效率的工具呀&#xff0c;我这次推荐几个堪称神器的高效率软件工具&#xff0c;也许能极大的提高你的工…

【转】带你玩转Visual Studio——01.开篇介绍

开篇之前&#xff0c;先唠叨几句 本人从事C开发工作一年半&#xff0c;总想就C开发方面写点东西。写什么呢&#xff1f;想了一下还是写点跟开发密切相关的吧&#xff0c;要说跟开发最密切相关的那莫过于就是开发工具了&#xff0c;也就是常常说的集成开发环境(IDE)。 这一年多…

Qt中的角度和正方向描述清单

在Qt开发中&#xff0c;不同的API对应有不同的0度和正方向&#xff0c;本文做一个汇总&#xff0c;方便查阅。 函数/模块角度描述画圆弧QPainter::drawArc3点钟方向为0度&#xff0c;逆时针为正向画弦QPainter::drawChord3点钟方向为0度&#xff0c;逆时针为正向画圆饼QPainte…

丢失模式不联网有用吗_用眼过度,打开手机“护眼模式”真的有用吗?

现如今&#xff0c;人们越来越离不开手机&#xff0c;用手机办公或者用手机娱乐&#xff0c;因疫情在家学习的学生们&#xff0c;都需要通过手机来进行学习&#xff0c;深夜剁手的你是不是也躲在被子里&#xff0c;一遍又一遍地刷新着消息&#xff0c;入睡前的最后一件事是关掉…

【基础】Qt-VS调试器下载与配置

前言 因为MSVC版的Qt不自带调试器&#xff0c;所以在Windows上使用MSVC版的Qt进行开发时&#xff0c;需要配置调试器。 本文讲解了在Windows下&#xff0c;下载和配置MSVC版Qt调试器的过程&#xff0c;并给出下载地址。 调试器选择 下表列出了不同平台下的调试器选择方案&am…