归并排序Java版(图文并茂思路分析)
归并排序
工作原理:
工作原理是将一个大问题分解成小问题,再将小问题分解成更小的。(乍一看就觉得是像一个递归)就像下图这样。然后不断的将其一份为二,分解成更小的排序。

我们设一个函数叫MergeSort(arr,l,r)意思就是将arr数组下标为[ l ,r ]之间的数进行排序。
那么就开始不断的调用自己,从而不断的将数组一分为二
int mid = ( l + r ) / 2;
MergeSort( arr, l , mid );
MergeSort( arr, mid+1 , r);
其次我们的递归出口应该就是在变化的参数l > r 的时候,由于没有什么返回值,直接return就好了
if (l >= r) return;
最后我们要将分开的数组重新组合起来,所以我们还需要一个函数,我们就叫它merge(arr,l,mid,r)吧,我们来思考一下如何将两个数组合并起来呢
我们先来看一个问题,怎么把ab两个数组合并成有序的?

很简单的思路,从左右遍各拿一个出来对比,把小的放进有序数组就好了。比如先把左边的1和右边的2拿出来对比,发现1小,便把1放入新数组。
然后再将未放入的2和左边的3对比,发现2小,把2放进新数组,以此类推...
所以回到我们的归并排序,我们怎么实现上面的思路呢?
首先我们要先复制一个数组出来,保证有一个数组作为参考,另一个作为上面所谓的新数组,把拿出来的值覆盖掉另一个即可。然后发现我们这只有一个数组啊,怎么变成两个数组呢 —> 用3个点将数组分为2段就好了

所以此时此刻,我们只需要将i和j拿出来进行对比,然后将小的覆盖到传进来的原数组即可(也就是把上图的第一行数组覆盖了)
优化前:
package com.sort;
import java.util.Arrays;
/**
* @Author: 翰林猿
* @Description: 归并排序
**/
public class Merge {
public Merge() {
}
public static <E extends Comparable<E>> void Mergesort(E[] arr, int l, int r) {
if (l >= r)
return;
int mid = l +(r - l) / 2;
Mergesort(arr, l, mid);
Mergesort(arr, mid + 1, r);
Merge(arr, l, mid, r);
}
public static <E extends Comparable<E>> void Merge(E[] arr, int l, int mid, int r) {
//先把数组复制一个
E[] copyArr = Arrays.copyOfRange(arr, l, r+1);
//用两个变量记住第一个下标l和中间的下标mid+1将数组一分为二
int i = l;
int j = mid + 1;
//走一遍数组,每轮都为arr[k]赋值,arr[k]最后是拿来按顺序覆盖传进来的原数组arr的从而形成排序
for (int k = l; k <= r; k++) {
//判断一下i是否越界,如果越界了,说明左边数组遍历完毕
// 就应当将临时数组的j处的值赋给arr[k](也就是直接将目前右边数组遍历到的值赋给arr[k])
//但是由于有偏移,比如说l是3,mid是6,r是9,其实存在copyarr里的下标是0,3,6
//所以我们要用j-l计算偏移过后的真正下标
if (i > mid) {
arr[k] = copyArr[j - l];
j++; //为了再下一次循环就可以只遍历我们的另一个数组了
} else if (j > r) { //如果是j下标越界了,说明右边数组遍历完毕,将左边目前遍历到的值返回
arr[k] = copyArr[i - l]; //同上
i++;
} else if (copyArr[i - l].compareTo(copyArr[j - l]) <= 0) { //说明两边都没有越界,对比i和j并将小的赋给k,然后i++
arr[k] = copyArr[i - l]; //左边小,把左边赋给k
i++;
} else { //右边小,把右边赋给k
arr[k] = copyArr[j - l];
j++;
}
}
}
public static void main(String[] args) {
Integer[] arr = {3, 2, 1, 4, 5};
Merge.Mergesort(arr, 0, arr.length-1);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
优化思路:
很多人其实不知道如何去优化一个算法,在这里总结几个方向给大家看看,这里主要是指排序类的算法。
个人认为优化其实就是去考虑一些边界和极端现象及内存浪费造成的性能损耗
极端有序情况优化:
如果给出的序列本身就是一个有序的,如何让算法减少遍历的次数甚至跳过?
如果给出的序列很短,造成序列已经有序的数字概率很大,如何让算法不再重新将已经有序的数字重新再排序造成的性能损耗?
这两个其实本质差不多,都是解决序列本身就有序或者接近有序怎么办。
这里考虑两个方案:
用 i f 语句在某处进行判断是否已经有序之类的,如果有序就不再遍历下去。比如说这样:
//优化一下,如果左数组和右数组连接的中间那两个数字已经有序,说明没必要再遍历下去了
if (arr[mid].compareTo(arr[mid + 1]) > 0) {
Merge(arr, l, mid, r);
}当序列很短时,我们使用对于短数组来说更快的算法。比如说当数组小于15个时使用复杂度为O(n)的插入排序
if (r - l <= 15) {
Insert.InsertSortForMerge(arr, l, r);
return;
}
内存浪费优化
考虑一下我们的算法中间有没有反复创建新的空间,导致内存不断占用之类的?
比如说我们在复制数组的时候,其实一直在反复调用,这种情况我们可以把它提到上一层去,然后将其当作一个参数传给下面调用者。这样无需在调用者种反复复制数组,完成内存操作优化。

到这里,其实我们的优化还没有完成,我们只是将一个arr这么大的数组空间重复利用了,但是内容还没有拷贝,我们可以使用这段代码将内容也拷贝进去。同时还顺便解决了偏移量的问题,不再需要老是减一个 L 了
System.arraycopy(arr, l, copyArr, l, r - l + 1);
优化后代码:
package com.sort;
import java.util.Arrays;
/**
* @Author: 翰林猿
* @Description: 归并排序
**/
public class Merge {
public Merge() {
}
public static <E extends Comparable<E>> void Mergesort(E[] arr, int l, int r) {
//将copy出来的arr提到上一层来,这样不必反复copy数组,节约内存空间。
E[] copyArr = Arrays.copyOf(arr, arr.length);
if (r - l <= 15) {
Insert.InsertSortForMerge(arr, l, r);
return;
}
int mid = l + (r - l) / 2;
Mergesort(arr, l, mid);
Mergesort(arr, mid + 1, r);
if (arr[mid].compareTo(arr[mid + 1]) > 0) { //优化一下,如果左数组和右数组连接的中间那两个数字已经有序,说明没必要再遍历下去了
Merge(arr, l, mid, r, copyArr);
}
}
public static <E extends Comparable<E>> void Merge(E[] arr, int l, int mid, int r, E[] copyArr) {
//先把数组复制一个
//E[] copyArr = Arrays.copyOfRange(arr, l, r + 1); 优化到上面
System.arraycopy(arr, l, copyArr, l, r - l + 1);
//用两个变量记住第一个下标l和中间的下标mid+1将数组一分为二
int i = l;
int j = mid + 1;
//走一遍数组,每轮都为arr[k]赋值,arr[k]最后是拿来按顺序覆盖传进来的原数组arr的从而形成排序
for (int k = l; k <= r; k++) {
//判断一下i是否越界,如果越界了,说明左边数组遍历完毕
// 就应当将临时数组的j处的值赋给arr[k](也就是直接将目前右边数组遍历到的值赋给arr[k])
//但是由于有偏移,比如说l是3,mid是6,r是9,其实存在copyarr里的下标是0,3,6
//所以我们要用j-l计算偏移过后的真正下标
if (i > mid) {
arr[k] = copyArr[j];
j++; //为了再下一次循环就可以只遍历我们的另一个数组了
} else if (j > r) { //如果是j下标越界了,说明右边数组遍历完毕,将左边目前遍历到的值返回
arr[k] = copyArr[i]; //同上
i++;
} else if (copyArr[i].compareTo(copyArr[j]) <= 0) { //说明两边都没有越界,对比i和j并将小的赋给k,然后i++
arr[k] = copyArr[i]; //左边小,把左边赋给k
i++;
} else { //右边小,把右边赋给k
arr[k] = copyArr[j];
j++;
}
}
}
public static void main(String[] args) {
Integer[] arr = {3, 2, 1, 4, 5, 9, 19, 15, 18};
Merge.Mergesort(arr, 0, arr.length - 1);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
归并排序Java版(图文并茂思路分析)的更多相关文章
- 归并排序—Java版
一开始做算法的时候,感觉递归算法很绕,所以我就在阅读别人代码的基础上,对代码每一步都添加自己的注解,方便我以后的学习. public class MergeSort { /** * 归并排序 * @p ...
- 八大排序算法——归并排序(动图演示 思路分析 实例代码java 复杂度分析)
一.动图演示 二.思路分析 归并排序就是递归得将原始数组递归对半分隔,直到不能再分(只剩下一个元素)后,开始从最小的数组向上归并排序 1. 向上归并排序的时候,需要一个暂存数组用来排序, 2. 将 ...
- 八大排序算法详解(动图演示 思路分析 实例代码java 复杂度分析 适用场景)
一.分类 1.内部排序和外部排序 内部排序:待排序记录存放在计算机随机存储器中(说简单点,就是内存)进行的排序过程. 外部排序:待排序记录的数量很大,以致于内存不能一次容纳全部记录,所以在排序过程中需 ...
- 八大排序算法——堆排序(动图演示 思路分析 实例代码java 复杂度分析)
一.动图演示 二.思路分析 先来了解下堆的相关概念:堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆:或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆.如 ...
- 八大排序算法——希尔(shell)排序(动图演示 思路分析 实例代码java 复杂度分析)
一.动图演示 二.思路分析 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序:随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止. 简单插 ...
- 八大排序算法——基数排序(动图演示 思路分析 实例代码java 复杂度分析)
一.动图演 二.思路分析 基数排序第i趟将待排数组里的每个数的i位数放到tempj(j=1-10)队列中,然后再从这十个队列中取出数据,重新放到原数组里,直到i大于待排数的最大位数. 1.数组里的数最 ...
- 八大排序算法——快速排序(动图演示 思路分析 实例代码Java 复杂度分析)
一.动图演示 二.思路分析 快速排序的思想就是,选一个数作为基数(这里我选的是第一个数),大于这个基数的放到右边,小于这个基数的放到左边,等于这个基数的数可以放到左边或右边,看自己习惯,这里我是放到了 ...
- 常见机试题分析Java版
1. 操作系统任务分为系统任务和用户任务两种.其中,系统任务的优先级<50,用户任务的优先级>=50且<=255.优先级大于255的为非法任务,应予以剔除.现有一任务队列task[] ...
- 八大排序算法——插入排序(动图演示 思路分析 实例代码java 复杂度分析)
一.动图演示 二.思路分析 例如从小到大排序: 1. 从第二位开始遍历, 2. 当前数(第一趟是第二位数)与前面的数依次比较,如果前面的数大于当前数,则将这个数放在当前数的位置上,当前数的下标-1 ...
- 八大排序算法——冒泡排序(动图演示 思路分析 实例代码java 复杂度分析)
一.动图演示 二.思路分析 1. 相邻两个数两两相比,n[i]跟n[j+1]比,如果n[i]>n[j+1],则将连个数进行交换, 2. j++, 重复以上步骤,第一趟结束后,最大数就会被确定 ...
随机推荐
- [BUUCTF]Pwn刷题记录
本部分内容长期更新,不再创建新文章影响阅读 rip 根据IDA加载入main函数声明发现s数组距离rbp的距离为F,即为15,这里的运行环境是64位,所以应当将Caller's rbp的数据填满,在这 ...
- IDA 逆 WDF 驱动时的函数识别插件
快一年没更新了,累,工作累,各种累,想换个工作,突然发现找不到合适的工作了,哎,自己往火坑里跳,怪不得别人. import idautils import idaapi import idc prin ...
- 排队论——系统运行指标的R语言实现
排队是在日常生活中经常遇到的现象,如顾客到商店购买物品.病人到医院看病常常要排队.此时要求服务的数量超过服务机构(服务台.服务员等)的容量.也就是说,到达的顾客不能立即得到服务,因而出现了排队现象.这 ...
- [Tomcat/Java EE/Linux]Tomcat启动异常:StandardServer.await: create[localhost:8005]: java.net.BindException: 无法指定被请求的地址
1 问题背景 部门新成员小J在一台虚拟机(ip:192.168.191.96)内安装部署部门的数据治理产品(含: 20余个微服务模块 + 1套(用户)基础管理系统BMS). 小J启动BMS的Tomca ...
- java基础--lambda表达式
lambda表达式,一种常见用法,就是简化匿名内部类.使用前提条件:如果一个方法A(),只涉及一个抽象方法待实现,那么使用A()时,涉及到匿名内部类,就可以简化为 lambda 表达式 lambda表 ...
- gs_probackup增量备份ptrack.cpp : 88
问题描述:使用gs_probackup对opengauss进行增量备份失败[omm@testmysqldb04 ~]$ sh gs_probackup.sh incbackup pg_switch_x ...
- 天梯赛L1-027 出租
一.问题描述 下面是新浪微博上曾经很火的一张图: 一时间网上一片求救声,急问这个怎么破.其实这段代码很简单,index数组就是arr数组的下标,index[0]=2 对应 arr[2]=1,index ...
- js复制功能(pc复制,移动端复制到手机剪切板)
一个函数,直接调就好了,已测pc和app都适用 1 // 一键复制 2 copyBtn(data) { 3 const input = document.createElement("inp ...
- 阿里云交互式建模(DSW)的探索和踩坑
前言 自己的笔记本炼丹还是太吃力了些,风扇嘶吼有点心疼,看到阿里云出了一些免费试用的资源,想着能白嫖一下高端显卡跑一跑自制模型还挺有趣,于是有了下面的一些操作,其实没啥难度的,大胆的按文档来做基本就可 ...
- 深入理解 slab cache 内存分配全链路实现
本文源码部分基于内核 5.4 版本讨论 在经过上篇文章 <从内核源码看 slab 内存池的创建初始化流程> 的介绍之后,我们最终得到下面这幅 slab cache 的完整架构图: 本文笔者 ...