连续子数组最大和问题编程实现
2015-05-14 20:23
621 查看
引自 http://www.cnblogs.com/en-heng/p/3970231.html
连续子数组最大和问题
问题描述
输入一个整形数组,求数组中连续的子数组使其和最大。比如,数组x应该返回 x[2..6]的和187.
问题解决
我们很自然地能想到穷举的办法,穷举所有的子数组的之和,找出最大值。
穷举法
i, j的for循环表示x[i..j],k的for循环用来计算x[i..j]之和。maxsofar = 0 for i = [0, n) for j = [i, n) sum = 0 for k = [i, j] sum += x[k] /* sum is sum of x[i..j] */ maxsofar = max(maxsofar, sum)
有三层循环,穷举法的时间复杂度为O(n3)
对穷举法的改进1
我们注意到x[i..j]之和 = x[i..j-1]之和 + x[j],因此在j的for循环中,可直接求出sum。
maxsofar = 0 for i = [0, n) sum = 0 for j = [i, n) sum += x[j] /* sum is sum of x[i..j] */ maxsofar = max(maxsofar, sum)
显然,改进之后的时间复杂度变为O(n2)。
对穷举法的改进2
在计算fibonacci数时,应该还有印象:用一个累加数组(cumulative array)记录前面n-1次之和,计算当前时只需加上n即可。同样地,我们用累加数组cumarr记录:cumarr[i] = x[0] + . . . +x[i],那么
x [i.. j]之和 = cumarr[j] -cumarr[i - 1]
cumarr[-1] = 0 for i = [0, n) cumarr[i] = cumarr[i-1] + x[i] maxsofar = 0 for i = [0, n) for j = [i, n) sum = cumarr[j] - cumarr[i-1] /* sum is sum of x[i..j] */ maxsofar = max(maxsofar, sum)
时间复杂度依然为O(n2)。
分治法
所谓分治法,是指将一个问题分解为两个子问题,然后分而解决之。具体步骤如下:先将数组分为两个等长的子数组a, b;
分别求出两个数组a,b的连续子数组之和;
还有一种情况比较容易忽略:有可能最大和的子数组跨越两个数组;
最后比较ma, mb, mc,取最大即可。
在计算mc时,注意:mc必定包含总区间的中间元素,因此求mc等价于
从中间元素开始往左累加的最大值 + 从中间元素开始往右累加的最大值。
float maxsum3(l, u) if (l > u) /* zero elements */ return 0 if (l == u) /* one element */ return max(0, x[l]) m = (l + u) / 2 /* find max crossing to left */ lmax = sum = 0 for (i = m; i >= l; i--) sum += x[i] lmax = max(lmax, sum) /* find max crossing to right */ rmax = sum = 0 for i = (m, u] sum += x[i] rmax = max(rmax, sum) return max(lmax+rmax, maxsum3(l, m), maxsum3(m+1, u));
容易证明,时间复杂度为O(n∗log n)。
Kadane算法
Kadane算法又被称为扫描法,该算法用到了一个启发式规则:如果前面一段连续子数组的和小于0,那么就丢弃它。其实也蛮好理解的,举个简单例子,比如:数组-1, 2, 3,-1为负数,为了使得子数组之和最大,显然不应当把-1计入进内。
max_ending_here记录前面一段连续子数组之和。
Initialize: max_so_far = 0 max_ending_here = 0 Loop for each element of the array (a) max_ending_here = max_ending_here + x[i] (b) if(max_ending_here < 0) max_ending_here = 0 (c) if(max_so_far < max_ending_here) max_so_far = max_ending_here return max_so_far
只遍历了一遍数组,因此时间复杂度为O(n)。
编程实现
#include <iostream> #include <fstream> #include <vector> using namespace std; vector<int> s; int maxSoFar; int maxSum3(int l, int u, vector<int> x);//分治法函数声明 int main() { ifstream in("input.txt"); for(int i;in>>i;) s.push_back(i); int sum; maxSoFar = 0; //穷举法方法一 for (int i = 0;i<s.size()-1;i++){ for (int j = i;j<s.size()-1;j++){ sum = 0; for(int k=i;k<j;k++) sum += s[k]; /* sum is sum of x[i..j] */ maxSoFar = max(maxSoFar, sum); } } cout << maxSoFar<< endl; //穷举法之改进1:在j的for循环中,可直接求出sum。 for (int i = 0;i<s.size()-1;i++){ sum = 0; for (int j = i;j<s.size()-1;j++){ sum += s[j]; maxSoFar = max(maxSoFar, sum); } } cout << maxSoFar<< endl; //穷举法之改进2:在计算fibonacci数时,我们用一个累加数组(cumulative array)记录前面n-1次之和,计算当前时只需加上n即可。 //同样地,我们用累加数组cumArr记录 vector<int> cumArr(s.size()); for (int i=0;i<s.size();i++) cumArr[i] = cumArr[i-1] + s[i]; maxSoFar = 0; for (int i= 0;i<s.size();i++) for(int j = i;j<s.size();j++){ sum = cumArr[j] - cumArr[i-1]; maxSoFar = max(maxSoFar, sum); } cout << maxSoFar<< endl; //================================================== // 分治法:可以看成递归的把每个元素假设成最大子数组中元素,并由它向左或向右查找 maxSoFar=maxSum3(0,s.size()-1,s); cout << maxSoFar<< endl; //================================================== //Kadane算法,即扫描法,该算法用到了一个启发式规则:如果前面一段连续子数组的和小于0,那么就丢弃它。 maxSoFar = 0; int maxEndingHere = 0; for(int i=0;i<s.size();i++){ maxEndingHere = maxEndingHere+ s[i]; if(maxEndingHere < 0)//如果当前子序列小于0了,又重新开始扫描。 maxEndingHere = 0; if(maxSoFar< maxEndingHere) maxSoFar= maxEndingHere; } cout << maxSoFar<< endl; return 0; } //=============================================== // 分治法 int maxSum3(int l, int r, vector<int> x){ if (l > r) /* zero elements */ return 0; if (l == r) /* one element */ return max(0, x[l]); int m = (l + r) / 2; //从中间向两边寻找,能够实现分治求解,并同时查找横跨两个子问题的最大子数组和 /* find max crossing to left */ int lmax = 0,sum = 0; for (int i = m; i >= l; i--){//保证可以从中间开始,找横跨两个子问题的最大子数组和。 sum += x[i]; lmax = max(lmax, sum); } /* find max crossing to right */ int rmax = 0; sum = 0; for (int i = m+1;i<=r;i++){ sum += x[i]; rmax = max(rmax, sum); } return max(lmax+rmax,//横跨两个子问题的子数组 max(maxSum3(l,m,x),maxSum3(m+1,r,x))); }
参考资料
[1] Jon Bentley, Programming Pearls.[2] GeeksforGeeks, Largest Sum Contiguous Subarray.
相关文章推荐
- 最大连续邮资问题的JAVA实现
- 编程实现:从字符中获取连续数组序列,如字符串"a1dl2iad9j3la5kudp7u9pn4blj8ap5u3e6ml9a"中可以得到的连续数字序列为1234556.【注】:只考虑一位,因此最长的数
- 数组问题之一维最大字段和问题<Java实现>
- 程序员编程艺术:第七章、求连续子数组的最大和
- 求数组连续最大和问题
- 程序员编程艺术:第七章、求连续子数组的最大和
- 程序员编程艺术:第七章、求连续子数组的最大和
- 程序员编程艺术:第七章、求连续子数组的最大和
- 程序员编程艺术:第七章、求连续子数组的最大和
- 程序员编程艺术:第七章、求连续子数组的最大和
- 程序员编程艺术:第七章、求连续子数组的最大和
- 程序员编程艺术:第七章、求连续子数组的最大和
- 一维数组及子数组最大和问题Java实现
- 算法导论C语言实现: 分治策略 -- 最大子数组问题
- 数组中连续最大和实现
- 求数组的子数组之和的最大值问题的实现
- 程序员编程艺术:第七章、求连续子数组的最大和
- 程序员编程艺术:第七章、求连续子数组的最大和
- 用C语言如何编程实现从三个数组中各抽取几个数进行组合的问题?
- 编程之美--求数组的子数组之和的最大值--扩展问题