您的位置:首页 > 其它

最大子数组和(DP和递归解法)与最大子矩阵和

2015-03-19 15:43 162 查看
1. 最大子数组和

参考资料:/article/1386223.html
http://blog.csdn.net/beiyeqingteng/article/details/7056687
题目:输入一个整型数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。求所有子数组的和的最大值。要求时间复杂度为O(n)。

例如:当输入的数组为1, -2, 3, 10, -4, 7, 2, -5,和最大的子数组为3, 10, -4, 7,2,因此输出为该子数组的和18。

当我们加上一个正数时,和会增加;当我们加上一个负数时,和会减少。如果当前得到的和是个负数,那么这个和在接下来的累加中应该抛弃并重新定位赋值,不然的话这个负数将会减少接下来的和。

最大子数组和实际上是DP问题。点击打开链接



感性理解:

设b[i]为以第i个元素结尾(这是为了好推算递推式)且和最大的连续子数组。假设对于元素i,所有以它前面的元素结尾的子数组的长度都已经求得,那么要求以第i个元素结尾且和最大的连续子数组,实际上要么是以第i-1个元素结尾且和最大的连续子数组加上这个元素,要么是只包含第i个元素,即b[i] = max(b[i-1] + a[i], a[i])。判断b[i-1]
+ a[i]是否大于a[i]实际上等价于判断b[i-1]是否大于0。

注意:有时候,虽然b[i] = b[i-1] + a[i],但可能b[i] < b[i-1] ,所以这和最大上升子序列不同,要求得最大和子序列,必须进行max{b[i], 0=<i<n}的运算。

换句话说,b递减( 随着i的增大 )是可以容忍的,因为可以通过max来选择大者。但是一旦b小于0就无法容忍了,必须另起炉灶(b=a[i]),因为此时是不可跨越的,无法和a[i-1]连成连续数组,原因正是1)

1)当以第(i-1)个数字为结尾的子数组中所有数字的和b(i-1)小于0时,如果把这个负数和第i个数相加,得到的结果反而比第i个数本身还要小,所以这种情况下最大子数组和是第i个数本身。

2)如果以第(i-1)个数字为结尾的子数组中所有数字的和b(i-1)大于0,与第i个数累加就得到了以第i个数结尾的子数组中所有数字的和。

由于每次运算只需要前一次的结果,因此并不需要像普通的动态规划那样保留之前所有的计算结果,只需要保留上一次的即可,因此算法的时间和空间复杂度都很小。

int MaxSub_array( int n ,int a[] )
{
int i;
int max,b;
max=b=a[0];   //赋的值必须是首项值,不能赋0,要不然如果数组全为负数的话,则求的最终结果就是0(max)了
for(i=1;i<n;i++)
{
if(b<=0)
{
sum=a[i];
}
else
{
b+=a[i];
}
if(b>max)
{
max=b;
}
}
return max;
}


2.最大子矩阵和
第0行时,有0,0~1,0~2,...,0~m-1这m种情况(这些情况都是一一叠加的),每种情况都可以合并(压缩)成一维数组通过最大子数组和来求;
同样地,第1行时,就有1,1~2,...,1~m-1这m-1种情况,每种情况都可以合并(压缩)成一维数组通过最大子数组和来求;
...
第m-1行时,只有m-1这一种情况。

这实际上在求,第0行,第0行和第1行压缩成的一维数组,第0、1、2行压缩成的一维数组...第0、1、2、...、m-1行压缩成的一维数组;第1行,第1行和第2行压缩成的一维数组,第1、2、3行压缩成的一维数组...第1、2、3、...、m-1行压缩成的一维数组;......;第m-1行 。这所有的情况压缩成的一位数组的子数组和的最大值。

int Max_SubMatrix(int *a[], int m, int n)
{
int sum=-10000;
for(int i=0;i<m;i++){
int b
={0};
for(int j=i;j<m;j++){
for(int k=0;k<n;k++){
b[k]+=a[j][k];
}
int max=Max_Subarray(n,b)
if(max>sum){
sum=max;
}
}
}
return sum;
}


3. 最大子数组和的递归解法
出自weiss的书P20
最大子序列和可能在三处出现。其一,完全在数据的左半部分,其二,完全在数据的右半部分,其三,跨越左右——可以通过求出前半部分的最大和(包含前半部分的最后一个元素)以及后半部分的最大和(包含后半部分的第一个元素)而相加得到。 最大子序列和是这三者的最大者。
另外值得注意的是,递归过程调用的一般形式是传递(整个)数组以及左边界和右边界,它们界定了数组要被处理的部分,也就是说,数组是作为完整的参数传递给函数的,但是真正需要处理的部分由边界值确定。
代码如下:

int Max_Subarray(int *a, int left, int right)
{
if (left==right){
return a[left];
}

int mid=(right+left)/2;
int Left_Max = Max_Subarray(a, left, mid);
int Right_Max= Max_Subarray(a, mid+1, right);

int Left_BorderSum=0;
int Left_BorderMax=-65536;  //负无穷
for(int i=mid;i>=left;i--){
Left_BorderSum+=a[i]
if(Left_BorderSum>Left_BorderMax){
Left_BorderMax=Left_BorderSum;
}
}

int Right_BorderSum=0;
int Right_BorderMax=-65536;  //负无穷
for(int j=mid+1;j<=right;j++){ //注意必须等于right,在最初调用的是Max_Subarray(a,0,n-1) ,是n-1,不是n
Right_BorderSum+=a[i]
if(Right_BorderSum>Right_BorderMax){
Right_BorderMax=Right_BorderSum;
}
}

if( Left_Max>Right_Max && Left_Max>(Left_BorderMax+Right_BorderMax)) {
return Left_Max;
}
if( Right_Max>Left_Max && Right_Max>(Left_BorderMax+Right_BorderMax)) {
return Right_Max;
}
else
return Left_BorderMax+Right_BorderMax;
}

int maxsubsequencesum(const a[],int n)
{
return Max_Subarray(a,0,n-1);
}


上面的代码中有一个递归出口比较难理解,可用下面的例子加以说明

void  printout( unsigned int n)
{
if(n>=10)
printout(n/10);
getchar(n%10+'0');   //只有n<10时,才能执行到这一句,递归才能返回,即隐性的递归出口——基准(base case)
}
在这段代码中,递归出口是隐性的,可以改成如下的显性形式:

void  printout( unsigned int n)
{
if(n<10)
getchar(n%10+'0');  //显性的出口,即基准情况(base case)
else                   //else必须有,否则n<10时也要执行下面的语句,就死循环了。
printout(n/10);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: