zoukankan      html  css  js  c++  java
  • 浅入浅出数据结构(21)——合并排序

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

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

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

    int i=0,j=0,z=0;
    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)/2; /*通过递归实现子数组的排序*/ MergeSort(src,0,center); MergeSort(src,center+1,right);
    /*伪代码:将子数组合并*/
    }
    }

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

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

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

            /*将子数组合并到tempArr*/
    
            unsigned int i=left,j=center+1,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) / 2;
            
            /*递归实现子数组排序*/
            MSort(src, tempArr, left, center);
            MSort(src, tempArr, center + 1, right);
            
            /*将子数组合并到tempArr*/
            unsigned int i=left,j=center+1,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, 0, size - 1);
        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拷贝数据回原数组也是一项花费巨大的工作。

     

  • 相关阅读:
    【转载】[SMS]SMS内容的7bit和UCS2编码方式简介
    【转载】两篇关于字符编码的博文
    【IRA/GSM/UCS2】the difference of IRA/GSM/UCS2 character set
    【LTE】LTE中SINR的理解
    【LTE】为什么使用SNR来表征信道质量,而并不用RSRQ?这两者的区别是什么?
    【C++】C++为什么要引入引用这个复合类型?
    【HTML55】HTML5与CSS3基础教程
    python 三种单例模式
    python3.10 新增的 match case 语句
    Python pyqt5简单的图片过滤工具
  • 原文地址:https://www.cnblogs.com/mm93/p/7597010.html
Copyright © 2011-2022 走看看