求最大子序列和的四种方法

求一个给定序列的连续子序列中和最大的那个子序列的和,下边方法只求和,没有找出最大子序列。

用到的头文件和宏定义如下

#include "stdafx.h"
#include<vector>
#include <string>
#include <fstream>
#include <random>
#include <time.h>
#include <iostream>
typedef long int l_int;

三层for循环穷举法

该方法设计三层for训话,第一层设定求和序列的开始位置,第二层设定求和位置的结束位置,第三层对子序列进行求和。例如该序列为a1 a2 a3 a4 a5 a6,那么先a1为开始位置,分别以a2、a3、a4、a5、a6为结束位置,对五个子序列分别求和,找出其最大和sum。接着以a2为起始位置,a3、a4、a5、a6为结束位置,分别求出四个子序列的和,并且和sum比较,找出最大和。以此类推。
l_int MaxSubSum2(std::vector<int> data){
    time_t t1,t2;
    time(&t1);
    l_int sum = 0;
    for (int i=0;i<data.size();i++)
    {
        for (int j=i;j<data.size();j++)
        {
            l_int temp = 0;
            for (int k=i;k<j;k++)
            {
                temp += data[k];
            }
            if (temp > sum)
            {
                sum = temp;
            }
        }
    }
    time(&t2);
    std::cout<<"MaxSubSum2: "<<sum<<" time:"<<t2-t1<<std::endl;
    return sum;
}

两层for穷举法

观察三层for穷举法,会发现在计算起始点相同的各个子序列和时,长序列包括了各个短序列的和。例如计算a1+a2+a3时,需要计算a1+a2。计算a1+a2+a3+a4+a5+a6时,可以分别得出各个子序列a1+a2、a1+a2+a3、a1+a2+a3+a4、a1+a2+a3+a4+a5的和,那么三层for穷举法可以做一下修改:
l_int MaxSubSum3(std::vector<int> data){
    time_t t1,t2;
    time(&t1);
    l_int sum = 0;
    for (int i=0;i<data.size();i++)
    {
        l_int temp = 0;
        for (int j=i;j<data.size();j++)
        {
            temp += data[j];
            if (temp > sum)
            {
                sum = temp;
            }
        }
    }
    time(&t2);
    std::cout<<"MaxSubSum3: "<<sum<<" time:"<<t2-t1<<std::endl;
    return sum;
}

一层for循环穷举法

如果a[i]是负数那么它不可能代表最有序列的起点,因为任何包含a[i]的作为起点的子序列都可以通过用a[i+1]作为起点来改进。类似的有,任何的负的子序列不可能是最优子序列的前缀。例如说,循环中我们检测到从a[i]到a[j]的子序列是负数,那么我们就可以推进i。关键的结论是我们不仅可以把i推进到i+1,而且我们实际可以把它一直推进到j+1。

l_int MaxSubSum4(std::vector<int> data){
    time_t t1,t2;
    time(&t1);
    l_int sum = 0;
    l_int temp = 0;
    for (int i=0;i<data.size();i++)
    {
            temp += data[i];
            if (temp > sum)
            {
                sum = temp;
            }
            else if(temp<0){
                temp = 0;
            }
    }
    time(&t2);
    std::cout<<"MaxSubSum4: "<<sum<<" time:"<<t2-t1<<std::endl;
    return sum;
}

递归法

最大子序列可能在三个地方出现,或者在左半部,或者在右半部,或者跨越输入数据的中部而占据左右两部分。前两种情况递归求解,第三种情况的最大和可以通过求出前半部分最大和(包含前半部分最后一个元素)以及后半部分最大和(包含后半部分的第一个元素)相加而得到。(算法导论)
l_int max3(l_int left,l_int right, l_int cross );
l_int re_MaxSubSum1(std::vector<int> data , int left, int right);

l_int MaxSubSum1(std::vector<int> data){
    time_t t1,t2;
    time(&t1);  
    l_int sum = re_MaxSubSum1(data,0,data.size()-1);
    time(&t2);
    std::cout<<"MaxSubSum1: "<<sum<<" time:"<<t2-t1<<std::endl;
    return sum;
}

l_int re_MaxSubSum1(std::vector<int> data , int left, int right){
    if (left == right)
    {

        //return (data[left]>0?data[left]:0);
        return data[left];
    }
    int cent = (left+right)/2;
    l_int MaxLeft = re_MaxSubSum1(data, left,cent);
    l_int MaxRight = re_MaxSubSum1(data,cent+1,right);


    l_int CrossSum_Ltemp = 0;
    l_int CrossSumL = 0;
    for (int i = cent;i>=left;i--)
    {
        CrossSum_Ltemp += data[i];
        if (CrossSum_Ltemp>CrossSumL)
        {
            CrossSumL = CrossSum_Ltemp;
        }
    }

    l_int CrossSum_Rtemp = 0;
    l_int CrossSumR = 0;
    for (int i = cent+1;i<=right;i++)
    {
        CrossSum_Rtemp += data[i];
        if (CrossSum_Rtemp>CrossSumR)
        {
            CrossSumR = CrossSum_Rtemp;
        }
    }
    l_int SumCross = CrossSumL+CrossSumR;
    return max3(MaxLeft,MaxRight,SumCross);
}

l_int max3(l_int left,l_int right, l_int cross ){
    l_int temp = left;
    if (temp < right)
    {
        temp = right;
    }
    if (temp < cross)
    {
        temp = cross;
    }
    return temp;
}

http://blog.163.com/kevinlee_2010/blog/static/169820820201010495438247/作者对四种方法也做了总结,本文主要参考该作者的方法。

测试

读测试文件函数

将测试文件的数据读入容器

bool readfile(std::vector<int> &data,std::string filename){
    std::ifstream infile(filename);
    if (!infile)
    {
        return false;
    }
    int s;
    while (infile>>s)
    {
        data.push_back(s);
    }
    return true;
}
生成测试文件函数

用C++11标准的随机数引擎和分布生成给定数量,一定范围内的随机数

bool writefile(l_int num,int max_nun,std::string filename = "text.txt"){
    std::ofstream outfile(filename);
    if (!outfile)
    {
        return false;
    }
    std::uniform_int_distribution<int> u(0,max_nun);
    std::default_random_engine e;
    for (int i =0;i<num;i++)
    {

        outfile<<(u(e)%2==0?u(e):~u(e))<<'\n';
    }
    return true;
}
测试函数
分别调用四种方法,观察运行时间,实验证明,一层for循环穷举法换时间最少,三层for循环最多,但数据数目上千后递归法耗时不比二层for循环穷举法短,主要因为其间函数调用耗时长。
int _tmain(int argc, _TCHAR* argv[])
{
    if(!writefile(100000,200)){
        return 0;
    }
    std::vector<int> data;
    if(!readfile(data,"text.txt")){
        return 0;
    }   
    l_int sum4 = MaxSubSum4(data);
    l_int sum3 = MaxSubSum3(data);
    l_int sum1 = MaxSubSum1(data);
    l_int sum2 = MaxSubSum2(data);
    return 0;
}

你可能感兴趣的:(C++,递归,穷举,四种方法,最大子序列求和)