最大子序列和问题

  本周学习陈越老师的数据结构课程时,约到最大子序列和的问题。趁着周末有空,把脑海中还有的记忆整理下。

  最大子序列和问题:给定一个由N个整数组成成的序列{A1,A2,...,AN},求函数f(i,j)=max{0,Σjk=iAk}的最大值。

  第一种方法可以用枚举法,把所有的可能的组合都遍历一遍。

#include <stdio.h>
#include <iostream>
using namespace std;

int MaxSubSum(int A[], int N){
    int i, j, k, Sum, Maxsum = 0;
    for( i = 0; i<N; i++){//子序列左端
        for( j = i; j<N; j++){//子序列右端
            Sum=0;//初始化子序列和
            for (k= i; k<j; k++){
                Sum+=A[k];//求得子序列和
            }
            if(Sum>Maxsum){//对比子序列和
                Maxsum=Sum;//更新子序列和
            }
        }
    }
    return Maxsum;   
}

int main(){
    int N, i = 0;
    scanf("%d\n",&N);//数组长度
    int A[N];
    for ( i= 0; i<N; i++){//输入数组
        scanf("%d",&A[i]);
    }
    printf("\n%d",MaxSubSum(A,N));
    return 0;
}

  把所有可能都求出来然后相加,时间复杂度为O(N3)。在该方法上改进,对于左端相同(即i相同)右端不同(即j不同)的子序列的和,只需要在前一项的结果加上当前的右端A[j]即可。

#include <stdio.h>
#include <iostream>
using namespace std;

int MaxSubSum(int A[], int N){
    int i, j, k, Sum, Maxsum = 0;
    for( i = 0; i<N; i++){//子序列左端
        Sum=0;//初始化子序列和
        for( j = i; j<N; j++){//子序列右端
            Sum+=A[j];//相同的i,不同j的子序列的和,只需要在上一次的结果上累加即可
        }
        if(Sum>Maxsum){//对比子序列和
            Maxsum=Sum;//更新子序列和
       }
   }
    return Maxsum;   
}

int main(){
    int N, i = 0;
    scanf("%d\n",&N);//数组长度
    int A[N];
    for ( i= 0; i<N; i++){//输入数组
        scanf("%d",&A[i]);
    }
    printf("\n%d",MaxSubSum(A,N));
    return 0;
}

  改进后,时间复杂度为O(N2)。

  第二种方法,可以用分治法;分治法:把问题分解为小的子问题解决,然后把解决的子问题合并得出原问题的答案。再最大子序列问题中算法思路是:

  (1)把原序列一份为二,分解为左右大小分别为(N/2)大小的序列;

  (2)变为求解左右两个序列的最大子序列问题,和解跨越两个子序列边界的最大子序列问题;

  (3)求解左右两个子序列值时,按(1)(2)步骤重复,直到不能再划分为二,此时,子序列为A[i],为一个元素;

  (4)把所有子问题答案整合,即为原问题的最大子序列答案。

#include <stdio.h>
#include <iostream>
using namespace std;

int Max(int A, int B, int C){//求最大整数
    int Max=A;
    if(B>Max){
        Max=B;
    }
    if(C>Max){
        Max=C;
    }
    return Max;
}

int MaxSubSum(int A[], int left, int right){
    int MaxLeftSum, MaxRightSum, 
    MaxLeftBorderSum, MaxRightBorderSum,
    LeftBorderSum, RightBorderSum;

    if(left == right)
        if(A[left]>0)
            return A[left];
        else
            return 0;

    int center, i;

    center = (left+right)/2;//分界线
    MaxLeftSum = MaxSubSum(A, left, center);
    MaxRightSum = MaxSubSum(A, center+1, right);//递归求左右两边子列的最大子列和

    MaxLeftBorderSum = 0, LeftBorderSum=0; 
    for ( i = center; i>=left; i--){
        LeftBorderSum+=A[i];
        if(LeftBorderSum>MaxLeftBorderSum){
            MaxLeftBorderSum=LeftBorderSum;
        }
    }//向左扫求跨边界的最大子列和
    
    MaxRightBorderSum = 0, RightBorderSum=0;
    for ( i = center+1; i<=right; i++){
        RightBorderSum+=A[i];
        if(RightBorderSum>MaxRightBorderSum){
            MaxRightBorderSum=RightBorderSum;
        }
    }//向右扫求跨边界的最大子列和

    return Max(MaxLeftSum, MaxRightSum, MaxLeftBorderSum+MaxRightBorderSum);//返回左右子列,和跨边界子列中最大的子列和
}

int main(){
    int N;
    scanf("%d\n",&N);
    int A[N];
    for (int i= 0; i<N; i++){
        scanf("%d",&A[i]);
    }
    int right = N-1, left=0;
    printf("\n%d",MaxSubSum(A,left,right));
    system("pause");
    return 0;
}

  这个算法总的时间复杂度的递推公式:T(N)=2*T(N/2)+c*N; T(N/2)=2*(N/4)+c*(N/2).........T(1)=O(1) ;T(N)=2k*O(1)+c*kN, 2k=N; T(N)=O(N)+O(N*logN);该算法时间复杂度为N*logN。

  第三种算法是在线处理法。该算法的思路是:

  (1)先给最大子序列和初始化为0

  (2)从左往右逐项累加求和;

  (3)把(2)中逐项累加的和与已有的最大子序列和对比,若当前和比最大子序列和大,则把值赋予最大子序列;

  (4)若当前和为负,则可以判断其与后面任何项相加,都会使其变小,因此,舍去前面的项,把当前和归0;

  (5)重复(2)-(4)得到最大子列和。

#include <stdio.h>
#include <iostream>
using namespace std;

int MaxSubSum(int A[], int N){
    int i, MaxSum = 0, sum = 0;//给MaxSum设置一个小值
    for (i = 0; i<N; i++ ){
        sum += A[i];  //逐项相加
        if(sum>MaxSum){
            MaxSum = sum; //如果当前的子项的和大于MaxSum,把当前子项的和sum代入
        }
        else if (sum<0){
            sum = 0;//若当前子项和小于0,则归零从当前项开始重新累加
        }
    }
    return MaxSum;
}

int main(){
    int N;
    scanf("%d\n",&N);
    int A[N];
    for (int i= 0; i<N; i++){
        scanf("%d",&A[i]);
    }
    printf("\n%d",MaxSubSum(A,N));
    system("pause");
    return 0;
}

该算法时间复杂度为O(N)。但当整个序列都不为正数时,给出答案会是0,很明显是不正确的;因此可以做下改进,得到:

#include <stdio.h>
#include <iostream>
using namespace std;

int MaxSubSum(int A[], int N){
    int i, k = 0, MaxSum = 0, sum = 0;//给MaxSum初始化为0
    
    for(i= 0; i<N; i++){ //判断整个序列是否都不大于0
       if(A[i]>0){
           k=1;
       } 
    }
    
    if(k){
        for (i = 0; i<N; i++ ){
            sum += A[i];  //逐项相加
            if(sum>MaxSum)
                MaxSum = sum; //如果当前的子项的和大于MaxSum,把当前子项的和sum代入
            else if (sum<0)
                sum = 0;//若当前子项和小于0,则归零从当前项开始重新累加         
        }
    }
    else{
        MaxSum = A[0];
        for (i = 0; i<N; i++ ){
            if(A[i]>MaxSum)
                MaxSum = A[i]; //当整个序列都不为正时,最大子序列也就是其序列元素中的最大值
        }
    }

    return MaxSum;
}

int main(){
    int N;
    scanf("%d\n",&N);
    int A[N];
    for (int i= 0; i<N; i++){
        scanf("%d",&A[i]);
    }
    printf("\n%d",MaxSubSum(A,N));
    system("pause");
    return 0;
}

 

posted @ 2019-03-31 13:53  laStardust  阅读(356)  评论(0)    收藏  举报