在本节中我们讨论的算法都基于归并这个简单的操作,即将两个有序的数组归并成一个更大的有序数组。很快人们就根据这个操作发明了一种简单的递归排序算法:归并排序。

要将一个数组排序,可以先(递归地)将它分成两半分别排序,然后将结果归并起来。你将会看到,归并排序最吸引人的性质是它能够保证将任意长度为N的数组排序所需时间和NlogN成正比;它的主要缺点则是它所需的额外空间和N成正比。简单的归并如图所示:

原地归并的抽象方法:

实现归并的一种直截了当的办法是将两个不同的有序数组归并到第三个数组中,两个数组中的元素应该都实现了Compare接口。实现的方法很简单,创建一个适当大小的数组然后将两个输入数组中的元素一个个从小到大放入这个数组中。

但是,当用归并将一个大数组排序时,我们需要进行很多次归并,因此在每次归并时都创建一个新数组来存储排序结果会带来问题。我们更希望有一种能够在原地归并的办法,这样就可以先将前半部分排序,再将后半部分排序,然后在数组中移动元素而不需要使用额外的空间。你可以停下来先想一想应该如何实现这一点,乍一看很容易做到,但实际上已有的实现都非常复杂,尤其是和使用额外空间的方法相比。

/**
* 原地归并的抽象方法
*/
public static void merge(Comparable[] a, int lo, int mid, int hi){//将a[lo..mid]和a[mid+1..hi]归并int i = lo, j = mid+1;//将a[lo..hi]复制到aux[lo..hi]for(int k=lo; k<=hi; k++){aux[k] = a[k];}//归并回到a[lo..hi]for(int k=lo; k<=hi; k++){if(i>mid){a[k] = aux[j++];}else if(j>hi){a[k] = aux[i++];}else if(less(aux[j], aux[i])){a[k] = aux[j++];}else{a[k] = aux[i++];}}}

View Code

自顶向下的归并排序

算法2.4基于原地归并的抽象实现了另一种递归归并,这也是应用高效算法设计中分治思想的最典型的一个例子。这段递归代码是归纳证明算法能够正确地将数组排序的基础:如果它能将两个子数组排序,它就能够通过归并两个子数组来将整个数组排序。

package algorithm;/*** 〈算法2.4 自顶向下的归并排序〉<br>*/
public class Merge {private static Comparable[] aux;  //归并所需的辅助数组public static void sort(Comparable[] a){aux = new Comparable[a.length];sort(a, 0, a.length-1);}private static void sort(Comparable[] a, int lo, int hi){//将数组a[lo...hi]排序if(hi <= lo) return;int mid = lo + (hi - lo)/2;sort(a, lo, mid);               //将左半边排序sort(a, mid+1, hi);         //将右半边排序merge(a, lo, mid, hi);          //归并结果
    }public static void merge(Comparable[] a, int lo, int mid, int hi){//将a[lo..mid]和a[mid+1..hi]归并int i = lo, j = mid+1;//将a[lo..hi]复制到aux[lo..hi]for(int k=lo; k<=hi; k++){aux[k] = a[k];}//归并回到a[lo..hi]for(int k=lo; k<=hi; k++){if(i>mid){a[k] = aux[j++];}else if(j>hi){a[k] = aux[i++];}else if(less(aux[j], aux[i])){a[k] = aux[j++];}else{a[k] = aux[i++];}}}private static boolean less(Comparable v, Comparable w){return v.compareTo(w) < 0;}
}

View Code

命题F:对于长度为N的任意数组,自顶向下的归并排序需要½NlgN至NlgN次比较。
命题G:对于长度为N的任意数组,自顶向下的归并排序最多需要访问数组6NlgN次。

(这些我没看懂,书上p186,怎么证明的看不懂...)

命题F和命题G告诉我们归并排序所需的时间和NlgN成正比。这和2.1节所述的初级排序方法不可同日而语,它表明我们只需要比遍历整个数组多个对数因子的时间就能将一个庞大的数组排序。可以用归并排序处理数百万甚至更大规模的数组,这是插入排序或者选择排序做不到的。归并排序的主要缺点是辅助数组所使用的额外空间和N的大小成正比。另一方面,通过一些细致的思考我们还能够大幅度缩短归并排序的运行时间。

对小规模子数组使用插入排序
用不同的方法处理小规模问题能改进大多数递归算法的性能,因为递归会使小规模问题中方法的调用过于频繁,所以改进对它们的处理方法就能改进整个算法。对排序来说,我们已经知道插入排序(或者选择排序)非常简单,因此很可能在小数组上比归并排序更快。

测试数组是否已经有序
我们可以添加一个判断条件,如果a[mid]小于等于a[mid+1],我们就认为数组已经是有序的并跳过merge()方法。这个改动不影响排序的递归调用,但是任意有序的子数组算法的运行时间就变为线性的了。

不将元素复制到辅助数组
我们可以节省将数组元素复制到用于归并的辅助数组所用的时间(但空间不行)。要做到这一点我们要调用两种排序方法,一种将数据从输入数组排序到辅助数组,一种将数据从辅助数组排序到输入数组。这种方法需要一些技巧,我们要在递归调用的每个层次交换输入数组和辅助数组的角色。

自底向上的归并排序

递归实现的归并排序是算法设计中分治思想的典型应用。我们将一个大问题分割成小问题分别解决,然后用所有小问题的答案来解决整个大问题。尽管我们考虑的问题是归并两个大数组,实际上我们归并的数组大多数都非常小。实现归并排序的另一种方法是先归并那些微型数组,然后再成对归并得到的子数组,如此这般,直到我们将整个数组归并在一起。这种实现方法比标准递归方法所需要的代码量更少。首先我们进行的是两两归并(把每个元素想象成一个大小为1的数组),然后是四四归并(将两个大小为2的数组归并成一个有4个元素的数组),然后是八八的归并,一直下去。在每一轮归并中,最后一次归并的第二个子数组可能比第一个子数组要小(但这对merge方法不是问题),如果不是的话所有的归并中两个数组大小都应该一样,而在下一轮中子数组的大小会翻倍。

自底向上的归并排序算法实现如下:

package algorithm;/*** 〈自底向上的归并排序〉<br>*/
public class MergeBU {private static Comparable[] aux;  //归并所需的辅助数组public static void sort(Comparable[] a){//进行lgN次两两归并int N = a.length;aux = new Comparable[N];for(int sz=1; sz<N; sz=sz+sz){          //sz子数组大小for(int lo=0; lo<N-sz; lo+=sz+sz){  //lo:子数组索引merge(a, lo, lo+sz-1, Math.min(lo+sz+sz-1, N-1));}}}public static void merge(Comparable[] a, int lo, int mid, int hi){//将a[lo..mid]和a[mid+1..hi]归并int i = lo, j = mid+1;//将a[lo..hi]复制到aux[lo..hi]for(int k=lo; k<=hi; k++){aux[k] = a[k];}//归并回到a[lo..hi]for(int k=lo; k<=hi; k++){if(i>mid){a[k] = aux[j++];}else if(j>hi){a[k] = aux[i++];}else if(less(aux[j], aux[i])){a[k] = aux[j++];}else{a[k] = aux[i++];}}}private static boolean less(Comparable v, Comparable w){return v.compareTo(w) < 0;}
}

View Code

自底向上的归并排序会多次遍历整个数组,根据子数组大小进行两两归并。子数组的大小sz的初始值为1,每次加倍。最后一个子数组的大小只有在数组大小是sz的偶数倍的时候才会等于sz(否则它会比sz小)

命题H:对于长度为N的任意数组,自底向上的归并排序需要1/2NlgN至NlgN次比较,最多访问数组6NlgN次。

p278页,这边看的不是太懂...

自底向上的归并排序比较适合用链表组织的数据。想象一下将链表先按大小为1的子链表进行排序,然后是大小为2的子链表,然后是大小为4的子链表等。这种方法只需要重新组织链表连接就能将链表原地排序(不需要创建任何新的链表结点)。

排序算法的复杂度

学习归并排序的一个重要原因是它是证明计算复杂性领域的一个重要结论的基础,而计算复杂性能够帮助我们理解排序自身固有的难易程度。计算复杂性在算法设计中扮演着非常重要的角色,而这个结论正是和排序算法的设计直接相关的,因此接下来我们就要详细地讨论它。

研究复杂度的第一步是建立一个计算模型。一般来说,研究者会尽量寻找一个和问题相关的最简单的模型。对排序来说,我们的研究对象是基于比较的算法,它们对数组元素的操作方式是由主键的比较决定的。一个基于比较的算法在两次比较之间可能会进行任意规模的计算,但它只能通过主键之间的比较得到关于某个主键的信息。因为我们局限于实现了Comparable接口的对象,本章中的所有算法都属于这一类(注意,我们忽略了访问数组的开销)。在第5章中,我们会讨论不局限于Comparable元素的算法。

命题I: 没有任何基于比较的算法能够保证使用少于lg(N!) ~ NlgN次比较将长度为N的数组排序。

p281页,这边的很多证明看不懂。

命题J: 归并排序是一种渐进最优的基于比较排序的算法。

但归并排序的最优性并不是结束,也不代表在实际应用中我们不会考虑其他的方法了,因为本节中的理论还是有许多局限性的,例如:

.归并排序的空间复杂度不是最优的;

.在实践中不一定会遇到最坏情况;

.除了比较,算法的其他操作(例如访问数组)也可能很重要;

.不进行比较也能将某些数据排序。

因此在本书中我们还将继续学习其他一些排序算法。

---

转载于:https://www.cnblogs.com/tenWood/p/10110895.html

算法(5) 归并排序相关推荐

  1. 排序算法中——归并排序和快速排序

    冒泡排序.插入排序.选择排序这三种算法的时间复杂度都为 $O(n^2)$,只适合小规模的数据.今天,我们来认识两种时间复杂度为 $O(nlogn)$ 的排序算法--归并排序(Merge Sort)和快 ...

  2. 【DS】排序算法之归并排序(Merge Sort)

    一.算法思想 归并排序是建立在归并操作上的一种有效的排序算法.该算法是采用分治法的一个非常典型的应用,指的是将两个已经排序的序列合并成一个序列的操作.其归并思想如下: 1)申请空间,使其大小为两个已经 ...

  3. 排序算法:归并排序、快速排序

    相关博客: 排序算法:冒泡排序.插入排序.选择排序.希尔排序 排序算法:归并排序.快速排序 排序算法:桶排序.计数排序.基数排序 排序算法:堆排序 十大排序算法小结 一.归并排序: 1.工作原理: 归 ...

  4. 数据结构与算法之归并排序

    数据结构与算法之归并排序 目录: 归并排序介绍 归并排序思想示意图 代码实现 1. 归并排序介绍 归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-a ...

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

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

  6. 排序算法之--归并排序(好玩的一个算法o。o)快速入门

    排序算法之--归并排序(好玩的一个算法o.o) 下面是归并操作的基本思路(注意:是归并操作哦,不是归并排序哦) 归并操作的工作原理如下: 第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存 ...

  7. NOI提高级:排序算法之归并排序、快速排序

    图解排序算法(四)之归并排序 图解排序算法(四)之归并排序 - dreamcatcher-cx - 博客园 小学生图解排序算法:⑥归并排序 小学生图解排序算法:⑥归并排序_纯文笔记-CSDN博客_图解 ...

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

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

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

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

最新文章

  1. QIIME 2教程. 24Python命令行模式Artifact API(2020.11)
  2. [zz]Ubuntu10.04源 更新源列表
  3. Linux 中如何复制和删除文件夹中的所有文件?
  4. 这份门禁系统培训PPT也太全面了,门禁系统知识,看这一篇就够了
  5. asp.net 2.0 中引用Web.config内的连接字符串的方法
  6. 数据结构与算法之“之”字型打印矩阵和矩阵中找数
  7. nssl1477-赛【对顶堆,贪心】
  8. linux0.11 init函数,linux0.11启动与初始化
  9. 《色彩解答》系列之二 色彩比例
  10. 学习Opencv笔记(二)————hsv色系
  11. [生存志] 第57节 孔子微言春秋大义
  12. 南京市儿童医院用医保身份(医保通道)网上预约挂号以及取号、付费看病流程
  13. 使用freemarker导出word含图片
  14. 微分方程建模(人口预测,捕食者猎物)
  15. 一些计算机u口无法使用的原因,电脑USB接口不能使用的原因分析
  16. 如何让技术大佬失去理智
  17. 做个grub的U盘启动盘,即将grub安装到U盘上面。
  18. [samtools] 文本查看语法,浏览SNP/INDEL位点
  19. 团队管理24--团建活动
  20. LeetCode总结——题1313、66、169、209

热门文章

  1. c# list集合根据某个字段去重_完美解决c# distinct不好用的问题
  2. pip install python-docx报错_python各种模块的安装
  3. html表格筛选排序规则,excel表的排序功能你真的会吗?带你重新认识真正的排序功能...
  4. matlab 画函数图像
  5. 强烈推荐——PQDT Open-ProQuest学位论文全文开放数据库
  6. 【 C 】对左值与右值的一些个人思考
  7. 有限状态机设计实例之空调控制器(Verilog HDL语言描述)(仿真与综合)(附用Edraw(亿图)画状态转移图)
  8. 数据库持久层封装设计
  9. Azure运维系列 3:善用Azure捕获功能事半功倍
  10. Ajax 通过 Request Payload 体发送 JSON 数据体