求最大连续子向量和-编程珠玑
问题描述:
输入:具有n个浮点数的向量x;
输出:向量x的任何连续子向量中的最大和。
如一维数组元素为31,-41,59,26,-53,58,97,-93,-23,84,则程序的输出为x[2...6]的总和187.
补充定义:若输入全部都是负数,总和最大的子向量是空向量,总和为0.
解法一:
即求x[0..n-1]中x[i...j]的之和的最大值,使用最普通的方法计算出任何x[i,j]之间各个和的最大值,然后取最大值。
int maxSum1(int *a,int n){
int i,j,max=0,sum;
for(i=0;i<n;i++){
sum=0;
for(j=i;j<n;j++){
sum +=a[j];
if(sum>max)
max=sum;
}
}
return max;
}
显然,这个算法的时间复杂度是O(n^2)
解法二:
另一个平法算法是通过访问在外循环执行之前就已经构建的数据结构的方式在内循环中计算总和。
cumarr数组中的第i个元素包含x[0..i]中各个数的累加和,所以x[i..j]中各个数的总和可以通过
计算cumarr[j]-cumarr[i-1]得到。
则本题转换为求cumarr[j]-cumarr[i-1]的最大值
int maxSum2(int *a,int n){
int i,j,sumOut=0,cumarr[10];
for(i=0;i<10;i++){
sumOut +=a[i];
cumarr[i]=sumOut;
}
int sumIn=0,max=0;
for(i=0;i<n;i++){
for(j=i+1;j<n;j++){
sumIn=cumarr[j]-cumarr[i];
if(sumIn>max)
max=sumIn;
}
}
return max;
}
这个算法的时间复杂度仍然是O(n^2)
解法三:
可以利用分治法来解决这个问题:
将原始向量x分为两个大小近似相等的子向量a和b,然后递归地找出a,b中元素总和最大的子向量ma和mb。
还有一种情况是最大子序列和在a和b之间,这个跨边界的最大子向量记为mc,通过观察可以发现:mc在a中
的部分是a中包含右边界的最大子向量,同时mc在b中的部分是b中包含左边界的最大子向量。
int maxSum3(int *a,int l,int r){
if(l>r){
return 0;
}
if(l==r)
return max(a[l],0);
int m=(l+r)/2;
int lmax=0,sum=0;
for(int i=m;i>=0;i--){
sum +=a[i];
if(sum>lmax)
lmax=sum;
}
int rmax=0;
sum=0;
for(int j=m+1;j<=r;j++){
sum +=a[j];
if(sum>rmax)
rmax=sum;
}
return max(lmax+rmax,max(maxSum3(a,l,m),maxSum3(a,m+1,r)));
}
max(int,int)函数定义:
int max(int a,int b){
return (a>b)?a:b;
}
该算法在每层递归中都执行了O(n)次操作,而总共有O(logn)层递归。故算法的时间复杂度为O(n*logn)
解法四:
这个算法称为扫描算法,时间复杂度为O(n)。
该算法的核心是定义了一个max_end变量,先看程序:
int maxSum4(int *a,int n){
int max_end=0,max_sofar=0;
for(int i=0;i<n;i++){
max_end=max(a[i]+max_end,0);
max_sofar=max(max_sofar,max_end);
}
return max_sofar;
}
可以看出,程序是相当简单的,max_end 期望尝试向右累加以获得最大子向量
如果X全是正数,max_end 向右累加持续增大;
如果X全是负数,max_end 将一直为0;
如果X有正有负,假设max_end 现在为正,max_end 向右累加,当加到小于0时,有一点可以肯定X(i)<0,
发现没有必要再向右加了,把当前max_end 置0,继续扫描。
可见,这个方法扫描出一座座山峰(不能合并的局部最大和子向量),最后看看哪个子向量的海拔高(和最大)。
max_end中的0指示了山峰的山脚(一座山的开始和结束)。这个算法最核心的地方是何时将max_end 置0。