归并排序的理解和实现(Java)
归并排序介绍
归并排序(Merge Sort)就是利用归并的思想实现的排序方法。它的原理是假设初始序列含有fn个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到[n2\frac{n}{2}2n]([x]表示不小于x的最小整数)个长度为2或1的有序子序列;在两两归并,…,如此重复,知道得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序。
根据具体的实现,归并排序包括"从上往下"和"从下往上"2种方式。
下面的图片很清晰的反映了"从下往上"和"从上往下"的归并排序的区别。

从上往下
代码实现:
/**
* 从上到下
* @param elem
* @param start
* @param end
*/
public void mergeSortUp2Down(int[] elem, int start, int end) {
if(elem == null || start >= end) {
return;
}
int mid = (start + end) / 2;
mergeSortUp2Down(elem, start, mid);
mergeSortUp2Down(elem, mid + 1, end);
merge(elem, start, mid, end);
}
public void merge(int[] elem, int start, int mid, int end) {
int[] temp = new int[end - start + 1];
int i = start;
int j = mid + 1;
int k = 0;
while(i <= mid && j <= end) {
if(elem[i] < elem[j]) {
temp[k++] = elem[i++];
}
else {
temp[k++] = elem[j++];
}
}
while(i <= mid) {
temp[k++] = elem[i++];
}
while(j <= end) {
temp[k++] = elem[j++];
}
for (i = 0; i < k; i++) {
elem[start + i] = temp[i];
}
temp = null;
}
从上往下的思路如图所示:

从下往上
代码实现:
/**
* 从下到上
* @param elem
*/
public void mergeSortDown2Up(int[] elem) {
if(elem == null) return;
for (int i = 1; i < elem.length; i *= 2) {
mergeGroups(elem, elem.length, i);
}
}
public void mergeGroups(int[] elem, int len, int gap) {
int i;
for (i = 0; i + 2 * gap -1 < len; i += (2 * gap)) {
merge(elem, i, i + gap -1, i + 2 * gap -1);
}
if(i + gap -1 < len - 1) {
merge(elem, i, i + gap - 1, len - 1);
}
}

归并排序的复杂度分析
归并排序的时间复杂度是O(nlog\loglogn)。
假设被排序的数列中有n个元素。遍历一趟的时间复杂度是O(n),需要遍历多少次呢?归并排序的形式就是一棵二叉树,它需要遍历的次数就是二叉树的深度,而根据完全二叉树的性质可以得知可以得出它的时间复杂度是O(nlog\loglogn)。
由于归并怕徐在归并过过程中需要与原始记录序列同样数量的存储空间存放归并结果以及递归时深度为log2log_2log2n的栈空间,所以空间复杂度为O(n + log\loglogn)
归并排序是稳定的算法,它满足稳定算法的定义。
归并排序的非递归实现
非递归的思想和递归一样,均为先分解后合并,非递归的重点在于如何确定并合理的分解待排序数组。
对于非递归来讲,切分的不向递归从大到小,非递归实际上从一开始构建算法的时候都从小到大。
第一次切分排序就确定最小单位为1个数字,将2个数字组合为一组。

第二次切分排序确定为2个数字,将4个数字组合为一组。

第三次切分排序确定为4个数字,将8(7)个数字组合为一组。

也就是说非递归归并排序中分解的依据为:从切分的长度为1开始,一次归并变回原来的2倍。每完成一次归并则 gap = gap * 2。
/**
* 非递归
* @param elem
*/
public void mergeSortNon(int[] elem) {
int gap = 1;
while(gap <= elem.length) {
for (int i = 0; i + gap < elem.length; i += (gap * 2)) {
int start = i, mid = i + gap -1, end = i + 2 * gap -1;
if(end > elem.length - 1) {
end = elem.length - 1;
}
merge(elem, start, mid, end);
}
gap *= 2;
}
}
完整代码:
public class Test {
/**
* 从上到下
* @param elem
* @param start
* @param end
*/
public void mergeSortUp2Down(int[] elem, int start, int end) {
if(elem == null || start >= end) {
return;
}
int mid = (start + end) / 2;
mergeSortUp2Down(elem, start, mid);
mergeSortUp2Down(elem, mid + 1, end);
merge(elem, start, mid, end);
}
/**
* 从下到上
* @param elem
*/
public void mergeSortDown2Up(int[] elem) {
if(elem == null) return;
for (int i = 1; i < elem.length; i *= 2) {
mergeGroups(elem, elem.length, i);
}
}
public void mergeGroups(int[] elem, int len, int gap) {
int i;
for (i = 0; i + 2 * gap -1 < len; i += (2 * gap)) {
merge(elem, i, i + gap -1, i + 2 * gap -1);
}
if(i + gap -1 < len - 1) {
merge(elem, i, i + gap - 1, len - 1);
}
}
/**
* 非递归
* @param elem
*/
public void mergeSortNon(int[] elem) {
int gap = 1;
while(gap <= elem.length) {
for (int i = 0; i + gap < elem.length; i += (gap * 2)) {
int start = i, mid = i + gap -1, end = i + 2 * gap -1;
if(end > elem.length - 1) {
end = elem.length - 1;
}
merge(elem, start, mid, end);
}
gap *= 2;
}
}
public void merge(int[] elem, int start, int mid, int end) {
int[] temp = new int[end - start + 1];
int i = start;
int j = mid + 1;
int k = 0;
while(i <= mid && j <= end) {
if(elem[i] < elem[j]) {
temp[k++] = elem[i++];
}
else {
temp[k++] = elem[j++];
}
}
while(i <= mid) {
temp[k++] = elem[i++];
}
while(j <= end) {
temp[k++] = elem[j++];
}
for (i = 0; i < k; i++) {
elem[start + i] = temp[i];
}
temp = null;
}
public static void main(String[] args) {
Test t = new Test();
int[] elem = {80,30,60,40,20,10,50,70};
t.mergeSortUp2Down(elem, 0, elem.length - 1); //从上到下
// t.mergeSortDown2Up(elem); //从下到上
// t.mergeSortNon(elem); //非递归
for (int i = 0; i < elem.length; i++) {
System.out.print(elem[i] + ", ");
}
}
}
参考:
https://www.cnblogs.com/skywang12345/p/3602369.html
https://www.cnblogs.com/yulinfeng/p/7078661.html?utm_source=itdadao&utm_medium=referral
《大话数据结构》
补充说明
最近在做一道《剑指Offer》上的一道算法的时候,用到了归并排序,因此加深了我对归并排序的核心排序(merge())方法加深了理解:

其实核心的排序就是设立三个指针P1, P2, P3。P3指针所指的数组就是两个数组合并且排序后的数组。每次比较P1和P2指针所指的元素:
- 如果P1<P2,那么将P1所指的元素赋给P3所指的数组节点,并将P1和P3的指针后移一位,P2指针不动。
- 如果P1>P2,那么将P2所指的元素赋给P3所指的数组节点,并将P2和P3的指针后移一位,P1指针不动。
- 当P1或P2其中某一个指针指到最后了,那么也就是说两个数组比较排序已经完成,但是其中一个数组还“剩下”一部分元素(注意剩下的这些元素是有序的且剩下元素中最小的元素也大于新数组的最大元素),将剩下这部分的元素放在新数组后面。
归并排序的理解和实现(Java)的更多相关文章
- Atitit 深入理解命名空间namespace java c# php js
Atitit 深入理解命名空间namespace java c# php js 1.1. Namespace还是package1 1.2. import同时解决了令人头疼的include1 1.3 ...
- 理解和解决Java并发修改异常ConcurrentModificationException(转载)
原文地址:https://www.jianshu.com/p/f3f6b12330c1 理解和解决Java并发修改异常ConcurrentModificationException 不知读者在Java ...
- 深入理解和探究Java类加载机制
深入理解和探究Java类加载机制---- 1.java.lang.ClassLoader类介绍 java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字 ...
- 深入理解什么是Java泛型?泛型怎么使用?【纯转】
本篇文章给大家带来的内容是介绍深入理解什么是Java泛型?泛型怎么使用?有一定的参考价值,有需要的朋友可以参考一下,希望对你们有所助. 一.什么是泛型 “泛型” 意味着编写的代码可以被不同类型的对象所 ...
- 多校第五场 归并排序+暴力矩阵乘+模拟+java大数&记忆化递归
HDU 4911 Inversion 考点:归并排序 思路:这题呀比赛的时候忘了知道能够用归并排序算出逆序数,可是忘了归并排序的实质了.然后不会做-- 由于看到题上说是相邻的两个数才干交换的时候.感觉 ...
- [转载] 深入理解Android之Java虚拟机Dalvik
本文转载自: http://blog.csdn.net/innost/article/details/50377905 一.背景 这个选题很大,但并不是一开始就有这么高大上的追求.最初之时,只是源于对 ...
- 如何理解和使用Java package包
Java中的一个包就是一个类库单元,包内包含有一组类,它们在单一的名称空间之下被组织在了一起.这个名称空间就是包名.可以使用import关键字来导入一个包.例如使用import java.util.* ...
- 深入理解JVM(6)——Java内存模型和线程
Java虚拟机规范中定义了Java内存模型(Java Memory Model,JMM)用来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果(“即Ja ...
- 理解JVM之Java内存区域
Java虚拟机运行时数据区分为以下几个部分: 方法区.虚拟机栈.本地方法栈.堆.程序计数器.如下图所示: 一.程序计数器 程序计数器可看作当前线程所执行的字节码行号指示器,字节码解释器工作时就是通过改 ...
随机推荐
- 2018.08.30 NOIP模拟 wall(模拟)
[问题描述] 万里长城是中国强大的标志,长城在古代的用途主要用于快速传递军事消息和抵御 外敌,在长城上的烽火台即可以作为藏兵的堡垒有可以来点燃狼烟传递消息. 现在有一段 万里长城,一共有 N 个烽火台 ...
- 36 The Benefits of Marriage 结婚的益处
36 The Benefits of Marriage 结婚的益处 ①Being sociable looks like a good way to add years to your life.Re ...
- Spinner功能和用法
书中只是简单写了选择的界面,没有写出选择之后的结果显示,我做了进一步功能. MainActivity.java public class MainActivity extends Activity { ...
- 浅析基于AXIS框架的WebService
一.写在前面 之前做项目用到了基于Axis的WebService,为了更进一步的理解和记忆,在这里通过代码实践和源码分析来完整的做一遍Axis的WebService以及对应的客户端调用实践,并和其它的 ...
- python文件操作,读取,修改,合并
# -*- coding:utf-8 -*- ''' 从11c开始提取 ''' import re import numpy as np import os year = '17A' ss=" ...
- 字符串"k:1“” 处理成字典 {'k':1,'k1':2....}
1.有字符串"k:1|k1:2|k2:3|k3:4" 处理成字典 {'k':1,'k1':2....} #第一种方法 s1 = "k:1|k1:2|k2:3|k3:4&q ...
- Django开发Web监控工具-pyDash
今天发现了一个比较有意思的监控工具,是基于Django开发的,开发大牛已经开放了源代码,向大牛致敬,同时研究研究,目前感觉这个监控比较直观,可以针对个人服务器使用,同时涉及的环境比较简单,部署起来 ...
- 在centos docker中安装nvidia驱动
因为计算需要用到GPU加速, 今天尝试在centos 机器的Docker里安装了GTX980驱动, 记录一下详细安装过程. 首先安装Docker和镜像: sudo yum install docker ...
- SMINT:单页网站的免費jQuery插件
最近为了做一个静态网页版的数据报告,不希望花很多时间去设计网页,或者花时间去调整布局,于是找到了一个名为Smint的免費jQuery插件.几乎不需要写什么代码就可以完成一个一页式网站.这非常适合用来制 ...
- Replication--查看未分发命令和预估所需时间
当复制有延迟时,我们可以使用复制监视器来查看各订阅的未分发命令书和预估所需时间,如下图: 但是当分发和订阅数比较多的时候,依次查看比较费时,我们可以使用sys.sp_replmonitorsubscr ...