挑战408——数据结构(5)——合并算法

从之前的学习可以看到,对大型vectory要求的排序,选择排序算法显然不符合要求,因为运行时间与输入问题规模大小的平方成比例增加,对于以线性顺序处理向量的元素的大多数排序算法也是如此。 所以要采用不同的方法来开发更好的排序算法。我们可以试着反过来思考。

强大的分治法(divide-and-conquer)

分治法的具体详见
C++抽象编程——递归简介(1)——递归范式

我们先来看看排序算法的性能为什么在问题规模增大后变得如此糟糕?我们之前分析过二次复杂度(即O(N^2)类)的基本特征是,随着问题的大小增加,运行时间增加了问题规模的两倍(比如问题规模增加2倍,那么运行时间要增加4倍)然而,反过来我们可以这样想。 如果将二次问题的大小除以2,则可以将运行时间减少相同的四倍。 也就是说我们可以将vector除以一半,然后应用递归的方法继续将问题的规模拆分,就可以成倍的减少所需的排序时间。

举个例子,假设你有一个很大的vector需要排序。如果将vector分成两半,然后使用选择排序算法对这些片段进行排序,会发生什么? 因为选择排序是的复杂度是二次的,每个较小的vector需要原始时间的四分之一(问题规模减少了2倍,时间就提高4倍)。 当然,你需要对这两半分别进行排序,但是排序两个较小vector所需的总时间仍然是排序原始vector所需的时间的一半。如果分开一个vector的两半可以简化整个vector排序的问题,我们将能够大大减少排序需要的总时间。更重要的是,一旦发现如何在一个地方提高性能,就可以使用相同的算法递归地对每一个进行排序。
为了确定分治法是否适用于这个排序问题,我们需要确定一个问题,即将vector分为两个较小的vector,然后对每个vector进行排序是否有助于解决一般问题(也就是拿一个实例来分析一下)。假设你从一个包含以下八个元素的vector开始排序:
在这里插入图片描述
如果将8个元素的vector划分为长度为4的两个vector,然后对每个较小的vector进行排序,就会得到下图:
在这里插入图片描述
现在我们需要从这些较小的vector中取出值,并将它们以正确的顺序放回到原始vector中。

合并两个vector

从较小的排序vector重组成完整的vector比排序本身要简单得多。这个过程我们称为合并(merging)即完整排序中的第一个元素必须是v1中的第一个元素或v2中的第一个元素,以较小者为准。回到这个例子当中,

  1. 我们新组成的的vector中的第一个元素是第二个vector(v2)中的第一个元素。然后将该元素添加到空的向量vec,此时我们把v2的19叉掉,表示已经取出,我们下图的结果
    在这里插入图片描述
  2. 再来一次,下一个元素只能是两个较小向量之一中的第一个未取出的元素。比较v1中的25与v2中的30,并选择前者:
    在这里插入图片描述
  3. 重复此过程,从v1或v2中选择较小的值,直到重构整个vector
合并排序算法

合并操作与递归分解相结合,产生了一种称为合并排序的新的排序算法,可以直接实现。 算法的基本思想可以概括如下:

  1. 检查vector是否为空或只有一个元素。如果是这样,它肯定已经被排序。此条件用于定义递归的simple case。
  2. 将vector分成两个较小的vector,每个vector的大小是前者的一半(意味着,不是值分成两个vector,而是每个分开的vector还可以继续分,重复这个过程)
  3. 递归地对每个较小的vector进行排序。
  4. 清除原始的vector,使其再次为空。(用来储存新的排序好的数字)
  5. 将两个排序好的vector合并回原来的vector。
合并排序的C++代码

合并排序思路简单,但是实现起来并不那么容易,下面是本人写的C++代码,在VS2015中编译通过:

/*
* create by redAnt
* 2019年10月23日21:11:12
* 合并排序C++代码
* vector为STL自带的C++标准类
* /

#include <iostream>
#include <vector>
using namespace std;
/*函数原型*/ 
void sort(vector<int> & vec);
void merge(vector<int> & vec,vector<int> & v1,vector<int> & v2);
/*主函数*/ 
int main(){
    vector<int> vec; 
    for(int i = 0; i < 8; i++){
        int n; 
        cin >> n;
        vec.push_back(n); //向vec中添加数据
    }
    sort(vec);//执行合并排序算法
    for(int k = 0; k < vec.size(); k++){
        cout << vec[k] << " ";
    }
    return 0;
}
/*
 *函数:sort
 *用法:sort(vec)
 *---------------
 *该函数使用合并排序算法对向量的元素进行升序排序,包括以下步骤:
 *1.将vector分成两半
 *2.递归地对每个较小的vector进行排序
 *3.将两个排序好的vector合并回原来的vector。
 */ 
void sort(vector<int> & vec){
    int n = vec.size();
    if(n <= 1) return; //当n<=1 的时候,为simple case,直接返回 
    vector<int> v1,v2; //将vector分成两半,然后分别装进v1,v2中 
    for(int i = 0; i < n; i++){
        if(i < n/2){
            v1.push_back(vec[i]);
        }else{
            v2.push_back(vec[i]);
        }
    }
    //递归调用 
    sort(v1); 
    sort(v2);
    //清除vec ,用来装排好序的vector
    vec.clear();
    //合并 
    merge(vec,v1,v2);
}
/*
 *函数:merge
 *用法:merge(vec,v1,v2);
 *------------------------
 *此函数将两个排序的向量v1和v2合并到向量vec中,
 *该向量vec在此操作之前应为空。因为输入向量已经被排序。 
 *所以函数可以总是在其中一个输入向量中选择第一个未使用的元素来填充下一个位置。 
 */ 
void merge(vector<int> & vec,vector<int> & v1,vector<int> & v2){
    int n1 = v1.size(); 
    int n2 = v2.size();
    int p1 = 0;
    int p2 = 0;
    /*下面为什么是v1[p1++],而不是v1[p1]?自行补习i++的特性*/ 
    while(p1 < n1 && p2 < n2){
        if(v1[p1] < v2[p2]){
            vec.push_back(v1[p1++]);
        }else{
            vec.push_back(v2[p2++]);
        }
    }
    /*
     *当上面的代码执行完后,如果还有vector的大小为偶数,那么肯定有一个vector
     *比较长,合并后剩下还有元素未合并进去原始的vector(至多一个元素),所以我们
     *还要添加下面的判断,找到它到底是属于哪个vecc
     */ 
    while(p1 < n1){
        vec.push_back(v1[p1++]);
    }
    while(p2 < n2){
        vec.push_back(v2[p2++]);
    }
}

运行效果如图:
在这里插入图片描述
合并排序算法的代码可以整齐地分为两个函数:排序和合并。 排序代码直接来自算法的步骤。在检查特殊情况后,算法将原始vector分为两个较小的v1和v2。一旦sort代码将所有元素复制到v1或v2中,v1,V2就已经被创建,其余的函数会递归地排序这些vector,最后清除原始vector,然后调用merge来重新组合vector,从而实现合并排序。
实际上大部分的工作是通过合并函数完成的,该函数采用目标vec,以及较小的向量v1和v2。指标p1和p2标记跟踪每一个vector的下标。 在循环的每个循环中,该函数从v1或v2选择一个元素取较小者,并将该值添加到vec的末尾。一旦两个较小的vector中的任何一个的元素被取尽,该函数可以简单地从另一个vector中直接复制元素而再比较它们。实际上,因为这些向vector中的其中一个已经在第一个while循环退出时已经耗尽,所以该函数可以将vector的其余部分复制到vec。 其中一个vector为空,相应的while循环将完全不执行。

这里说一下,v1[p1++],其实我们都知道 i++返回的 i的值是自增1的。但是这个运算符返回的是自增前的值。也就是说比如 i = 2,执行

i++;

之后,就是 i = 3,但是 (i++)这个整体的值就还是 2(可以写个程序试试)。
所以说v1[p1++]这句代码等价于:
···
v1[p1];
p1 ++;
···
下一篇的文章我们就去分析一下这个算法的复杂度

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值