在讲解合并排序之前,我们先来想一想下面这个问题如何解决:

  有两个数组A和B,它们都已各自按照从小到大的顺序排好了数据,现在我们要把它们合并为一个数组C,且要求C也是按从小到大的顺序排好,请问该怎么做?

  这个问题非常容易解决,我们将A、B和C都视为队列,然后不断比较A和B的首部,取出其中更小的数据出队,然后将该数据插入队列C,若A或B有一方为空了,则将另一方数据顺序出队再插入C即可:

int i=,j=,z=;
while(i<a_size && j<b_size)
{
if(a[i]<b[j])
c[z++]=a[i++];
else
c[z++]=b[j++];
}
while(i<a_size)
c[z++]=a[i++];
while(j<b_size)
c[z++]=b[j++];

  显然,上述问题的解决非常简单,时间复杂度为O(N),N为总数据量。也就是说,如果我们有两个已排好序的数组,那么我们就可以快速地将它们合并起来,而这,就是合并排序的根本思想。

  回顾快速排序,可以看出快速排序的做法其实就是:选取枢纽,将小于枢纽的元素组成一个子数组,大于枢纽的元素组成一个子数组,只要这两个子数组排好序,整个数组就能排好序,至于两个子数组怎么排好序,递归实现。

  合并排序的做法与快速排序有些类似,只是“过程”反了过来:将原数组对半分为两个子数组,只要这两个子数组排好序,我就能将它们通过合并(上述问题的解法)来得到有序的原数组,至于怎么得到两个排好序的子数组,递归实现。

  用伪代码来表示合并排序的过程,就是这样:

void MergeSort(int *src,unsigned int left,unsigned int right)
{
if(left<right)
{
/*将原数组一分为二*/
unsigned int center= (left+right)/;
/*通过递归实现子数组的排序*/
MergeSort(src,,center);
MergeSort(src,center+,right);
/*伪代码:将子数组合并*/
}
}

  有了伪代码后,剩下的工作就是将伪代码中的“空”填上去。

  上述伪代码有两个“空”,一个是实现递归的基准情形,这个“空”很好填,因为根本不用填,为什么呢?因为只要数组大小大于1,我们就一直划分下去,那么最终划分得到的子数组将会只有1个数据,此时这个子数组必为“有序”,也就是说递归的基准情形必然存在。

  另一个“空”则是实现子数组的合并,这个“空”我们可以参考本文一开始提出的问题的解法,但是该解法需要用到一个额外的数组C,且最终的有序数据都放进了C里面,该怎么办呢?思路很简单也很直接,那就是:既然你要一个额外数组,那我就给你一个额外数组tempArr,有序数据在tempArr里面,而我希望它们在原数组里面,那我就将tempArr里的数据复制回来:

        /*将子数组合并到tempArr*/

        unsigned int i=left,j=center+,z=left; //注意,i,j,z的初始化和范围都要有所变化
while(i<=center&&j<=right)
{
if(src[i]<src[j])
tempArr[z++]=src[i++];
else
tempArr[z++]=src[j++];
}
while(i<=center)
tempArr[z++]=src[i++];
while(j<=right)
tempArr[z++]=src[j++]; /*将tempArr的数据拷贝到原数组中*/
for (z = left;z <= right;++z)
src[z] = tempArr[z];

  将上面的代码填入到伪代码中,并将伪代码的参数稍加修改,便有了如下合并排序:

void MSort(int *src, int *tempArr, unsigned int left, unsigned int right)
{
if (left < right)
{
/*将原数组一分为二*/
unsigned int center = (left + right) / ; /*递归实现子数组排序*/
MSort(src, tempArr, left, center);
MSort(src, tempArr, center + , right); /*将子数组合并到tempArr*/
unsigned int i=left,j=center+,z=left;
while(i<=center&&j<=right)
{
if(src[i]<src[j])
tempArr[z++]=src[i++];
else
tempArr[z++]=src[j++];
}
while(i<=center)
tempArr[z++]=src[i++];
while(j<=right)
tempArr[z++]=src[j++]; /*将tempArr的数据拷贝到原数组中*/
for (int i = left;i <= right;++i)
src[i] = tempArr[i];
}
}

  为了方便调用,我们再实现一个小接口:

void MergeSort(int *src, unsigned int size)
{
int *tempArr = (int *)malloc(sizeof(int)*size);
MSort(src, tempArr, , size - );
free(tempArr);
}

  

  至此,合并排序就实现完毕了,其占用的空间显然比快速排序等算法要多出一倍,那么其时间复杂度如何呢?我们就来简单的算算看。

  首先,我们假设进行合并排序的数组大小为N且为2的幂,T(N)表示对其排序耗费的时间,那么就有:

  1.T(1)=1

  2.T(N)=2*T(N/2)+2N(两个N分别为合并耗费时间和拷贝回原数组所耗费的时间)

  将2式左右除以N,得:

  T(N)/N=T(N/2)/(N/2)+2

  递推该式,得:

  T(N/2)/(N/2)=T(N/4)/(N/4)+2

  T(N/4)/(N/4)=T(N/8)/(N/8)+2

  ……
  T(2)/2=T(1)/1+2

  将上述所有式子左侧相加且右侧相加,得:

  T(N)/N+T(N/2)/(N/2)+T(N/4)/(N/4)+……+T(2)/2=T(N/2)/(N/2)+T(N/4)/(N/4)+……+T(1)/1+2*logN

  化简,得:

  T(N)/N=T(1)/1+2*logN,即T(N)=N+2*N*logN=O(N*logN)

  这个时间复杂度与快速排序的平均时间复杂度相同,比快速排序的最坏情况要好得多,但是在实际应用中快速排序要比合并排序优先考虑,原因在于合并排序需要更多的内存空间,并且从tempArr拷贝数据回原数组也是一项花费巨大的工作。

深入浅出数据结构C语言版(21)——合并排序的更多相关文章

  1. 深入浅出数据结构C语言版(22)——排序决策树与桶式排序

    在(17)中我们对排序算法进行了简单的分析,并得出了两个结论: 1.只进行相邻元素交换的排序算法时间复杂度为O(N2) 2.要想时间复杂度低于O(N2),算法必须进行远距离的元素交换 而今天,我们将对 ...

  2. 深入浅出数据结构C语言版(17)——有关排序算法的分析

    这一篇博文我们将讨论一些与排序算法有关的定理,这些定理将解释插入排序博文中提出的疑问(为什么冒泡排序与插入排序总是执行同样数量的交换操作,而选择排序不一定),同时为讲述高级排序算法做铺垫(高级排序为什 ...

  3. 深入浅出数据结构C语言版(17)——希尔排序

    在上一篇博文中我们提到:要令排序算法的时间复杂度低于O(n2),必须令算法执行"远距离的元素交换",使得平均每次交换减少不止1逆序数. 而希尔排序就是"简单地" ...

  4. 深入浅出数据结构C语言版(16)——插入排序

    从这一篇博文开始,我们将开始讨论排序算法.所谓排序算法,就是将给定数据根据关键字进行排序,最终实现数据依照关键字从小到大或从大到小的顺序存储.而这篇博文,就是要介绍一种简单的排序算法--插入排序(In ...

  5. 深入浅出数据结构C语言版(5)——链表的操作

    上一次我们从什么是表一直讲到了链表该怎么实现的想法上:http://www.cnblogs.com/mm93/p/6574912.html 而这一次我们就要实现所说的承诺,即实现链表应有的操作(至于游 ...

  6. 深入浅出数据结构C语言版(1)——什么是数据结构及算法

    在很多数据结构相关的书籍,尤其是中文书籍中,常常把数据结构与算法"混合"起来讲,导致很多人初学时对于"数据结构"这个词的意思把握不准,从而降低了学习兴趣和学习信 ...

  7. 深入浅出数据结构C语言版(8)——后缀表达式、栈与四则运算计算器

    在深入浅出数据结构(7)的末尾,我们提到了栈可以用于实现计算器,并且我们给出了存储表达式的数据结构(结构体及该结构体组成的数组),如下: //SIZE用于多个场合,如栈的大小.表达式数组的大小 #de ...

  8. 深入浅出数据结构C语言版(12)——从二分查找到二叉树

    在很多有关数据结构和算法的书籍或文章中,作者往往是介绍完了什么是树后就直入主题的谈什么是二叉树balabala的.但我今天决定不按这个套路来.我个人觉得,一个东西或者说一种技术存在总该有一定的道理,不 ...

  9. 深入浅出数据结构C语言版(15)——优先队列(堆)

    在普通队列中,元素出队的顺序是由元素入队时间决定的,也就是谁先入队,谁先出队.但是有时候我们希望有这样的一个队列:谁先入队不重要,重要的是谁的"优先级高",优先级越高越先出队.这样 ...

随机推荐

  1. 关于spingMVC使用时配置自动扫描出现的路径报错

    com.lab –controller –service –dao –pojo <context:component-scan base-package=" " /> ...

  2. Java温故而知新-空心菱形

    空心菱形 今天做题练手,题目是空心菱形,一开始没什么思路,去网上找了找,发现很难找到讲的通透的,自己现在独立做出来了,记录一下,以备后用. * * * * * * * * * * * * * * * ...

  3. 如何搭建Zookeeper集群

     ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应用提供一致性服务的软件,提供的 ...

  4. 前端开发【第3篇:JavaScript序】

    JavaScript历史 聊聊JavaScript的诞生 JavaScirpt鼻祖:Bremdan Eich(布兰登·艾奇),JavaScript的诞生于浏览器的鼻祖网景公司(Netscape),发布 ...

  5. Java缓存类的实际应用场景

    不要着迷于技术,应把注意力放到问题上. 一个普通的后台管理系统,一定会有参数配置.参数配置数据表和其他的数据表是不同的,它的操作基本都是查的操作.参数配置的这些数据信息是贯穿在整个项目中,那么把他们放 ...

  6. poj 3635 带花费的Dij+head优化

    练习!! 这里主要需要注意的是进队的条件和dp[][]状态的控制,dp[i][j]表示到第i个城市剩余汽油为j的最小花费. 代码: #include<iostream> #include& ...

  7. js中防止全局变量被污染的方法

    (function ($){ var oDiv = $('div1');//获取id为'div1'的页面元素 })(function $(id){ return document.getElement ...

  8. [我所理解的REST] 3.基于网络应用的架构

    上篇中解释到什么是架构风格和应该以怎样的视角来理解REST(Web的架构风格).本篇来介绍一组自洽的术语,用它来描述和解释软件架构:以及列举下对于基于网络的应用来说,哪些点是需要我们重点关注的. 1 ...

  9. 团队作业4——第一次项目冲刺(Alpha版本)第一天+第二天+第三天+第四天+第五天+第六天+第七天

    冲刺第一天 一.Daily Scrum Meeting照片 二.每个人的工作 1.今天计划完成的任务 GUI.计时功能.题目生成 2.工作中遇到的困难 刚开始在计时功能模块只能做到秒位,经过查询资料后 ...

  10. 201521123107 《Java程序设计》第2周学习总结

    第2周作业-Java基本语法与类库 1.本周学习总结 要点主要有: (1)String类 String类是本周的一个重点,String类的对象是不可变的,即String对象后就在内存中开辟了一个字符串 ...