Motivate
MergeSort是个相对古老的算法了,为什么现在我们还要讨论这么古老的东西呢?有几个原因:
  • 它虽然年龄很大了,但是在实践中一直被沿用,仍然是很多程序库中的标准算法之一。
  • 实现它的本质是分治思想,是一个理解分治算法思想的好例子,好起点。
  • 本文会使用“递归树”来对它进行运行时间分析,后面会集合这种思路生成“主方法”。
题目
 
输入一个数组,数组里面的每个数字是不重复的,输出是已经排序好的数组。
比如输入的是:
期望输出的是:
可能之前我们有所知道一些排序算法,比如SelectionSort,扫描全数组找到最小元素,把它放到输出数组的第一位,接着扫描复制次小的元素,以此类推;比如BubbleSort,对相邻无序的元素进行比较,执行反复的交换,直到最后数组完成排序。等等。但这些算法的问题就是运行时间是平方级的。那我们来看看今天这个排序算法用更少的运行时间是怎么实现的。
例子 想要理解MergeSort算法是如何运行的,一个最简单的方法就是看看具体的例子。
整体过程就是:
它把数组 [5, ,4, 1, 8, 7, 2, 6, 3] 划分为更小的数组(子问题)[5, 4, 1, 8] 和 [7, 2, 6, 3]排序,通过神奇的递归操作,得到每个排序后的子数组,再将子数组合并起来。
伪代码 将上面的图换成伪代码就是这样的过程
而这个Merge程序怎么实现呢?
由上面的图我们可以知道,Merge的时候,其实输入两个已经排序好的数组C, D,再把它们排序输出到B。
索引 k 操控的是输出数组,索引 i,j 操控的是已排序好的C和D子数组,都是从左向右扫描。每一次的for循环,其实就是找C和D中最小的数字,因为C和D是已排序好的数组,所以最小的数字就是i或j对应的元素。比较后把它放入输出数组B中,并将对应的索引+1,这样下次循环就跳过已复制的元素了。所以最后的数组B输出是按序方式生成的。
算法分析
我们先来对Merge程序算算它的执行操作数量。 第1,2行有一次赋值操作。 第3行是一个for循环,每一个for循环里,有比较操作(C[i]和D(i)比较),赋值操作(B[k]的赋值),递增操作(i或j加1),循环变量k还要加1,所以每一次循环一共有4次操作。
一共就是4n+2次操作,为了后面的计算方便,当n>=1时,4n+2<6n(去掉常数项), 我们取6n次操作作为Merge程序操作上界。
我们现在再来看MergeSort的运行时间。 为了简单起见,假如输入数组的长度是n的2次方(如果没有这个假设只需要额外工作就能消除这个假设),我们用递归树的方法来分析运行时间的上界,每一个节点就表示一次递归调用。
最外层是整个原始的输入数组,它在进行MergeSort的时候会有2个递归调用,所以这是一个二叉树(每个节点都有两个子节点),第一层的2个节点就是原始数组的左半部分和右半部分,再次递归最后到达最底层,一个长度为1或0的数组。我们需要知道几个数量:
原始数组长度是n,递归树有多少层?
由于每深入一层,数组长度就缩小一半,第0层是n,第1层是n/2(除了一次2),第2层是n/4(除了2次2),最后一层的数组长度是不大于1的,就是除以了log2(n)次2,所以最后一层是log2(n)层。(也可以假定n/2^a = 1, 求解a)如果没有n是2的次方这个假设,就向上取整。一共就是log2(n) +1层。
递归树的第j层有多少个节点(子问题)?每个节点的数组长度是多少?
因为每一层的递归数量是前一层的两倍,所以第j层就有2^j个子问题。每个节点的长度,总长度是n,均分到每个节点就是n/(2^j)个长度。
所以总的运行时间就是:
层数 * 每一层的工作量
= 层数 * (第 j 层的子问题数量 * 每个第j层子问题完成的工作数) = 层数 * (第 j 层的子问题数量 * (每个第j层子问题的规模 * Merge的时间))
= (log2(n)+1) * ( 2^j * n/(2^j) * 6n)
=6n * log2(n) +6n
= O(nlogx(n))
这时候,我们就可以理直气壮的说递归的分治算法比其它更简单的算法要快的多啦。看图直观感受一下
当数据非常大的时候,它是非常有优势的,指数函数增长十分的缓慢。
今日互动
有什么不明白的欢迎在评论区留言。
 

斯坦福算法分析和设计_2. 排序算法MergeSort的更多相关文章

  1. 在一个N个整数数组里面,有多个奇数和偶数,设计一个排序算法,令所有的奇数都在左边。

    //在一个N个整数数组里面,有多个奇数和偶数,设计一个排序算法,令所有的奇数都在左边. // 例如: 当输入a = {8,4,1,6,7,4,9,6,4}, // a = {1,7,9,8,4,6,4 ...

  2. java排序算法(三):堆排序

    java排序算法(三)堆排序 堆积排序(HeapSort)是指利用堆积树这种结构所设计的排序算法,可以利用数组的特点快速定位指定索引的元素.堆排序是不稳定的排序方法.辅助空间为O(1).最坏时间复杂度 ...

  3. 算法分析中最常用的几种排序算法(插入排序、希尔排序、冒泡排序、选择排序、快速排序,归并排序)C 语言版

    每次开始动手写算法,都是先把插入排序,冒泡排序写一遍,十次有九次是重复的,所以这次下定决心,将所有常规的排序算法写了一遍,以便日后熟悉. 以下代码总用一个main函数和一个自定义的CommonFunc ...

  4. Python排序算法(六)——归并排序(MERGE-SORT)

    有趣的事,Python永远不会缺席! 如需转发,请注明出处:小婷儿的python https://www.cnblogs.com/xxtalhr/p/10800699.html 一.归并排序(MERG ...

  5. 简单排序算法设计(Java)

    总共有八种排序算法,还是慢慢看吧 1.简单排序算法 简单排序算法就是设置标兵,逐个比较数,然后查找插入位置,插入 public static void p(int[] a){ for(int i=0; ...

  6. 排序算法之归并排序(Mergesort)解析

    转自:http://www.cnblogs.com/ayqy/p/4050452.html   一.归并排序的优缺点(pros and cons) 耗费心思来理解它,总要有个理由吧: 归并排序的效率达 ...

  7. 十大经典排序算法总结(JavaScript描述)

    前言 读者自行尝试可以想看源码戳这,博主在github建了个库,读者可以Clone下来本地尝试.此博文配合源码体验更棒哦~~~ 个人博客:Damonare的个人博客 原文地址:十大经典算法总结 这世界 ...

  8. 转载部长一篇大作:常用排序算法之JavaScript实现

    转载部长一篇大作:常用排序算法之JavaScript实现 注:本文是转载实验室同门王部长的大作,找实习找工作在即,本文颇有用处!原文出处:http://www.cnblogs.com/ywang172 ...

  9. 常用排序算法之JavaScript实现

    1.插入排序 1)算法简介 插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法.它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从 后向前扫描,找到相应位置并插入 ...

随机推荐

  1. Python--day48--面向对象回顾

    面向对象回顾: 例1: 例2: 特殊方法(要背会):

  2. [转]【Linux】Linux 目录结构

    初学Linux,首先需要弄清Linux 标准目录结构 / root --- 启动Linux时使用的一些核心文件.如操作系统内核.引导程序Grub等. home --- 存储普通用户的个人文件 ftp ...

  3. java 代理的概念与作用

    1.引入: 为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理.日志.计算方法的运行时间.事务管理.等等,你准备如何做? 编写一个与目标类具有相同接口的代理类,代理类的每个方 ...

  4. SVG基础绘图实例

    SVG可缩放矢量图(Scalable Vector Graphics),是使用 XML 来描述二维图形和绘图程序的语言,图像在放大或改变尺寸的情况下其图形质量不会有所损失,是万维网联盟的标准. 下面整 ...

  5. dotnet 启动 JIT 多核心编译提升启动性能

    用2分钟提升十分之一的启动性能,通过在桌面程序启动 JIT 多核心编译提升启动性能 在 dotnet 可以通过让 JIT 进行多核心编译提升软件的启动性能,在默认托管的 ASP.NET 程序是开启的, ...

  6. dotnet 线程静态字段

    在 dotnet 程序提供了一个好用的特性,可以让字段作为线程的静态字段,也就是在相同线程的所有代码访问的静态字段是相同对象,但不同线程访问的时候是不同的 在 .NET 程序可以使用 ThreadSt ...

  7. 【Linux】CentOS 7.5 修改时区

    1⃣️查看当前CentOS系统版本: [parallels@k8s-node2 ~]$ cat /etc/redhat-release CentOS Linux release 7.5.1804 (C ...

  8. UTF-8、UTF-16、UTF-32编码的相互转换(不使用现成的函数)

    最近在考虑写一个可以跨平台的通用字符串类,首先需要搞定的就是编码转换问题. vs默认保存代码文件,使用的是本地code(中文即GBK,日文即Shift-JIS),也可以使用带BOM的UTF-8.gcc ...

  9. RecursiveTask和RecursiveAction的使用总结

    一:什么是Fork/Join框架    Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架.我们再通 ...

  10. 小白进阶之路-python数据类型

    1.数据类型:变量值是我们存储的数据,所以数据类型值得就是变量的不同种类 2.数据分类型的原因:变量值是用来保存现实世界的中的状态的,呢么针对不同的状态就应该用不同类型上午数据去表示 (1)整型int ...