概述

上一篇我们说了一个非常简单的排序算法——选择排序。其复杂程序完全是冒泡级的,甚至比冒泡还要简单。今天要说的是一个相对比较复杂的排序算法——归并排序。复杂的原因不仅在于归并排序分成了两个部分进行解决问题,而是在于,你需要一些算法的思想支撑。
归并排序和之前我写的一篇博客《大数据算法:对5亿数据进行排序》有很多相似的地方,如果你感兴趣,也可以去看看那一篇博客。


版权说明

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
本文作者:Q-WHai
发表日期: 2016年5月27日
本文链接:https://qwhai.blog.csdn.net/article/details/51517753
来源:CSDN
更多内容:分类 >> 算法与数学


目录

文章目录

  • 概述
  • 版权说明
  • 目录
    • @[toc]
  • 弱分治归并
    • 算法原理
      • 背景
      • 合并
      • 分治
      • 排序过程图解
    • 算法实现
  • 强分治归并
    • 算法原理
      • 合并
      • 分治
  • 总结
    • 算法复杂度
    • 强弱分治归并的比较
  • Ref
  • Github源码下载
  • 征集

弱分治归并

归并的核心算法就是上面提到过的两个过程。分别是分治与合并。合并都好理解,那么什么是分治呢?下面就来逐一说明一下。

算法原理

弱分治归并排序算法中,我们主要说的是合并,因为这里的分治更像是分组。

背景

假设我们有序列 T0 = [ 4, 3, 6, 5, 9, 0, 8, 1, 7, 2 ]
那么,在一开始,我们的序列就被分成了 10 组,每一组的元素个数为 1。

合并

先说合并吧,因为它简单一些。在合并模块中,需要传入两个序列参数,并保证待合并的两个序列本身已经有序。现在我们假设待合并的两个有序序列分别为:
t0 = [ 0, 9 ]
t1 = [ 1, 8 ]
看到这两个序列让人很自然地想到,只要依次取 t0 和 t1 中的最小的元素即可。并且最小的元素就是第一个元素。当我们取完 t0 中的 0 之后,t0 中就不再有 0 了,这一点很重要。表现在代码上就是下标的移动了。第二次取到的是 t1 中的 1。重复这个过程,就可以获得合并后的有序序列 tm = [ 0, 1, 8, 9, ]。
合并过程图解

下面是合并的核心代码

// 合并的核心模块private void merge(int[] array, int low, int mid, int hight) {if (low >= hight) {return;}int[] auxArray = new int[hight - low + 1];int index1 = low;int index2 = mid + 1;int i = 0;while(index1 <= mid && index2 <= hight) {if (array[index1] <= array[index2]) {auxArray[i] = array[index1];index1++;i++;} else {auxArray[i] = array[index2];index2++;i++;}}// 继续合并前半段数组中未被合并的部分while (index1 <= mid) {auxArray[i] = array[index1];index1++;i++;}// 继续合并后半段数组中未被合并的部分while (index2 <= hight) {auxArray[i] = array[index2];index2++;i++;}// 将合并好的序列写回到数组中for (int j = 0; j < auxArray.length; j++) {array[low + j] = auxArray[j];}}

分治

我想大部分人应该不会被合并逻辑给难住吧。只是分治的逻辑会有一些麻烦,麻烦不是在于分治思想的麻烦,而是分治过程的逻辑代码不好编写。正因为如此,所以我们在前面先讲解弱分治归并,这样在下面看到强分治归并的分治逻辑时,你才不会毫无头绪。在上面也说了,弱分治并归更像是一个分组合并的过程。也就是一开始就有很多组,然后慢慢合并,在合并的过程中分组减少了,合并后的有序数组变大了,直至只有一个数组为止。
在合并中最容易想到的是两两合并。所以在分组后,就两两进行合并。只要我们能准确地取到相邻的两个序列就可以进行合并了。
下面是代码实现

// 对数组进行分组的核心模块private void sortCore(int[] array) {int length = array.length;int groupSize = 1;while(groupSize < length) {for (int i = 0; i < length; i += (groupSize * 2)) {int low = i;int hight = Math.min(i + groupSize * 2 - 1, length - 1);int middle = low + groupSize - 1;merge(array, low, middle >= hight ? (low + hight) / 2 : middle, hight);}groupSize *= 2;}// 对分组中的奇数情况进行另外处理if (groupSize / 2 < length) {int low = 0;int hight = length - 1;merge(array, low, groupSize / 2 - 1, hight);}}

在上面的代码中,可以看到最后有一个奇数分组的逻辑处理。这是怎么回事呢?很好理解,假设,现在给你 (2n + 1) 个分组的有序序列,按照前面讲的两两合并,那么只能合并前面的 2n 个序列,第 (2n + 1) 个序列找到可合并的对象。处理的方式就是把它保留到最后与迭代后的有序序列进行合并即可。这一点从下面的图解中也可以获知。

排序过程图解


算法实现

/*** <p>* 归并排序算法* </p>* 2016年1月20日* * @author <a href="http://weibo.com/u/5131020927">Q-WHai</a>* @see <a href="https://qwhai.blog.csdn.net/">https://qwhai.blog.csdn.net/</a>* @version 0.1.1*/
public class MergeSort implements Sortable {@Overridepublic int[] sort(int[] array) {if (array == null) {return null;}sortCore(array);return array;}// 对数组进行分组的核心模块private void sortCore(int[] array) {( ... 此处省略上面分治的逻辑 ... )}// 合并的核心模块private void merge(int[] array, int low, int mid, int hight) {( ... 此处省略上面合并的逻辑 ... )}
}

强分治归并

算法原理

强分治归并相比弱分治归并的不同点在于,强分治归并有没在一开始就对数组 T0 进行分组,而是通过程序来对 T0 进行分组,现在可以看一张强分治归并排序算法的过程图感受一下。

合并

不管弱分治归并还是强分治归并,其合并的逻辑都是一样的。大家可以自行参考上面的逻辑,这里就不废话了。

分治

从上面的排序过程图中也可以发现,强分治归并需要将原数组先划分成小数组。首先将一个大数组分割成两个小数组,再将两个小数组分割成四个小数组,如此往复。字里行间都表明了,这里需要进行递归操作。
强分治归并算法的分治部分逻辑代码:

/*** 对数组进行分组的核心模块* * @param array*      待排序数组* @param start*      开始位置* @param end*      结束位置(end 为 数组可达下标)*/private void sortCore(int[] array, int start, int end) {if (start == end) {return;} else {int middle = (start + end) / 2;sortCore(array, start, middle);sortCore(array, middle + 1, end);merge(array, start, middle, end);}}

总结

算法复杂度

排序方法 时间复杂度 空间复杂度 稳定性 复杂性
平均情况 最坏情况 最好情况
归并排序 O($n * log~{n}$) O($n * log~{n}$) O($n * log~{n}$) O($n + log~{n}$) 稳定 较复杂

强弱分治归并的比较

需要比较的主是代码逻辑复杂度和运行效率
这里我们用了一个数组为: int[] array = { 4, 3, 6, 5, 9, 0, 8, 1, 7, 2 };
循环运行 1000000 次后得到如下结果:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
MergeSort 用时:509 ms
-------------------------------------
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
MergeImproveSort 用时:374 ms
算法名称 代码逻辑复杂度 运行效率 bigger 值
弱分治归并 简单
强分治归并 复杂

所以,如果想要运行效率高一些或是刷刷 bigger 值,那么请使用强分治归并排序算法。


Ref

  • 《大话数据结构》

Github源码下载

  • https://github.com/qwhai/algorithms-sort

征集

如果你也需要使用ProcessOn这款在线绘图工具,可以使用如下邀请链接进行注册:
https://www.processon.com/i/56205c2ee4b0f6ed10838a6d

排序算法系列:归并排序算法相关推荐

  1. 排序算法:归并排序算法实现及分析

    归并排序算法介绍 归并排序(Merging Sort)就是利用归并的思想实现排序的放.它的原理是假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到n/2个 ...

  2. 算法竞赛——归并排序算法

    算法竞赛--归并排序算法 分治法 划分问题:把序列分成元素个数尽量相等的两半 递归求解:把两半元素分别排序 合并问题:把两个有序表合并成一个 借鉴RuJia的精妙的合并过程 void merges2( ...

  3. 趣学算法系列-贪心算法

    趣学算法系列-贪心算法 声明:本系列为趣学算法一书学习总结内容,在此推荐大家看这本算法书籍作为算法入门, 原作者博客链接,本书暂无免费电子版资源,请大家支持正版,更多的案例分析请查看原书内容. 第二章 ...

  4. 算法设计与分析 ——插入排序算法与归并排序算法比较

    插入排序算法与归并排序算法比较 实验目的 通过插入排序算法与归并排序算法效率对比体会算法在求解问题中的重要性. 实验内容 分别编写函数实现插入排序算法和归并排序算法: 利用随机函数产生大量数据存入数组 ...

  5. 【排序算法】归并排序算法原理

    归并排序 概念 使用前提 算法思路 适用场景 算法描述 递归法(Top-down) 分而治之 迭代法(Bottom-up) 迭代 概念   归并排序是建立在归并操作上的一种有效的排序算法.    该算 ...

  6. java的归并排序算法_归并排序算法Java实现

    一. 算法描述 归并排序采用了分治策略(divide-and-conquer),就是将原问题分解为一些规模较小的相似子问题,然后递归解决这些子问题,最后合并其结果作为原问题的解. 归并排序将待排序数组 ...

  7. 搞定面试算法系列 | 分治算法三步走

    戳蓝字"CSDN云计算"关注我们哦! 作者 | 江子抑 转自 | 编程拯救世界 主要思想 分治算法,即分而治之:把一个复杂问题分成两个或更多的相同或相似子问题,直到最后子问题可以简 ...

  8. 优化算法系列-模拟退火算法(1)——基本原理枯燥版本

    优化算法系列之模拟退火算法(1)--基本原理枯燥版本 推荐书籍--><智能优化算法及其MATLAB实例(第二版)> 知乎上的形象描述: 一个锅底凹凸不平有很多坑的大锅,晃动这个锅使得 ...

  9. 大数据与算法系列之算法性能分析

    我们在敲出自己心爱的程序的时候,我们是否想过,自己程序的性能咋样! 今天,我们说一说对程序或者算法的性能分析! 算法复杂度 算法复杂度是算法性能最基本的评价标准,复杂度是一个算法的时间运行函数,常用大 ...

  10. 算法系列——弗洛伊德算法(Floyd)

    本系列旨在用简单的人话讲解算法,尽可能避免晦涩的定义,读者可以短时间内理解算法原理及应用细节.我在努力! 本篇文章编程语言为Python,供参考. 弗洛伊德算法(Floyd) 典型最短路径算法.用于计 ...

最新文章

  1. SSH-Struts第三弹:传智播客视频教程第一天上午的笔记
  2. 建立asp.net应用程序提示:无法与服务器建立连接
  3. LSTM-pytorch 写诗之位置编码
  4. 二分查找(递归与非递归)
  5. 降息大法好!银行4.35%消费贷了解一下?
  6. python preference界面设置_偏好设置如何更改Preference的样式
  7. win10系统按esc会弹出计算机,win10系统版本2004控制面板多出ESC是什么原因?
  8. 第25课 成绩等级 《小学生C++趣味编程》
  9. libreoffice使用_使用LibreOffice Calc管理您的财务
  10. 跨应用的访问 contentprovider
  11. 12864 C语言程序 带详细注解
  12. 【软件工程】 详细设计
  13. android 放大镜功能,利用Android实现一个放大镜功能
  14. mysql存储包含单引号英文字符串,SQL中写入包含有英文单引号“ '' ”失败问题深入详解...
  15. 广东省数字经济行业发展动态及十四五前景预测分析报告2022-2027年
  16. 数据挖掘入门(一)基本理论
  17. 中国互联网公司亏损能力排行榜
  18. c++ 实现贪吃蛇(含技术难点解析和完整代码)
  19. C语言后缀.h文件和.c文件作用和区别
  20. android输入法01:SoftKeyboard源码解析02

热门文章

  1. 1068 万绿丛中一点红 (C++)
  2. css背景图重复怎们弄,CSS如何实现背景图像重复效果
  3. html图片颜色变深,利用CSS改变图片颜色的100种方法!
  4. 孙海波:重新发现“同案”:构建案件相似性的判断标准
  5. win7网络发现启用后找不到网络计算机,win7启用网络发现怎么又关闭了怎么解决...
  6. 关于安卓手机的一些专业名称解释
  7. 跨层中介作用模型2-1-1的Mplus语法
  8. sqlloader 导入数据的一点经验教训(最后附我的导入过程)
  9. java获取剩余手机电池容量_怎样判断手机电池的剩余容量
  10. python rgb转换为gray