归并排序就这么简单
归并排序就这么简单
从前面已经讲解了冒泡排序、选择排序、插入排序,快速排序了,本章主要讲解的是归并排序,希望大家看完能够理解并手写出归并排序快速排序的代码,然后就通过面试了!如果我写得有错误的地方也请大家在评论下指出。
归并排序的介绍
来源百度百科:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
过程描述:
归并过程为:比较a[i]和b[j]的大小,若a[i]≤b[j],则将第一个有序表中的元素a[i]复制到r[k]中,并令i和k分别加上1;否则将第二个有序表中的元素b[j]复制到r[k]中,并令j和k分别加上1,如此循环下去,直到其中一个有序表取完,然后再将另一个有序表中剩余的元素复制到r中从下标k到下标t的单元。归并排序的算法我们通常用递归实现,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。
原理:
归并操作的工作原理如下: 第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列 第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置 第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置 重复步骤3直到某一指针超出序列尾 将另一序列剩下的所有元素直接复制到合并序列尾
下面我就来做个小小的总结:
- 将两个已排好序的数组合并成一个有序的数组,称之为归并排序
- 步骤:遍历两个数组,比较它们的值。谁比较小,谁先放入大数组中,直到数组遍历完成
一、演算归并排序过程
现在我有两个已经排好顺序的数组:int[] arr1 = {2, 7, 8}
和int[] arr2 = {1, 4, 9}
,我还有一个大数组来装载它们int[] arr = new int[6];
1.1
那么,我将两个数组的值进行比较,谁的值比较小,谁就放入大数组中!
首先,拿出arr1[0]
和arr2[0]
进行比较,显然是arr2[0]
比较小,因此将arr2[0]
放入大数组中,同时arr2
指针往后一格
所以,现在目前为止arr = {1}
1.2
随后,拿arr1[0]
和arr2[1]
进行比较,显然是arr1[0]
比较小,将arr1[0]
放入大数组中,同时arr1
指针往后一格
所以,现在目前为止arr = {1,2}
1.3
随后,拿arr1[1]
和arr2[1]
进行比较,显然是arr2[1]
比较小,将arr2[1]
放入大数组中,同时arr2
指针往后一格
所以,现在目前为止arr = {1,2,4}
……..
遍历到最后,我们会将两个已排好序的数组变成一个已排好序的数组arr = {1,2,4,7,8,9}
二、归并排序前提分析(分治法)
从上面的演算我们就直到,归并排序的前提是需要两个已经排好顺序的数组,那往往不会有两个已经排好顺序的数组给我们的呀(一般是杂乱无章的一个数组),那这个算法是不是很鸡肋的呢??
其实并不是的,首先假设题目给出的数组是这样子的:int[] arr = {2, 7, 8, 1, 4, 9};
当我们要做归并的时候就以arr[3]
也就元素为1的那个地方分开。是然后用一个指针L
指向arr[0]
,一个指针M
指向arr[3]
,用一个指针R
指向arr[5]
(数组最后一位)。有了指针的帮助,我们就可以将这个数组切割成是两个有序的数组了(操作的方式就可以和上面一样了)
可是上面说了,一般给出的是杂乱无章的一个数组,现在还是达不到要求。比如给出的是这样一个数组:int[] arrays = {9, 2, 5, 1, 3, 2, 9, 5, 2, 1, 8};
此时,我们就得用到分治的思想了:
- 那么我们也可以这样想将
int[] arr = {2, 7, 8, 1, 4, 9};
数组分隔成一份一份的,arr[0]
它是一个有序的"数组",arr[1]
它也是一个有序的"数组",利用指针(L,M,R)又可以像操作两个数组一样进行排序。最终合成{2,7}
…….再不断拆分合并,最后又回到了我们的arr = {1,2,4,7,8,9}
,因此归并排序是可以排序杂乱无章的数组的
这就是我们的分治法--->将一个大问题分成很多个小问题进行解决,最后重新组合起来
三、归并代码实现
实现步骤:
- 拆分
- 合并
……..
public static void main(String[] args) {
int[] arrays = {9, 2, 5, 1, 3, 2, 9, 5, 2, 1, 8};
mergeSort(arrays, 0, arrays.length - 1);
System.out.println("公众号:Java3y" + arrays);
}
/**
* 归并排序
*
* @param arrays
* @param L 指向数组第一个元素
* @param R 指向数组最后一个元素
*/
public static void mergeSort(int[] arrays, int L, int R) {
//如果只有一个元素,那就不用排序了
if (L == R) {
return;
} else {
//取中间的数,进行拆分
int M = (L + R) / 2;
//左边的数不断进行拆分
mergeSort(arrays, L, M);
//右边的数不断进行拆分
mergeSort(arrays, M + 1, R);
//合并
merge(arrays, L, M + 1, R);
}
}
/**
* 合并数组
*
* @param arrays
* @param L 指向数组第一个元素
* @param M 指向数组分隔的元素
* @param R 指向数组最后的元素
*/
public static void merge(int[] arrays, int L, int M, int R) {
//左边的数组的大小
int[] leftArray = new int[M - L];
//右边的数组大小
int[] rightArray = new int[R - M + 1];
//往这两个数组填充数据
for (int i = L; i < M; i++) {
leftArray[i - L] = arrays[i];
}
for (int i = M; i <= R; i++) {
rightArray[i - M] = arrays[i];
}
int i = 0, j = 0;
// arrays数组的第一个元素
int k = L;
//比较这两个数组的值,哪个小,就往数组上放
while (i < leftArray.length && j < rightArray.length) {
//谁比较小,谁将元素放入大数组中,移动指针,继续比较下一个
if (leftArray[i] < rightArray[j]) {
arrays[k] = leftArray[i];
i++;
k++;
} else {
arrays[k] = rightArray[j];
j++;
k++;
}
}
//如果左边的数组还没比较完,右边的数都已经完了,那么将左边的数抄到大数组中(剩下的都是大数字)
while (i < leftArray.length) {
arrays[k] = leftArray[i];
i++;
k++;
}
//如果右边的数组还没比较完,左边的数都已经完了,那么将右边的数抄到大数组中(剩下的都是大数字)
while (j < rightArray.length) {
arrays[k] = rightArray[j];
k++;
j++;
}
}
我debug了一下第一次的时候,就可以更容易理解了:
- 将大数组的前两个进行拆分,然后用数组装载起来
- 比较小数组的元素哪个小,哪个小就先放入大数组中
上面的两个步骤不断循环,最后得出有序的数组:
四、归并排序的优化
来源:http://www.cnblogs.com/noKing/p/7940531.html
我这里整理一下要点,有兴趣的同学可到上面的链接上阅读:
- 当递归到规模足够小时,利用插入排序
- 归并前判断一下是否还有必要归并
- 只在排序前开辟一次空间
- Hadoop数据分析平台实战——020Hadoop Shell命令(初学跳过)离线数据分析平台实战——020Hadoop Shell命令(可跳过)
- HDU 2080 夹角有多大II
- 二分查找模版
- Hadoop数据分析平台实战——010hadoop介绍安装
- Python为什么文件运行和在命令行运行同样语句但结果却不同?
- HDU 2034 人见人爱A-B
- Hadoop数据分析平台实战——030Hadoop Shell命令02(熟悉linux跳过)离线数据分析平台实战——030Hadoop Shell命令02
- Hadoop数据分析平台实战——070深入理解MapReduce 02(案例)离线数据分析平台实战——070深入理解MapReduce 02
- Hadoop数据分析平台实战——040HDFS介绍(熟悉基础概念跳过)离线数据分析平台实战——040HDFS&JAVA API(熟悉基础概念跳过)
- Hadoop数据分析平台实战——060深入理解MapReduce 01(案例)离线数据分析平台实战——060深入理解MapReduce 01(案例)
- 利用向量积(叉积)计算三角形的面积和多边形的面积
- HDU 1556 Color the ball
- Hadoop数据分析平台实战——080HBase介绍和安装离线数据分析平台实战——080HBase介绍和安装
- Hadoop数据分析平台实战——130Hive Shell命令介绍 02(熟悉Hive略过)离线数据分析平台实战——130Hive Shell命令介绍 02(熟悉Hive略过)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 【java设计模式系列】1. 工厂方法模式(Factory Method)
- 为什么 Java 中 1000==1000 为 false ?
- 【java设计模式系列】2. 单例模式(Singleton)
- 跨域请求的解决方案
- 【排序】快速排序
- 因为BitMap,白白搭进去8台服务器...
- java所有的“锁”大总结,以后面试再也不怕遇到锁了
- 【Java8新特性】03 Stream流式数据处理
- 慎用JSON.stringify
- 【Java8新特性】04 详解Lambda表达式中Predicate Function Consumer Supplier函数式接口
- ubuntu下的进程控制系统————Supervisor
- 如何高效、快速、准确地完成ML任务,这4个AutoML库了解一下
- [译]如何用 Typescript 写一个完整的 Vue 应用程序
- Docker上手系列:Docker入门hello world
- 前端应该知道的 HTTP 知识