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