[C++]四种方式求解最大子序列求和问题

来源:互联网 发布:上海希而科 知乎 编辑:程序博客网 时间:2024/06/06 03:26

问题

给定整数: A1,A2,,An
jk=iAk 的最大值(为方便起见,如果所有的整数均为负数,则最大子序列和为0)

例如

对于输入:-2,11,-4,13,-5,-2,答案为20,即从A2A4

分析

这个问题之所以有意思,是因为存在很多求解它的算法。

解法一:穷举遍历

老老实实的穷举出所有的可能,代码如下:

1234567891011121314151617181920212223242526272829
//计算并返回所最大子序列的和:穷举遍历int maxSubSum1(const vector<int> & a){//用来存储最大子序列的和int maxSum = 0;//i标记子序列的头for (int i = 0; i < a.size(); i++){//j标记子序列的尾for (int j = i; j < a.size(); j++){//用来存储当前头尾计算的求和结果int thisSum = 0;//将子序列的值依次加入求和结果for (int k = i; k <= j; k++){thisSum += a[k];}//存储两者的最大值if(thisSum > maxSum)maxSum = thisSum;}}return maxSum;}

这是大多数人都会想到的方法:把所有的可能都列举出来,然后再把子序列的所有值都加起来求和。
简单粗暴的解决了问题,而且还很好理解。
这种算法的时间复杂度为O(N3)

解法二:穷举优化

其实第三个for循环完全没有必要,在第二层for循环的时候就计算求得的和并且继续带入下一轮的for循环即可:

123456789101112131415161718192021222324252627
//计算并返回所最大子序列的和:穷举优化int maxSubSum2(const vector<int> & a){//用来存储最大子序列的和int maxSum = 0;//i标记子序列的头for (int i = 0; i < a.size(); i++){//用来存储当前头尾计算的求和结果int thisSum = 0;//j标记子序列的尾for (int j = i; j < a.size(); j++){//将子序列的值加入上次求和结果thisSum += a[j];//存储两者的最大值if(thisSum > maxSum)maxSum = thisSum;}}return maxSum;}

这种算法的时间复杂度为O(N2)

解法三:分而治之

分而治之,顾名思义分为两个部分

  • 分:把大问题分成大致相等的两个子问题,然后递归的进行求解。
  • 治:把两个子问题的解合并到一起并再做少量的附加工作。

在最大子序列和的问题里,最大子序列的和可能出现在三个地方:

  • 整个出现在输入数据的左半部
  • 整个输入数据的右半部
  • 横跨左右两个部分

对于前两种可以递归求解,对于第三种,可以把左右两个部分的和分别求出,然后加在一起。
具体的代码如下:

123456789101112131415161718192021222324252627282930313233343536373839404142434445
//计算并返回所最大子序列的和:分而治之int maxSubSum3(const vector<int> & a,int left,int right){//基础情况:单个元素。直接返回这个数值或者0if(left == right){return a[left];}//获取中点int center = (left + right) / 2;/* 整个出现在输入数据的左半部的最大子序列求和 */int leftMaxSum = maxSubSum3(a,left,center);/* 整个出现在输入数据的右半部的最大子序列求和 */int rightMaxSum = maxSubSum3(a,center+1,right);//计算左右两个子序列求和结果的最大值int lrMaxSum = max(leftMaxSum,rightMaxSum);/* 横跨左右两个部分的最大子序列求和 *///从center向左处理左半边int maxLeftSum = 0;int leftSum = 0;for (int i = center; i >= left; i--){leftSum += a[i];maxLeftSum = max(maxLeftSum,leftSum);}//从center向右处理右半边int maxRightSum = 0;int rightSum = 0;for (int j = center+1; j <= right; j++){rightSum += a[j];maxRightSum = max(maxRightSum,rightSum);}//返回求和和前面算出结果的最大值return max( lrMaxSum, maxLeftSum+maxRightSum);}

这种算法的时间复杂度为O(NlogN)

解法四:联机算法

先来解释一下联机算法的概念:

联机算法:在任意时刻,算法对要操作的数据只读入(扫描)一次,一旦被读入并处理,它就不需要在被记忆了。而在此处理过程中算法能对它已经读入的数据立即给出相应子序列问题的正确答案。具有这种特性的算法叫做联机算法(on-line algorithm)。

对于这个问题,代码如下:

1234567891011121314151617181920212223
//计算并返回所最大子序列的和:最优算法int maxSubSum4(const vector<int> & a){//最终结果int maxSum = 0;//当前求和int nowSum = 0;//遍历序列的所有元素for (int j = 0; j < a.size(); j++){//将当前元素添加到结果中nowSum += a[j];//如果大于最大值,则存储为新的最大值if(nowSum > maxSum)maxSum = nowSum;else if(nowSum < 0)nowSum = 0;}return maxSum;}

这种算法的时间复杂度为O(N)

总结

下表是统计的四种算法的运算结果:

输入大小O(N3)O(N2)O(NlogN)O(N)N=100.0000090.0000040.0000060.000003N=1000.0025800.0001090.0000450.000006N=10002.2810130.0102030.0004850.000031N=10000NA1.23290.0057120.000317N=100000NA1350.0646180.003206

从表中可以看出,在数据量很小的时候(在它小时候=.=),算法在瞬间就完成了,差距也不是很明显。
但是随着输入量的增大,一些算法的弊端逐渐显现出来,效率低的甚至在有限的时间里算不出结果来。
对于很多高效的算法来说,读取数据往往是解决问题的瓶颈,

1 0
原创粉丝点击