[數據結構與算法分析] 求連續子數組的最大和問題

前言

  這幾天一直在讀Weiss的數據結構書(Data Structures and Algorithm Analysis in C:Second Edition),其中第二章是關於簡單的算法分析(引入大O記號等工具),以“求連續子數組的最大和問題”爲例,進行了一些說明和闡釋。最大子數組和問題(原書翻譯爲“最大的子序列和問題”)實際上我去年夏天暑假在家刷學院OJ的時候就見過,後來秋天開算法課,在上機時也有碰到。在網上看到這還是一道經典的面試題目,在此結合Weiss的書做一點總結性討論。

問題描述

  一個整數數組中的元素有正有負,在該數組中找出一 個連續子數組,要求該連續子數組中各元素的和最大,這個連續子數組便被稱作最大連續子數組。比如數組{2,4,-7,5,2,-1,2,-4,3}的最大連續子數組爲{5,2,-1,2},最大連續子數組的和爲5+2-1+2=8。問題輸入就是一個數組,輸出該數組的“連續子數組的最大和”。

思路分析

  這個問題我所見的有四種不同時間複雜度的算法。暴力模擬顯然是最慢的,而應用動態規劃思想,可以得到一個O(N)級別的線性時間算法,再它的基礎上稍微變形,可以得到一個更簡潔的形式。Talk is cheap, let's show codes.

代碼

Solution 1: 暴力模擬,三層循環,O(n^3)級別

 int MaxSubsequenceSum1(const int A[],int N)  {
    int ThisSum=0 ,MaxSum=0,i,j,k;  
    for(i=0;i<N;i++)
        for(j=i;j<N;j++)
        {  
            ThisSum=0;  
            for(k=i;k<=j;k++)
                ThisSum+=A[k]; 
              
            if(ThisSum>MaxSum)  
                MaxSum=ThisSum;  
        }  
        return MaxSum;  
} 

 

Solution 2:在Solution1基礎上撤出一個for循環,O(N^2)級別

int MaxSubsequenceSum2(const int A[],int N)  
{  
    int ThisSum=0,MaxSum=0,i,j,k;  
    for(i=0;i<N;i++)
    {  
        ThisSum=0;  
        for(j=i;j<N;j++)
        {  
            ThisSum+=A[j];  
            if(ThisSum>MaxSum)  
                MaxSum=ThisSum;  
        }  
    }  
    return MaxSum;  
}  


 

Solution 3: 分治法,分支界限的處理思路。

因爲最大子序列和可能在三處出現,整個出現在數組左半部,或者整個出現在右半部,又或者跨越中間,佔據左右兩半部分。遞歸將左右子數組再分別分成兩個數組,直到子數組中只含有一個元素,退出每層遞歸前,返回上面三種情況中的最大值。

根據這樣的分析,寫出代碼:

 static int MaxSubSum(const int A[],int Left,int Right)  
{  
    int MaxLeftSum,MaxRightSum;              //左、右部分最大連續子序列值
    int MaxLeftBorderSum,MaxRightBorderSum;  //從中間分別到左右兩側的最大連續子序列值
    int LeftBorderSum,RightBorderSum;  
    int Center,i;  
    if(Left == Right)Base Case  
        if(A[Left]>0)  
            return A[Left];  
        else  
            return 0;  
        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;  
        }  
        //比較各種情況,求出最大值
        int max1=MaxLeftSum>MaxRightSum?MaxLeftSum:MaxRightSum;  
        int max2=MaxLeftBorderSum+MaxRightBorderSum;  
        return max1>max2?max1:max2;  
}  


另外一份寫的更清晰的代碼:


/*
求三個數中的最大值
*/
int Max3(int a,int b,int c)
{
    int Max = a;
    if(b > Max)
        Max = b;
    if(c > Max)
        Max = c;
    return Max;
}


int MaxSubSum2(int *arr,int left,int right)
{
    int MaxLeftSum,MaxRightSum;    //左右邊的最大和
    int MaxLeftBorderSum,MaxRightBorderSum;    //含中間邊界的左右部分最大和
    int LeftBorderSum,RightBorderSum;    //含中間邊界的左右部分當前和
    int i,center;

    //遞歸到最後的基本情況
    if(left == right)
        if(arr[left]>0)
            return arr[left];
        else
            return 0;

    //求含中間邊界的左右部分的最大值
    center = (left + right)/2;
    MaxLeftBorderSum = 0;
    LeftBorderSum = 0;
    for(i=center;i>=left;i--)
    {
        LeftBorderSum += arr[i];
        if(LeftBorderSum > MaxLeftBorderSum)
            MaxLeftBorderSum = LeftBorderSum;
    }
    MaxRightBorderSum = 0;
    RightBorderSum = 0;
    for(i=center+1;i<=right;i++)
    {
        RightBorderSum += arr[i];
        if(RightBorderSum > MaxRightBorderSum)
            MaxRightBorderSum = RightBorderSum;
    }

    //遞歸求左右部分最大值
    MaxLeftSum = MaxSubSum2(arr,left,center);
    MaxRightSum = MaxSubSum2(arr,center+1,right);

    //返回三者中的最大值
    return Max3(MaxLeftSum,MaxRightSum,MaxLeftBorderSum+MaxRightBorderSum);
}

/*
將分支策略實現的算法封裝起來
*/
int MaxSubSum2_1(int *arr,int len)
{
    return MaxSubSum2(arr,0,len-1);
}

以上代碼時間複雜度爲O(NlogN)

Solution 4: 動態規劃(DP)

不難得出,針對這個問題,遞推公式是DP[i] = max{DP[i-1] + A[i],A[i]};既然轉移方程出來了,意味着寫一層循環就可以解決這個問題。

將這個轉移方程變爲形象的if-else判斷,代碼(來源於Weiss的書)爲:

int MaxSubSum(int arr[],int len)  
{  
    int i;  
    int MaxSum = 0;  
    int ThisSum= 0;  
    for(i=0;i<len;i++)  
    {  
        ThisSum+= arr[i];  
        if(ThisSum > MaxSum)  
            MaxSum = ThisSum;  
        /*如果累加和出現小於0的情況,  
           則和最大的子序列肯定不可能包含前面的元素,  
           這時將累加和置0,從下個元素重新開始累加  */
        else if(ThisSum< 0)  
            ThisSum= 0;  
    }  
    return MaxSum;  
}  

 

最後貼一份我去年暑假過學校OJ題時AC的代碼。

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
int MaxsumUlt(int * arr, int size)
{
    int maxSum = 0xf0000000;
    int sum = 0;
    for(int i = 0; i < size; ++i)
    {
        if(sum < 0)
        {
            sum = arr[i];
        }
        else
        {
            sum += arr[i];
        }
        if(sum > maxSum)
        {
            maxSum = sum;
        }
    }
    return maxSum;
}

int main(){
    int n;
    while(cin>>n){
    int a[n];
    for(int i = 0;i<n;i++)
        cin>>a[i];
    printf("%d \n",MaxsumUlt(a,n));
    }

}


 

其他

  Weiss的這本數據結構書的確不錯,跟我大一下學校開數據結構課程所用的教材(兩本清華出版的)簡直雲泥之別。只不過對於沒有算法基礎的初學者而言,可能會有一定難度,篇幅簡略必然帶來理解難度的增加。

參考資料

  http://blog.csdn.net/ns_code/article/details/20942045

  http://blog.sina.com.cn/s/blog_60d6fadc0101369g.html


  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章