2、最大子序列和问题的四个算法

来源:互联网 发布:淘宝闲鱼官方下载 编辑:程序博客网 时间:2024/06/06 00:40

在本书的第二章,作者提到了最大子序列和问题:在一个列表中,连续多个数的最大值可以达到多少?为方便起见,如果全部数为负数,则最大值为0。例如,输入-2,11,-4,13,-5,-2,答案为20。

作者提到了4个算法,相差很大。本文复习之,并对其算法复杂度进行分析。

1、生成数据

稍微改一下之前的数据,我们用Python写出这样的代码:

# -*- coding:utf-8 -*- # 采用Python3.6编写import randomimport datetimeimport timedataCount = 5000    def genDataBase1(fileName,dataCount):    outp = open(fileName,'w')    i = 0                                                                         while i<dataCount:        value=random.random()*dataCount-dataCount/2        mLine = "%f\n"%(value)        outp.write(mLine)        i += 1    outp.close()if __name__ == "__main__":    random.seed()    start = time.time()    genDataBase1('F:\Data Structures and Algorithm Analysis in C\\Largest_subsequence.txt',dataCount)    end = time.time()    print('use time:',(end-start))    print('Ok')

2、利用C++实现第一个算法

第一个算法的思路用三个for循环嵌套,第一层是序列的长度,第二层是起始元素的索引值,第三层是在子序列中的索引值。这是一种穷举遍历的方式,算法思路最简单,运算时间最长。
由于C++要计算很长的时间,因此数据量改成了5000个。
用时24.279s,答案为:163908。
显然是一个三阶方法,即O(N^3)。

#include <iostream>#include<string>#include<fstream>#include<vector>#include <sstream>#include <time.h>using namespace std;int main(){    //读取txt的输入流,要#include<fstream>    string filename = "F:\\Data Structures and Algorithm Analysis in C\\Largest_subsequence.txt";    ifstream infile(filename.c_str());    string temp;    int len, index, i, N,position,length;    float value,sum, sumMax;    vector<float> list;    clock_t start_time = clock();//声明计时器对象并开始计时 ,要#include <time.h>    while (getline(infile, temp))    {        //利用字符串流将字符串转换为浮点数,要#include <sstream>        stringstream stream(temp);        stream >> value;        //将读入的浮点数添加到列表内        list.push_back(value);                          }    N = list.size();    sumMax = 0;    for (len = 1; len <= N; len++)    {        for (index = 0; index <= N - len; index++)        {            sum = 0;            for (i = 0; i < len; i++)            {                sum += list[index + i ];                if (sum > sumMax)                {                    sumMax = sum;                    position = index;                    length = len;                }            }        }    }    clock_t end_time = clock();    //static_cast是标准转换运算符,确保类型转换成功。CLOCKS_PER_SEC定义了每秒钟包含多少了时钟单元数    cout << "运行时间:" << static_cast<double>(end_time - start_time) / CLOCKS_PER_SEC << "s" << endl;//输出已流失的时间    cout << "答案为:" << sumMax << endl;//输出在10000个数中最大序列值    cout << "起始位置:" << position << endl;    cout << "终止位置:" << position+length-1 << endl;    getchar();}

3、利用C++实现第二个算法

第二个算法的思路
用时0.024s,答案为:163908。
第一个算法的第三个for循环中有大量不必要的重复计算,如:计算i到j的和,然而i到j-1的和在前一次的循环中已经计算过,无需重复计算,故该for循环可以去掉。
显然,该算法的时间复杂度是O(N^2)。

#include <iostream>#include<string>#include<fstream>#include<vector>#include <sstream>#include <time.h>using namespace std;int main(){    //读取txt的输入流,要#include<fstream>    string filename = "F:\\Data Structures and Algorithm Analysis in C\\Largest_subsequence.txt";    ifstream infile(filename.c_str());    string temp;    int i, index, N,start,end;    float value,sum, sumMax;    vector<float> list;    clock_t start_time = clock();//声明计时器对象并开始计时 ,要#include <time.h>    while (getline(infile, temp))    {        //利用字符串流将字符串转换为浮点数,要#include <sstream>        stringstream stream(temp);        stream >> value;        //将读入的浮点数添加到列表内        list.push_back(value);                          }    N = list.size();    sumMax = 0;    for (index = 0; index < N; index++)    {        sum = 0;        for (i = index; i < N; i++)        {            sum += list[i];            //每次累加后就与sumMax比较            if (sum > sumMax)            {                sumMax = sum;                start = index;                end = i;            }        }    }    clock_t end_time = clock();    //static_cast是标准转换运算符,确保类型转换成功。CLOCKS_PER_SEC定义了每秒钟包含多少了时钟单元数    cout << "运行时间:" << static_cast<double>(end_time - start_time) / CLOCKS_PER_SEC << "s" << endl;//输出已流失的时间    cout << "答案为:" << sumMax << endl;//输出在10000个数中最大序列值    cout << "起始位置:" << start << endl;    cout << "终止位置:" << end << endl;    getchar();}

4、利用C++实现第三个算法

用时0.009s,答案为:163908。
该算法利用“分而治之”的思想,将整个数组分成左半部和右半部,则最大子序列可能出现在左侧、右侧、跨两侧。如果是第三种情况,则最大和可以通过求出左侧最大和+左侧剩余靠近中部的所有元素(如果还有剩余)和+右侧最大和+右侧剩余靠近中部的所有元素(如果还有剩余)。
例如对于数列:4 -3 5 -2 -1 2 6 -2
左侧最大和为6,右侧最大和为8,中部最大和为:6-2-1+8=11。比较三个数大小,则中部最大和为全数列的最大子序列和。
该算法采用递归的思想,首先计算出从left到center之间的所有的元素的最大和,从center往左推进,再算出从center到right的所有元素的最大和,从center往右推进,两者之和即为中部最大和。再与递归得到的左侧最大和、右侧最大和进行比较,得到的值最大值为需要求的最大子序列和。

该算法的复杂度分析:

令T(N)是求解大小为N的最大子序列和问题所需要的时间,则如果N=1,算法3执行的时间成为一个单元时间,于是T(1)=1,否则程序需要运行两次递归调用,N=2,执行两次递归调用,每次递归调用的时间为O(N)。这是因为center = (left + right) / 2;和return Max3(maxLeftSum, maxRightSum, maxLeftBorderSum + maxRightBorderSum);均为O(1),直接忽略,两个for循环为O(N),则共计为O(N)。

接着,我们知道T(1)=1;T(N)=T(N/2)*2+O(N)=T(N/2)*2+N。
如果N=2^(m),则T(N)=2^m*T(1)+m*N;
则T(N)=N+m*N; 忽略N项,则T(N)=N*log(N)。

#include <iostream>#include<string>#include<fstream>#include<vector>#include <sstream>#include <time.h>using namespace std;float Max3(float a, float b, float c){    if (a >= b&&a >= c)        return a;    if (b >= a&&b >= c)        return b;    if (c >= a&&c >= b)        return c;}float MaxSubSum(vector<float> &A, int left, int right){    float maxLeftSum, maxRightSum;    float maxLeftBorderSum, maxRightBorderSum;    float LeftBorderSum, RightBorderSum;    int center, i;    if (left == right)    {        return (A[left] > 0)?A[left]:0;    }    center = (left + right) / 2;    maxLeftSum = MaxSubSum(A, left, center);    maxRightSum = MaxSubSum(A, center+1, right);    maxLeftBorderSum = 0;    LeftBorderSum = 0;    for (i = center; i >= left; i--)    {        LeftBorderSum += A[i];        if (LeftBorderSum > maxLeftBorderSum)        {            maxLeftBorderSum = LeftBorderSum;        }    }    maxRightBorderSum = 0;    RightBorderSum = 0;    for (i = center+1; i <= right; i++)    {        RightBorderSum += A[i];        if (RightBorderSum > maxRightBorderSum)        {            maxRightBorderSum = RightBorderSum;        }    }    return Max3(maxLeftSum, maxRightSum, maxLeftBorderSum + maxRightBorderSum);}int main(){    //读取txt的输入流,要#include<fstream>    string filename = "F:\\Data Structures and Algorithm Analysis in C\\Largest_subsequence.txt";    ifstream infile(filename.c_str());    string temp;    int N;    float value,sumMax;    vector<float> list;    clock_t start_time = clock();//声明计时器对象并开始计时 ,要#include <time.h>    while (getline(infile, temp))    {        //利用字符串流将字符串转换为浮点数,要#include <sstream>        stringstream stream(temp);        stream >> value;        //将读入的浮点数添加到列表内        list.push_back(value);    }    N = list.size();    sumMax = MaxSubSum(list, 0, N - 1);    clock_t end_time = clock();    //static_cast是标准转换运算符,确保类型转换成功。CLOCKS_PER_SEC定义了每秒钟包含多少了时钟单元数    cout << "运行时间:" << static_cast<double>(end_time - start_time) / CLOCKS_PER_SEC << "s" << endl;//输出已流失的时间    cout << "答案为:" << sumMax << endl;//输出在10000个数中最大序列值    getchar();}

5、利用C++实现第四个算法

用时0.012s,答案为:163908。如果将加载数据不计入时间,则0.006s。
该算法理解起来稍微有点困难。设a[i]为和最大序列的起点,则如果a[i]是负的,那么它不可能代表最优序列的起点,类似的,任何负的子序列也不可能是最优子序列的前缀。
我们每次计算已经读入的数据,如果累计和大于上次得到的最大值,则将最大值更新,同时将终止索引更新为当次的索引值。如果累计和小于0,则将前面的数据全部清空,因为前面都是负子序列,同时将起始索引值更新为当次的索引值+1,即下一个数。
显然算法复杂度是线性的,O(N)。这是完美的联机算法:只对数据进行一次扫描,而且在任意时刻,算法都能对它读入的数据给出子序列问题的正确答案。

#include <iostream>#include<string>#include<fstream>#include<vector>#include <sstream>#include <time.h>using namespace std;int main(){    //读取txt的输入流,要#include<fstream>    string filename = "F:\\Data Structures and Algorithm Analysis in C\\Largest_subsequence.txt";    ifstream infile(filename.c_str());    string temp;    int i, N, start=0, end;    float value, sum, sumMax;    vector<float> list;    clock_t start_time = clock();//声明计时器对象并开始计时 ,要#include <time.h>    while (getline(infile, temp))    {        //利用字符串流将字符串转换为浮点数,要#include <sstream>        stringstream stream(temp);        stream >> value;        //将读入的浮点数添加到列表内        list.push_back(value);    }    N = list.size();    sum=sumMax = 0;    for (i = 0; i < N; i++)    {        sum += list[i];        //每次累加后就与sumMax比较        if (sum > sumMax)        {            sumMax = sum;            end=i;        }        else if (sum < 0)        {            sum = 0;            start=i+1;        }    }    clock_t end_time = clock();    //static_cast是标准转换运算符,确保类型转换成功。CLOCKS_PER_SEC定义了每秒钟包含多少了时钟单元数    cout << "运行时间:" << static_cast<double>(end_time - start_time) / CLOCKS_PER_SEC << "s" << endl;//输出已流失的时间    cout << "答案为:" << sumMax << endl;//输出在10000个数中最大序列值    cout << "起始位置:" << start << endl;    cout << "终止位置:" << end << endl;    getchar();}
原创粉丝点击