来源:http://blog.csdn.net/luxiaoxun/article/details/7438315
问题:
给定一整数序列A1, A2,... An (可能有负数),求A1~An的一个子序列Ai~Aj,使得Ai到Aj的和最大
例如:整数序列-2, 11, -4, 13, -5, 2, -5, -3, 12, -9的最大子序列的和为21。对于这个问题,最简单也是最容易想到的那就是穷举所有子序列的方法。利用三重循环,依次求出所有子序列的和然后取最大的那个。当然算法复杂度会达到O(n^3)。
1 int maxSubSum1(int a[],int size ) 2 { 3 int maxSum = 0; 4 for ( int i = 0; i < size; i++ ) 5 for ( int j = 1; j < size; j++ ) 6 { 7 int thisSum = 0; 8 for ( int k = i; k <= j; k++ ) 9 thisSum += a[k]; 10 if ( thisSum > maxSum ) 11 maxSum = thisSum; 12 } 13 return maxSum; 14 }
这个算法很简单,i表示子序列起始下标,j表示子序列结束下标,遍历子序列的开头和结束下标,计算子序列的和,然后判断最大子序列。很明显的看出算法复杂度是O( pow( n, 3 ) )
显然这种方法不是最优的,下面给出一个算法复杂度为O(n)的线性算法实现,算法的来源于Programming Pearls一书。在给出线性算法之前,先来看一个对穷举算法进行优化的算法,它的算法复杂度为O(n^2)。其实这个算法只是对对穷举算法稍微做了一些修改:其实子序列的和我们并不需要每次都重新计算一遍。假设Sum(i, j)是A[i] ... A[j]的和,那么Sum(i, j+1) = Sum(i, j) + A[j+1]。利用这一个递推,我们就可以得到下面这个算法:
1 int max_sub(int a[],int size) 2 { 3 int i,j,v; 4 int max=a[0]; 5 for(i=0;i<size;i++) 6 { 7 v=0;//开始以下一个i开头时要对上一个i开头的累加和清零。 8 for(j=i;j<size;j++) 9 { 10 v=v+a[j]; //Sum(i, j+1) = Sum(i, j) + A[j+1] 11 if(v>max) max=v; 12 } 13 } 14 return max; 15 }
那怎样才能达到线性复杂度呢?这里运用动态规划的思想。先看一下源代码实现:
(PS:嗯这个动态规划的代码还真没看懂……)
int max_sub2(int a[], int size) { int i,max=0,temp_sum=0; for(i=0;i<size;i++) { temp_sum+=a[i]; if(temp_sum>max) max=temp_sum; else if(temp_sum<0) temp_sum=0; } return max; }
在这一遍扫描数组当中,从左到右记录当前子序列的和temp_sum,若这个和不断增加,那么最大子序列的和max也不断增加(不断更新max)。如果往前扫描中遇到负数,那么当前子序列的和将会减小。此时temp_sum 将会小于max,当然max也就不更新。如果temp_sum降到0时,说明前面已经扫描的那一段就可以抛弃了,这时将temp_sum置为0。然后,temp_sum将从后面开始将这个子段进行分析,若有比当前max大的子段,继续更新max。这样一趟扫描结果也就出来了。
分治法:最大子序列和可能出现在三个地方:整个出现在输入数据的左半部分,整个出现在输入数据的右半部分,或者跨越输入数据的中部从而占据左右两个半部分。
(PS:原文的这段分治法的代码看懂了个大概,后面附上另外两段分治法的代码吧。
另外,不管怎写,分治法解决这个题目,时间复杂度都是O(nlogn)级别的。)
/** * Recursive maximum contiguous subsequence sum algorithm. * Finds maximum sum in subarray spanning a[left..right]. * Does not attempt to maintain actual best sequence. */ int maxSumRec( const vector<int> & a, int left, int right ) { if( left == right ) // Base case if( a[ left ] > 0 ) return a[ left ]; else return 0; int center = ( left + right ) / 2; int maxLeftSum = maxSumRec( a, left, center ); int maxRightSum = maxSumRec( a, center + 1, right ); int maxLeftBorderSum = 0, leftBorderSum = 0; for( int i = center; i >= left; i-- ) { leftBorderSum += a[ i ]; if( leftBorderSum > maxLeftBorderSum ) maxLeftBorderSum = leftBorderSum; } int maxRightBorderSum = 0, rightBorderSum = 0; for( int j = center + 1; j <= right; j++ ) { rightBorderSum += a[ j ]; if( rightBorderSum > maxRightBorderSum ) maxRightBorderSum = rightBorderSum; } return max3( maxLeftSum, maxRightSum, maxLeftBorderSum + maxRightBorderSum ); } /** * Driver for divide-and-conquer maximum contiguous * subsequence sum algorithm. */ int maxSubSum3( const vector<int> & a ) { return maxSumRec( a, 0, a.size( ) - 1 ); }
下面的代码来自http://www.2cto.com/kf/201311/255297.html
1 #include <stdio.h> 2 int Max2(int a, int b) 3 { 4 return a>b ? a:b; 5 } 6 int Max3(int a, int b, int c) 7 { 8 int max = a; 9 if(max < b) max = b; 10 if(max < c) max = c; 11 return max; 12 } 13 int MaxSubSum(int* a, int fI, int lI) 14 { 15 int i = 0; 16 int mI = (fI+lI) >> 1; 17 int lMax = 0, rMax = 0, lMaxBorder = 0, rMaxBorder = 0, lSumBorder = 0, rSumBorder = 0, max = 0; 18 //so how about 19 if(fI == lI) 20 return *(a+fI); 21 22 lMax = MaxSubSum(a, fI, mI); 23 rMax = MaxSubSum(a, mI+1, lI); 24 for(i=mI;i>=fI;i--) 25 { 26 lSumBorder += *(a+i); 27 if(lMaxBorder < lSumBorder) 28 lMaxBorder = lSumBorder; 29 } 30 for(i=mI+1;i<=lI;i++) 31 { 32 rSumBorder += *(a+i); 33 if(rMaxBorder < rSumBorder) 34 rMaxBorder = rSumBorder; 35 } 36 max = Max3(lMax, rMax, lMaxBorder+rMaxBorder); 37 return max; 38 } 39 40 int main() 41 { 42 int a[9] = {4,-3,5,-2,-1,2,6,-2,3}; 43 int L = sizeof(a) / sizeof(int); 44 int max = MaxSubSum(a, 0, L-1); 45 printf("%d \n", max); 46 return 0; 47 48 }
下面是参考刘汝佳的《书法竞赛入门经典》page141 的代码:
1 int maxSum(int *A,int x,int y)//返回数组A在左闭右开区间[x,y)中的最大连续和 2 { 3 int i,m,v,Lmax,Rmax,L,R; 4 int ans; 5 if(y-x==1) return A[x]; //假如只有一个元素,直接返回 6 m=x+(y-x)/2; //分治第一步:划分成[x,m)和[m,y) 7 Lmax=maxSum(A,x,m); //分治第二步:递归求解 8 Rmax=maxSum(A,m,y); 9 10 v=0; //分治第三步:合并(1)——寻找从分界点向左的最大连续和L 11 L=A[m-1]; 12 for(i=m-1;i>=x;i--) 13 { 14 v+=A[i]; 15 if(v>L) L=v; 16 } 17 18 v=0; //分治第三步:合并(2)——寻找从分界点向右的最大连续和R 19 R=A[m]; 20 for(i=m;i<y;i++) 21 { 22 v=v+A[i]; 23 if(v>R) R=V; 24 } 25 //把子问题的解与L+R 比较从而得到原问题的解。 26 ans=(Lmax>Rmax ? Lmax : Rmax); 27 return ans>(L+R)? ans:(L=R); 28 }
刘大师的代码就是简洁又给力呵呵