1.最大子段和问题的简单算法
用数组a[]存储给定的n个整数a1,a2,……,an。
#include<iostream> #include<cmath> #include<algorithm> #include<cstring> using namespace std; #define Max 50005 int a[Max]; int MaxSum(int n,int * a) { int sum = 0; for(int i = 0;i <= n;i ++) for(int j = i;j <= n;j ++) { int thissum = 0; for(int k = i;k <= j;k ++) thissum += a[k]; if(thissum > sum) { sum = thissum; //besti = i; //bestj = j; } } return sum; } int main() { int n; cin >> n; for(int i = 0;i < n;i ++) cin >> a[i]; cout << MaxSum(n,a) << endl; return 0; }
从这个算法的3个for循环可以看出,它所需要的计算时间是O(n^3)。事实上,如果注意到a[i]到a[j]的和= a[j]+ a[i]到a[j - 1]的和,则可将算法中的最后一个for循环省去,避免重复计算,从而使算法得以改进。改进后的代码为:
#include<iostream> #include<cmath> #include<algorithm> #include<cstring> using namespace std; #define Max 50005 int a[Max]; int MaxSum(int n,int * a) { int sum = 0; for(int i = 0;i <= n;i ++) { int thissum = 0; for(int j = i;j <= n;j ++) { thissum += a[j]; if(thissum > sum) sum = thissum; } } return sum; } int main() { int n; cin >> n; for(int i = 0;i < n;i ++) cin >> a[i]; cout << MaxSum(n,a) << endl; return 0; }
改进后的算法显然只需要O(n^2)的计算时间。上述改进是在算法设计技巧上的一个改进,能充分利用已经得到的结果,避免重复计算,节省了计算时间。
2.最大子段和问题的分治算法
针对最大子段和这个具体问题本身的结构,还可以从算法设计的策略上对上述O(n^2)计算时间算法加以更深刻的改进。从这个问题的解的结构可以看出,它适合于用分治法求解。
如果将所给的序列a[1:n]分为长度相等的两段a[1:n/2]和a[n/2 + 1:n],分别求出这两段的最大子段和,则a[1:n]的最大子段和有三种情形;
(1)a[1:n]的最大子段和与a[1:n/2]的最大子段和相同;
(2)a[1:n]的最大子段和与a[n/2 + 1:n]的最大子段和相同;
(3)a[1:n]的最大子段和为a[i]到a[j]的和,且1 <= i <= n/2, n/2 + 1 <= j <= n。
(1)和(2)这两种情形可递归求得。对于情形(3),容易看出,a[n/2]与a[n/2+1]在最优子序列中。因此,可以在a[1:n/2]中计算出s1 = 子段a[i]到a[n/2]的最大和,并在a[n/2+ 1:n]中计算出s2=子段a[n/2+1]到a[j]的最大和。则s1 + s2 即为出现情形(3)时的最优值。据此可设计出求最大子段和的分治算法如下:
#include<iostream> #include<cmath> #include<algorithm> #include<cstring> using namespace std; #define Max 50005 typedef long long LL; LL a[Max]; LL MaxSubSum(LL *a,int left,int right) { LL sum = 0; if(left == right) sum = a[left] > 0 ? a[left] : 0; else { LL center = (left + right) / 2; LL leftsum = MaxSubSum(a,left,center); LL rightsum = MaxSubSum(a,center + 1,right); LL s1 = 0; LL lefts = 0; for(LL i = center;i >= left;i --) { lefts += a[i]; if(lefts > s1) s1 = lefts; } LL s2 = 0; LL rights = 0; for(LL i = center + 1;i <= right;i ++) { rights += a[i]; if(rights > s2) s2 = rights; } sum = s1 + s2; if(sum < leftsum) sum = leftsum; if(sum < rightsum) sum = rightsum; } return sum; } LL MaxSum(int n,LL * a) { return MaxSubSum(a,1,n); } int main() { int n; cin >> n; for(int i = 1;i <= n;i ++) cin >> a[i]; cout << MaxSum(n,a) << endl; return 0; }
该算法所需的计算时间T(n)=O(nlogn).
3.最大子段和问题的动态规划算法
在对上述分治算法的分析中注意到,若记b[j]=a[1:j]的最大子段和,则所求的最大子段和为
a[1:n]的最大子段和=b[1 :n]的最大值
由b[j]的定义易知,当b[j - 1]>0时b[j] = b[j - 1] + a[ j ],否则b[j] = a[j]。由此可得计算b[j]的动态规划递归式
b[ j ] = max{ b[ j - 1] + a[ j ],a[ j ]}, 1 <= j <= n
据此,可设计出求最大子段和的动态规划算法如下。
#include<iostream> #include<cmath> #include<algorithm> #include<cstring> using namespace std; #define Max 50005 long long a[Max]; long long MaxSum(int n,long long * a) { long long sum = 0,b = 0; for(int i = 1;i <= n;i ++) { if(b > 0) b += a[i]; else b = a[i]; if(b > sum) sum = b; } return sum; } int main() { int n; cin >> n; for(int i = 1;i <= n;i ++) cin >> a[i]; cout << MaxSum(n,a) << endl; return 0; }
上述算法显然需要O(n)计算时间和O(n)空间。
例题:
第1行:整数序列的长度N(2 <= N <= 50000) 第2 - N + 1行:N个整数(-10^9 <= A[i] <= 10^9)
输出最大子段和。
6 -2 11 -4 13 -5 -2
20