今天来学习归并排序算法。

分而治之

归并算法的核心思想是分而治之,就是将大问题转化为小问题,在解决小问题的基础上,再去解决大问题。将这句话套用到排序中,就是将一个大的待排序区间分为小的待排序区间,对小的排序区间进行排序,然后再去解决大区间的排序,而对小区间进行排序的时候可以继续使用该方法,将小的待排序区间分为小小的待排序区间... ...依次往复。最终将排序区间分到只有一个元素,这个时候,因为一个元素已经就是排好序的,无需继续切分了,然后我们再依照此结果去解决大区间的排序。

假设我们现在有[53, 89, 32, 45, 67, 2, 32, 89, 65, 54]这么一个数组,我们要对它进行归并排序(从小到大排序,此文中均为从小到大排序),整体的过程如下图所示:

归并排序算法完整过程

整个算法分为两大阶段,分割阶段归并阶段

分割阶段

1. [53, 89, 32, 45, 67, 2, 32, 89, 65, 54]分为[53, 89, 32, 45, 67]和[2, 32, 89, 65, 54]。

2. [53, 89, 32, 45, 67]分为[53, 89]和[32, 45, 67], [2, 32, 89, 65, 54]分为[2, 32]和[89, 65, 54]。

4. ... ...

5. 数组分割完毕,所有小数组依次为[53],[89] ,[32] ,[45],[67],[2],[32],[89],[65],[54]。

归并阶段

1. [53],[89]归并为[53, 89],[32] ,[45]归并为[32, 45],[2]和[32]归并为[2, 32],[65]和[54]归并为[54, 65](这一步中,[67]和[89]没有归并,因为在最后一步分割过程中,它们被单独分开了)。

2. [32, 45]和[67]归并为[32, 45, 67],[89]和[54, 65]归并为[54, 65, 89]。

3. [53, 89]和[32, 45, 67]归并为[32, 45, 53, 67, 89],[2, 32]和[54, 65, 89]归并为[2, 32, 54, 65, 89]。

4. [32, 45, 53, 67, 89]和[2, 32, 54, 65, 89]归并为[2, 32, 32, 45, 53, 54, 65, 67, 89, 89](其中两个32和两个89,在归并的过程中保留它们的原始顺序)。

整个分而治之的过程我们已经清楚了,可还有一个问题没有解决,就是具体应该怎么去归并呢,即如何将两个排序子数组(或子区间)合并为大的排序好的数组(或区间)呢?

我们可以先举个简单的例子:现在有[2]和[1]两个数组,我们如何把它们合并为[1, 2]整个数组呢?很简单,我们首先会把这两个元素取出来,对比一下,取出2和1,我们一对比,发现1小于2, 所以我们在结果数组中先放入1,然后再放入2。可以发现,我们就是将两个子数组中的元素取出来比较了一下,哪个小就把哪个先放入结果数组中。

从上面的例子中我们可以得到大概的思路就是,针对两个有序的子数组(或子区间),我们可以从头依次取两个子数组(或子区间)的首元素(因为从小到大排序后首元素肯定最小),然后作对比,把小的元素放入结果数组中,并且这个元素在下次选取的时候剔除,下一个元素也应用同样的方法得到,放入结果数组中,依次进行,直到两个数组的元素都取完为止,如果发现其中一个子数组(或子区间)率先取完,就直接将另外一个子数组(或子区间)中剩下的元素全部放入结果数组中即可。具体步骤描述如下:

1. 判断两个子数组(或子区间)是否含有剩余元素,如果都有剩余元素,进入第2步;如果只有一个有剩余元素,进入第5步;如果没有,则退出。

2. 取出左子数组(或左子区间)的首个元素和右子数组(或右子区间)的首个元素。

3. 两个元素对比,将小的元素放入结果数组,并且从对应数组中剔除该元素。

4. 回到第1步(上一步选中的元素已被剔除)。

5. 将剩余元素直接全部放入结果数组中,退出(因为元素全部移动完毕)。

其中,剔除这一步在代码实现中可看成索引的移动。

上述这个过程我们取[53, 89]和[32, 45, 67]这两个子数组的合并来描述一下,如图所示:

归并

1. 取出左子数组中的首个元素53和右子数组中的首个元素32,两个作对比,发现32 < 53,所以我们将32放入结果数组:

2. 取出左子数组中的首个元素53和右子数组中的首个元素45,两个作对比,发现45 < 53,所以我们将45放入结果数组:

3. 取出左子数组中的首个元素53和右子数组中的首个元素67,两个作对比,发现53 < 67,所以我们将53放入结果数组:

4. 取出左子数组中的首个元素89和右子数组中的首个元素67,两个作对比,发现67 < 89,所以我们将67放入结果数组:

5. 此时我们发现只有左子数组存在元素,所以直接将左子数组的剩下所有元素,此时只有89放入结果数组:

6. 至此,所有元素移动完毕,退出。

通过以上分析,我们可以知道整个归并排序算法总体上分为一个整体的大逻辑(分而治之)和一个局部的小逻辑(归并),在大逻辑(分而治之,将整个数组切分,并在确认子数组有序后归并)的基础上,结合使用小逻辑(归并,将两个有序子数组归并为一个大的有序数组)即可实现对整个数组的排序。

最终代码实现如下:

/** * 数组的归并排序算法 * * @param nums 数组 * @param lo 区间的lo索引(包含) * @param hi 区间的hi索引(不包含) */public static void mergeSort(int[] nums, int lo, int hi) {    // 数组为null则直接返回    if (nums == null) {        return;    }    // 索引检查    if (lo < 0 || nums.length <= lo) {        throw new IllegalArgumentException("lo索引必须大于0并且小于数组长度,数组长度:" + nums.length);    }    if (hi < 0 || nums.length < hi) {        throw new IllegalArgumentException("hi索引必须大于0并且小于等于数组长度,数组长度:" + nums.length);    }    if (hi <= lo) {        // lo索引必须小于hi索引(等于也不行,因为区间是左闭右开,如果等于,区间内元素数量就为0了)        throw new IllegalArgumentException("lo索引必须小于hi索引");    }    if (lo + 1 >= hi) {        // 区间元素个数最多为1        // 无需排序        return;    }    int mid = (lo + hi) / 2;    // 对左子区间排序    mergeSort(nums, lo, mid);    // 对右子区间排序    mergeSort(nums, mid, hi);    // 对两个排序好的子区间归并,得到一个整体有序的区间    merge(nums, lo, mid, hi);}public static void merge(int[] nums, int lo, int mid, int hi) {    // 这里不用检查索引,调用方已经决定了索引是有效的    // 结果区间和右子区间使用原有数组    // 左子区间使用临时数组(因为结果区间可能会覆盖左子区间的元素,所以需要开辟新数组保存)    int leftLen = mid - lo;    int[] left = new int[leftLen];    System.arraycopy(nums, lo, left, 0, leftLen);    // 左子区间索引    int leftIdx = 0;    // 右子区间索引    int rightIdx = mid;    // 结果区间索引    int resultIdx = lo;    while (true) {        if (leftIdx < leftLen && rightIdx < hi) {            // 两个子区间都存在元素            // 取两个子区间的有效首元素对比            if (left[leftIdx] <= nums[rightIdx]) {                // 左子区间首元素小于右子区间首元素                // 将左子区间首元素放到结果位置,同时更新索引位置                nums[resultIdx++] = left[leftIdx++];            } else {                // 右子区间首元素小于左子区间首元素                // 将右子区间首元素放到结果位置,同时更新索引位置                nums[resultIdx++] = nums[rightIdx++];            }        } else {            if (leftIdx < leftLen) {                // 左子区间还有剩余元素                // 直接将左区间所有元素一起移动到结果位置                System.arraycopy(left, leftIdx, nums, resultIdx, leftLen - leftIdx);            } else {                // 右子区间还有剩余元素                // 因为经过上一次判断,左子区间和右子区间只会有一个存在剩余元素                // 直接将右区间所有元素一起移动到结果位置                System.arraycopy(nums, rightIdx, nums, resultIdx, hi - rightIdx);            }            // 全部元素移动完毕,退出            break;        }    }}

测试代码如下:

List numList = IntStream.range(0, 10).boxed().collect(Collectors.toList());for (int i = 1; i <= 5; i++) {    System.out.println("================第" + i + "次================");    Collections.shuffle(numList);    int[] nums = new int[numList.size()];    for (int j = 0; j < nums.length; j++) {        nums[j] = numList.get(j);    }    System.out.println("排序前:" + Arrays.toString(nums));    mergeSort(nums, 0, numList.size());    System.out.println("排序后:" + Arrays.toString(nums));}

运行结果如下:

================第1次================排序前:[8, 4, 1, 6, 7, 0, 5, 9, 2, 3]排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]================第2次================排序前:[2, 5, 6, 7, 9, 4, 3, 1, 0, 8]排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]================第3次================排序前:[2, 0, 5, 6, 7, 3, 4, 9, 8, 1]排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]================第4次================排序前:[4, 0, 3, 8, 1, 5, 9, 7, 2, 6]排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]================第5次================排序前:[7, 9, 8, 2, 0, 5, 6, 3, 4, 1]排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

测试代码中5次随机将数组打乱,然后运行我们的归并排序算法,均得到有序结果,符合我们的预期。

两个数组结果相减_学点算法(三)——数组归并排序相关推荐

  1. 中两个查询结果相减_位移差?应力差?利用fish语言实现结果文件间运算。

    重要通知:本公众号已开通赞赏功能,各位读者老爷有钱的可以给呱太捧个钱场!推荐使用微信PC端进行阅读,以避免命令流显示畸变.文章所附命令流均可直接复制到FLAC3D 6.0中运行,若有报错,请手动重输命 ...

  2. pgsql 两个时间字段相减_如何在Excel做专业的时间序列分析

    一.工具产生背景 在生产中我们常会根据历史数据去预测未来的发展趋势.比如客户投诉量.用户留存率.页面点击率等等. 对于预测功能的需求多但是具备相应能力的分析师却很少.想做出一份精准的预测需要具备很多专 ...

  3. 两个年月日怎么相减_(excle可以计算日期差值吗)excel表中,两个日期相减如何得出相差年月...

    如何使用excel函数计算两个日期参数的差值 "使用excel函数计算两期参数的差值"的操骤是: 1.以Excel 2016为例,打开Excel工作表: 2.由已知条件可知,需要根 ...

  4. 两个年月日怎么相减_会议记录应该怎么记?看这里

    [会议记录应该怎么记?] 一.会议记录是什么? 会议记录是将一场线上或者线下的会议的核心内容.基本信息.议程.结果等以文字.表格.图片等形式记录下来的信息合集. 好的会议记录具有尊重事实.高度清晰.总 ...

  5. java编程两个超长正整数相减_【每日编程237期】数字分类

    1012 数字分类 每日编程中遇到任何疑问.意见.建议请公众号留言或直接撩Q474356284(备注每日编程) 给定一系列正整数,请按要求对数字进行分类,并输出以下 5 个数字: A1 = 能被 5 ...

  6. 两个年月日怎么相减_用EXCEL表格怎么进行年月日的加减??

    2007-09-21 excel里时间(时,分,秒,百分秒)进 是否有这个函数,我不太清楚,我用VBA自己编写了一个,两种方式,任你选择,第二种方式,可以根据实际情况调整循环的大小.单元格的位置. 如 ...

  7. mysql中日期相减_非凡教育教你excel怎么计算两个日期天数差和时间差

    商务办公培训老师在本文中主要是介绍如何在excel中计算日期和时间,包括两个日期之间的天数.时间之间的差和显示样式. 首先,计算两个日期之间的天数.在excel中,两个日期直接相减就可以得到两个日期间 ...

  8. html5数组删除相同数据,js数组相减简单示例【删除a数组所有与b数组相同元素】...

    js数组相减简单示例[删除a数组所有与b数组相同元素] 本文实例讲述了js数组相减.分享给大家供大家参考,具体如下: js数组相减 function arrChange( a, b ){ for (v ...

  9. (c++)两道关于日期相减的题目

    第一题题目是这样,题意好理解,但是实现日期相减对我而言难度不小 ,在这里我把我做这题的算法详细复盘一下: 1首先创造两个全局数组,分别为平年和闰年的每月的天数 2 对于日期相减的情况,分三种情况讨论: ...

最新文章

  1. Python基本语法_变量作用域LEGB
  2. 二维随机变量期望公式_MIT 6.041 概率论笔记 离散随机变量(二)
  3. 2给我背书_让优秀的人做你的背书人
  4. 软件性能测试过程详解与案例剖析_推荐软件测试书籍
  5. 将Pandas中的DataFrame类型转换成Numpy中array类型的三种方法(亲测)
  6. C# 解决串口接收数据不完整
  7. [FxCop.设计规则]13. 定义自定义属性参数的访问属性
  8. crio电压采集 labview_NI cDAQ917采集温度方法
  9. 【Elasticsearch】 Elasticsearch对外提供分词服务实践
  10. 细数人们对安卓的误解
  11. java里有哪些对象_Java中创建对象的方式有哪些
  12. code::blocks打造自己的开发环境
  13. 1304: 防御导弹 (未完)
  14. CSS3学习笔记--line-height:150%与line-height:1.5的真正区别
  15. 计算机颜色的概念,颜色空间
  16. 大一计算机课程ppt作业,《计算机应用基础》课程第4次作业-PPT操作题答案步骤...
  17. JavaFx之Ikonli图标库大全(十五)
  18. C++:乱码之字符串编码
  19. 线性代数中满足乘法交换律的运算-行列式与迹
  20. 飞桨PP-HumanSeg本地实时视频推理代码解读

热门文章

  1. Connection to node 0 (/192.168.204.131:9092) could not be established
  2. wpf page 界面渲染完成后执行自动操作_Vue项目骨架屏自动生成方案(dps)
  3. 删除电脑中的mysql数据库吗_【数据库】怎么彻底删除mysql服务?
  4. 观察者模式在JDK应用中的源码分析
  5. 布隆过滤器(Bloom Filter)的原理和实现
  6. IDEA 创建 SpringCloud项目-多项目方式
  7. java pdf 导出下载_Java+PDF模板导出成pdf文件,并下载
  8. oracle 修索引改空间_Oracle如何更改表空间的数据文件位置详解
  9. php 打印对象到文件,php实现将数组或对象写入到文件的方法小结【三种方法】...
  10. js中调用C标签实现百度地图