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

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

17349d377b446fbd453b26a9a8178446.png

分而治之

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

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

0a2591f2f9ad17219b06fd8f1cb29240.png

归并排序算法完整过程

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

分割阶段

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]这两个子数组的合并来描述一下,如图所示:

db6e5363a2ecdcd1e6d3bdf1064b6aa7.png

归并

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

07125af4005bed9a00fc01e7fef2d940.png

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

d578d1e3c7860efd12245e39f75958b4.png

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

4702f5fe45e96658536c6c8aca80e517.png

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

f159ef56201fd2f7db99b542f82b4163.png

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

2f81ad7ee06b10875fc6e289158b3106.png

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次随机将数组打乱,然后运行我们的归并排序算法,均得到有序结果,符合我们的预期。

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

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

相关文章

ASP记数器

这两天有好几个老的ASP网站要改&#xff0c;其中有要求加记数器&#xff0c;为图简单&#xff0c;就用文本文件的形式存储记数。以前用ifream的形式嵌入&#xff0c;不能很好的控制记数器显示的风格&#xff0c;现在改进了一下&#xff0c;可以很好的与嵌入板块风格结合了。把做…

[转] DevExpress 第三方控件汉化的全部代码和使用方法

DevExpress.XtraEditors.Controls 此控件包中包含的控件最多&#xff0c;包括文本框&#xff0c;下拉列表&#xff0c;按钮&#xff0c;等等 DevExpress.XtraGrid 网格 DevExpress.XtraBars 菜单栏 和 工具栏 DevExpress.XtraNavBar 导航条 DevExpress.XtraPr…

福音!微信个人公众号可以改名了!

微信个人公众号可以改名了&#xff01;&#xff01;&#xff01;今年&#xff0c;我们学校从景德镇陶瓷学院更名为景德镇陶瓷大学&#xff0c;但苦于微信限制&#xff0c;很多微信公众号无法更名。很多组织社团就放弃了原先的关注量&#xff0c;重新申请注册账号。当前我们的订…

js list删除指定元素_删除js数组中的指定元素,有这两步就够了

js数组是js部分非常重要的知识&#xff0c;有时我们有这么个需求js数组删除指定元素&#xff0c;先定义一个函数来获取删除指定元素索引值&#xff0c;然后用js数组删除的方法&#xff0c;来删除指定元素即可&#xff0c;就两步不难&#xff0c;很简单。1、JS的数组对象定义一个…

计算机协会丨让技能得到提升,让思维受到启迪

“ 各位2016级新生&#xff0c;新的学期马上就要开始了&#xff0c;学校的各个组织和社团你真的了解了吗&#xff1f;在眼花缭乱的社团里如何找到自己真正喜欢的呢&#xff1f;或许看完计算机协会的纳新微信你就都明白啦&#xff01;关键词&#xff1a;计算机协会景德镇陶瓷大学…

ondestroy什么时候调用_尾调用和尾递归

尾调用1. 定义尾调用是函数式编程中一个很重要的概念&#xff0c;当一个函数执行时的最后一个步骤是返回另一个函数的调用&#xff0c;这就叫做尾调用。注意这里函数的调用方式是无所谓的&#xff0c;以下方式均可&#xff1a;函数调用: func()方法调用: obj.method()call调用:…

dhl:使用return RedirectToAction()和 return view()

一个Action&#xff1a; Code/// <summary> /// Friend好友的地 /// </summary> /// <returns></returns> public ActionResult FriendFarm(string pid) {BLL.DTOFarm farm new AppleGrange.BLL.DTOFarm(pid); …

radio切换控制div显示_JavaScript连载31图片动态切换以及关闭图片案例

一、图标切换31.1点击那两个按钮可以做到轮番显示图片二、关闭图片案例31.2点击右上角的叉&#xff0c;图片会消失。三、源码&#xff1a;D31_iconSwitch.htmlD31_2_CloseImage.html地址:https://github.com/ruigege66/JavaScript/blob/master/D31_iconSwitch.htmlhttps://gith…

工作一年后,我有些感悟(写于2017年)

时间拉回到2016年5月23日&#xff0c;当天拍毕业照&#xff0c;晚上是大学毕业酒会&#xff0c;那一晚整个酒店都弥漫着伤感的气息。那一晚大家为了找KTV拖延到很晚&#xff0c;最后一群人选择来到了操场&#xff0c;凌晨两点多一群人还在操场上玩着游戏。5月25日离校&#xff…

PHP基础学习之数组使用要点

一、什么是PHP数组&#xff1f;数组 array 是一组有序的变量&#xff0c;其中每个变量都被称为一个元素。每个元素由一个特殊的标识符来区分&#xff0c;这个标识符称之为键&#xff08;也可以称之为下标&#xff09;。数组中的每个元素都包含两项&#xff1a;键和值。可以通过…

高考七年后、工作三年后的感悟

本打算端午假期发表这文章&#xff0c;后来因为文章还需要有些调整&#xff0c;工作日又比较忙&#xff0c;就到今天周三才发。随便写了近3000字&#xff0c;文章最后有免费送书活动&#xff0c;欢迎留言参与。又一年高考结束了。转眼高考过去七年了&#xff0c;工作了三年。很…

蚂蚁金服天街:OceanBase 在大促 5 年来的技术演进

为了与金融从业者、科技从业者共同探讨金融 业务的深层次问题&#xff0c;蚂蚁金服联手 TGO 鲲鹏会&#xff0c;在 12 月 8 日举办了「走进蚂蚁金服&#xff1a;双十一背后的蚂蚁金服技术支持」活动。蚂蚁金服高级技术专家天街为大家分享了《蚂蚁双 11 大促 OceanBase 核心技术…

学习 jQuery 源码整体架构,打造属于自己的 js 类库

虽然现在基本不怎么使用 jQuery了&#xff0c;但 jQuery流行 10多年的 JS库&#xff0c;还是有必要学习它的源码的。也可以学着打造属于自己的 js类库&#xff0c;求职面试时可以增色不少。本文章学习的是 v3.4.1版本。unpkg.com源码地址&#xff1a;https://unpkg.com/jquery3…

5分钟轻松教您如果组建100-500路大型拼接监控系统!

冰山融汇百家号17-07-2700:41大型监控系统如何组网&#xff0c;分布式还是集中式&#xff1f;可靠性与性价比又如何取舍&#xff1f;什么才是最合适的视频监控存储产品&#xff1f;在不同地区、行业的项目中&#xff0c;这些疑问均成为业主、专家、系统集成商等各方面共同关注的…

(转)mssql2005生成表字典

出处不详 CodeSELECT TOP 100 PERCENT --a.id, CASE WHEN a.colorder 1 THEN d.name ELSE END AS 表名, CASE WHEN a.colorder 1 THEN isnull(f.value, ) ELSE END AS 表说明, a.colorder AS 字段序号, a.name AS 字段名, CASE WHEN COLUMNPROPERTY(a.id, a.name, IsIdenti…

表操作

2019独角兽企业重金招聘Python工程师标准>>> 字段修改 alter table TA drop partition (day<2018-12-10); ALTER TABLE TB ADD COLUMNS (userStatus String) CASCADE; ALTER TABLE TC change appversion appCommonVersion String CASCADE; ALTER TABLE TD DROP C…

学习underscore源码整体架构,打造属于自己的函数式编程类库

前言上一篇文章写了 jQuery整体架构&#xff0c;学习 jQuery 源码整体架构&#xff0c;打造属于自己的 js 类库虽然看过挺多 underscore.js分析类的文章&#xff0c;但总感觉少点什么。这也许就是纸上得来终觉浅&#xff0c;绝知此事要躬行吧。于是决定自己写一篇学习 undersco…

拓扑目的 1.Pc9通过van3访问pc10 2.Pc9通过Vlan1\Vlan2访问pc11

1拓扑图2设置路由器R12的接口的IPint g0/0/0ip address 192.168.20.254 24undo shutdown int g0/0/01ip address 192.168.1.1 24undo shutdownint g2/0/00ip address 192.168.3.1 24undo shutdown 3设置路由器R10的接口的IPint g0/0/0ip address 192.168.2.1 24undo shutdownin…

学习 lodash 源码整体架构,打造属于自己的函数式编程类库

前言这是 学习源码整体架构系列第三篇。整体架构这词语好像有点大&#xff0c;姑且就算是源码整体结构吧&#xff0c;主要就是学习是代码整体结构&#xff0c;不深究其他不是主线的具体函数的实现。文章学习的是打包整合后的代码&#xff0c;不是实际仓库中的拆分的代码。上上篇…

推荐一个快速反射调用的类

使用传统的.net反射机制&#xff0c;调用类的方法时&#xff0c;在调用频率大的情况下&#xff0c;会感觉速度很慢。最近浏览卢彦的博客时&#xff0c;找到一个他改进后的反射调用类。试用以后感觉效率明显提高&#xff0c;特推荐给大家。作者重新实现了&#xff0c;反射调用方…