以下对应英文原版第68页
本节开始提出了一个问题,关于低价购入高价卖出,分别在哪一天买卖可以获得最大利润,问题经过转化,用A[n]表示第n天的价格减去第n+1天的价格,问题转化为求最大连续和。
方法是“分治法”,把一组连续数字从中间分成两半,那么最大连续和有三种可能:
1.最大连续的和的一组“子数组”存在于“母数组”的前半段。
2.存在于后半段。
3.跨越前后半段。
请细细体会。
对于一串数组,我分别要求出它的前后半段的最大子数组,以及第三种情况的最大子数组,然后进行比较,选出最大的即为要求的结果。那么怎么求其前后半段的最大子数组呢?使用递归。下面给出代码。
先给出测试代码
#include <iostream>
using namespace std;
const int N=1000000;
struct subarray//定义了一个结构体,各参数功能如下
{
int sum;//连续子数组的和
int left;//子数组最左边元素的下标
int right;//同上
};
int main()
{
int A[]={0,13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7};//从A[1]开始
subarray a=Find_maximum_subarray(A,1,16);
cout<<a.left<<' '<<a.right<<' '<<a.sum;
}
给出求第三种情况的代码
伪代码在原书71页
subarray Find_max_crossing_subarray(int* A,int low,int mid,int high)
{
int left_sum=-N,right_sum=-N,max_left,max_right;
int sum=0;
for(int i=mid;i>=low;i--)
{
sum=sum+A[i];
if(sum>left_sum)
{
left_sum=sum;
max_left=i;
}
}
sum=0;
for(int j=mid+1;j<=high;j++)
{
sum=sum+A[j];
if(sum>right_sum)
{
right_sum=sum;
max_right=j;
}
}
subarray a;
a.sum=left_sum+right_sum;
a.left=max_left;
a.right=max_right;
return a;
}
再给出分治的代码
伪代码在原书第72页
subarray Find_maximum_subarray(int *A,int low,int high)
{
subarray a,b,c;
if(high==low)
{
a.left=low;
a.right=high;
a.sum=A[low];
return a;
}
int mid=(low+high)/2;
a=Find_maximum_subarray(A,low,mid);
b=Find_maximum_subarray(A,mid+1,high);
c=Find_max_crossing_subarray(A,low,mid,high);
if(a.sum>=b.sum&&a.sum>=c.sum)
return a;
else if(b.sum>=a.sum&&b.sum>=c.sum)
return b;
else
return c;
}
递归过程的图类似于二叉树,理解时最好借助于图。
下面给出简化代码,把以上两个函数合二为一
参考《算法竞赛入门经典(第二版)》第223页
下面代码和以上最大的不同是范围的选用,函数传入的是左闭右开区间[x,y),代码有些改动,请细细体会。
int maxsum(int* A,int x,int y)
{
if(y-x==1)
return A[x];
int m=x+(y-x)/2;
int maxs=max(maxsum(A,x,m),maxsum(A,m,y));
int Lsum,Rsum,v;
Lsum=A[m-1],v=0;
for(int i=m-1;i>=x;i--)
Lsum=max(Lsum,v+=A[i]);
Rsum=A[m];v=0;
for(int i=m;i<y;i++)
Rsum=max(Rsum,v+=A[i]);
return max(maxs,Lsum+Rsum);
}
利用分治法计算最大连续的的时间复杂度是O(nlogn),除此之外还有一种O(n)的算法,笔者在这里依然参考了上述资料。
下面给出代码
int maxsum(int* A,int length)
{
int S[17];
S[0]=0;
for(int i=1;i<=16;i++)
{
//S[i]表示前i项和
S[i]=S[i-1]+A[i];
}
//best表示最大连续和,mins表示当前S[i]前面最小的S。
int mins=S[1],best=S[1];
for(int i=2;i<=length;i++)
{
//计算best和mins的顺序不能换,否则mins=S[i],而best=S[i]-mins,相当于一个A[i]也不选。
best=max(best,S[i]-mins);
mins=min(mins,S[i]);
}
return best;
}