问题描述
给定由n个整数(可能为负整数)组成的序列
a
1
,
a
2
,
…
,
a
n
a_1,a_2,…,a_n
a1,a2,…,an,求该序列形如
∑
k
=
i
j
a
k
\sum_{k=i}^j a_k
∑k=ijak的子段和的最大值。当所有整数均为负整数时定义其最大子段和为0。依此定义,所求的最优值为
m
a
x
{
0
,
m
a
x
∑
k
=
i
j
a
k
(
1
≤
i
≤
j
≤
n
)
}
max\{ 0,max \sum_{k=i}^j a_k \left(1\le i \le j \le n\right)\}
max{0,maxk=i∑jak(1≤i≤j≤n)}
例如当
(
a
1
,
a
2
,
a
3
,
a
4
,
a
5
,
a
6
)
=
(
−
2
,
11
,
−
4
,
13
,
−
5
,
−
2
)
\left(a_1,a_2,a_3,a_4,a_5,a_6\right)=\left(-2,11,-4,13,-5,-2\right)
(a1,a2,a3,a4,a5,a6)=(−2,11,−4,13,−5,−2)时,最大子段和为
∑
k
=
2
4
a
k
=
20
\sum_{k=2}^4 a_k=20
∑k=24ak=20
简单算法
最大子段和问题简单算法改进后的代码 。对所有的 ( i, j )对,顺序求和ai + … + aj,并比较出最大的和,标识该子段的首末位置。时间复杂度为 O ( n 2 ) O(n^2) O(n2)
#include <iostream>
using namespace std;
int MaxSum(int n,int *a,int &besti,int &bestj)
{
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;
besti = i;
bestj = j;
}
}
}
return sum;
}
int main()
{
int n = 6;
int a[] = {-2,11,-4,13,-5,-2};
int besti;
int bestj;
cout<<"最大子段和为:"<<MaxSum(n,a,besti,bestj)<<endl;
cout<<"起始位置:"<<besti<<endl<<"终止位置:"<<bestj;
return 0;
}
递归算法
针对最大子段和这个具体问题本身的结构,还可以从算法设计的策略上对上述
O
(
n
2
)
O(n^2)
O(n2)计算时间算法加以更深刻的改进。从这个问题的解的结构可以看出,它适合用分治法求解。
如果将所给的序列a[1:n]分为长度相等的两段a[1:n/2]和a[n/2+1:n],分别求出这两个段的最大子段和,则a[1:n]的最大子段和有三种情形:
- a[1:n]的最大子段和与a[1:n/2]的最大子段和相同;
- a[1:n]的最大子段和与a[n/2+1:n]的最大子段和相同;
- a[1:n]的最大子段和为 ∑ k = i j a k \sum_{k=i}^j a_k ∑k=ijak,且 1 ≤ i ≤ n / 2 , n / 2 + 1 ≤ j ≤ n 1\le i\le n/2,n/2+1\le j\le n 1≤i≤n/2,n/2+1≤j≤n
第1和第2种情形可递归求得。对于第三种情况,容易看出,a[n/2]与a[n/2+1]在最优子序列中。因此,可以在a[1:n/2]中计算出 s 1 = m a x ∑ k = i n / 2 a [ k ] ( 1 ≤ i ≤ n / 2 ) s_1=max \sum_{k=i}^{n/2} a[k] (1\le i\le n/2) s1=max∑k=in/2a[k](1≤i≤n/2),并在a[n/2+1:n]中计算出 s 2 = m a x ∑ k = n / 2 + 1 i a [ k ] ( n / 2 + 1 ≤ i ≤ n ) s_2=max \sum_{k=n/2+1}^i a[k] (n/2+1\le i\le n) s2=max∑k=n/2+1ia[k](n/2+1≤i≤n),则 s 1 + s 2 s_1+s_2 s1+s2即为出现情形3时的最优质。据此可设计出求最大子段和的分治算法如下,该算法的时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn):
#include<iostream>
using namespace std;
int MaxSubSum(int *a,int left,int right)
{
int sum = 0;
if(left == right)
sum = a[left]>0?a[left]:0;
else
{
int center = (left + right)/2;
int leftsum = MaxSubSum(a,left,center);
int rightsum = MaxSubSum(a,center+1,right);
int s1 = 0;
int lefts = 0;
for(int i = center;i >= left;i--)
{
lefts += a[i];
if(lefts > s1)
s1 = lefts;
}
int s2 = 0;
int rights = 0;
for(int 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;
}
int MaxSum(int n,int *a)
{
return MaxSubSum(a,0,n-1);
}
int main()
{
int n = 6;
int a[] = {-2,11,-4,13,-5,-2};
cout<<"最大子段和为:"<<MaxSum(n,a)<<endl;
return 0;
}
动态规划算法
对上述分治算法的分析中注意到,若记
b
[
i
]
=
m
a
x
{
∑
k
=
i
j
a
[
k
]
}
(
1
≤
i
≤
j
,
1
≤
j
≤
n
)
b[i]=max\{\sum_{k=i}^j a[k]\} \left(1\le i\le j,1\le j\le n\right)
b[i]=max{k=i∑ja[k]}(1≤i≤j,1≤j≤n),则所求的最大子段和为
m
a
x
1
≤
i
≤
j
≤
n
∑
k
=
i
j
a
[
k
]
=
m
a
x
1
≤
j
≤
n
m
a
x
1
≤
i
≤
j
∑
k
=
i
j
a
[
k
]
=
m
a
x
1
≤
j
≤
n
b
[
j
]
max_{1\le i\le j\le n} \sum_{k=i}^j a[k]=max_{1\le j\le n} max_{1\le i\le j} \sum_{k=i}^j a[k]=max_{1\le j\le n} b[j]
max1≤i≤j≤nk=i∑ja[k]=max1≤j≤nmax1≤i≤jk=i∑ja[k]=max1≤j≤nb[j]
由b[j]的定义易知,当b[j-1]>0时b[j]=b[j-1]+a[j],否则b[j]=a[j]。由此可得计算b[j]的动态规划递归式为
b
[
j
]
=
m
a
x
{
b
[
j
−
1
]
+
a
[
j
]
,
a
[
j
]
}
,
1
≤
j
≤
n
b[j]=max\{b[j-1]+a[j],a[j]\},1\le j\le n
b[j]=max{b[j−1]+a[j],a[j]},1≤j≤n
因此,可设计出求最大子段和的动态规划算法如下:
#include<iostream>
using namespace std;
int MaxSum(int n,int *a,int &besti,int &bestj)
{
//b表示的是以元素a[i]为结尾的最大子段和
int sum = -1, b = 0,flag;
for(int i = 0;i < n;i++)
{
if(b > 0) b += a[i];
else
{
b = a[i];
flag=i;
}
if(b > sum)
{
sum = b;
besti=flag;
bestj=i;
}
}
return sum;
}
int main()
{
int n = 6;
int a[] = {-2,11,-4,13,-5,-2};
int besti=0,bestj=n-1;
int sum=MaxSum(n,a,besti,bestj);
if(sum==-1) sum=0;
cout<<"最大子段和为:"<<sum<<endl;
cout<<"初始位置:"<<besti<<"末尾位置:"<<bestj<<endl;
return 0;
}
上述算法显然需要时间和空间复杂度均为 O ( n ) O(n) O(n)