ACM-最大子序列和

感謝原作者的勞動:https://blog.csdn.net/u011787119/article/details/44737749

最大子序列和問題,即給出一個序列a1,a2,a3,...,an,問該序列的所有子序列中,各項相加後和最大是多少,甚至還可能要求給出該子序列的起始位置和終止位置。

算法一:

最容易想到、直觀的算法,就是枚舉子序列的任何一個起點和終點位置,這樣一定可以得到正確答案,並且也能求出子序列的位置,但同時也可以確定的是這樣做的時間複雜度太高,達到了o(n^3),程序執行效率不高,一般都會超過時間限制。實現代碼如下:

// o(n^3)算法
int MaxSubsequenceSum(int *seque, int size)
{
    int maxSum = seque[0];
    // i爲子序列的左邊界
    for(int i=0; i<size; ++i)
    {
        // j爲子序列的右邊界
        for(int j=i; j<size; ++j)
        {
            int subSum = 0;
            // 迭代子序列中的每一個元素,求和
            for(int k=i; k<=j; ++k) subSum += seque[k];
            if(subSum > maxSum) maxSum = subSum;
        }
    }
    return maxSum;
}

算法二:

算法一使用了三層循環,但其實最裏面的那一層循環,即通過循環來計算某一個子序列的和,這裏是可以避免的,可以使用累加的思想來代替循環求取子序列的和,即將複雜度降爲o(n^2)。試想如果起點i確定,在枚舉終點j的時候,由於j是逐漸增大的,所以當前的sum(i,j),不就是前面計算過的sum(i,j-1),然後再加上aj麼。實現代碼如下:

// o(n^2)算法
int MaxSubsequenceSum(int *seque, int size)
{
    int maxSum = seque[0];
    for(int i=0; i<size; ++i)
    {
        // 維護前綴和
        int subSum = 0;
        for(int j=i; j<size; ++j)
        {
            subSum += seque[j];
            if(subSum > maxSum) maxSum = subSum;
        }
    }
    return maxSum;
}

算法三:

這裏介紹一種o(nlongn)的算法,其根本思想是分治。根據分治三部曲,首先將序列不斷二分,最終和最大的子序列出現的位置不過以下三種可能:

1、完全在左半部

2、完全在右半部

3、跨越左右兩個部分

對於1、2情況,直接遞歸就可以找出正確答案;對於3情況,可以以中點爲中心,向左算出包含左半部分最後一個元素的最大子序列和,向右算出包含右半部份第一個元素的最大子序列和,那麼答案就是它們的和。實現代碼如下:

// o(nlogn)算法
int Max3(int a, int b, int c)
{
    // 返回三個數中的最大值
    if(a < b) a = b;
    if(a > c) return a;
    else return c;
}
int MaxSubsequenceSum(int *seque, int left, int right)
{
    if(left == right)
    {
        if(seque[left] > 0) return seque[left];
        // 保證最小值爲0
        else return 0;
    }
 
    int mid = left + (right-left)/2;
    // 遞歸調用,求左部分的最大和
    int maxLeftSum = MaxSubsequenceSum(seque, left, mid);
    // 遞歸調用,求右部分的最大和
    int maxRightSum = MaxSubsequenceSum(seque, mid+1, right);
 
    // 定義左邊界子序列的和
    int leftBorderSum=0, maxLeftBorderSum=0;
    for(int i=mid; i>=left; --i)
    {
        leftBorderSum += seque[i];
        if(leftBorderSum > maxLeftBorderSum)
        {
            maxLeftBorderSum = leftBorderSum;
        }
    }
 
    // 定義右邊界子序列的和
    int rightBorderSum=0, maxRightBorderSum=0;
    for(int i=mid+1; i<=right; ++i)
    {
        rightBorderSum += seque[i];
        if(rightBorderSum > maxRightBorderSum)
        {
            maxRightBorderSum = rightBorderSum;
        }
    }
 
    // 選出這三者中的最大值並返回
    return Max3(maxLeftSum, maxRightSum, maxLeftBorderSum+maxRightBorderSum);
}

算法四:

其實對於最大子序列和問題,還有時間複雜度僅爲o(n)的算法,此算法基於以下分析:

如果ai是負數,那麼它不可能代表最優序列的起點,因爲任何包含ai的作爲起點的子序列都可以通過使用a[i+1]作爲起點得到改進。類似的,任何負的子序列也不可能是最優子序列的前綴(原理相同)。如果在內循環中檢測到從ai到aj的子序列的和是負數,那麼可以向後推進i。關鍵的結論是:我們不僅能夠把i推進到 i+1,而且實際上我們還可以把它一直推進到j+1。該算法的一個附帶優點是:它只對數據進行一次掃描,一旦ai被讀入並處理,它就不再需要被記憶,並且在任意時刻,算法都能對它已經讀入的數據給出最大子序列和問題的正確答案,具有這種特性的算法叫做“聯機算法”,僅需要常量空間並以線性時間運行。實現代碼如下:

// o(n)算法,但無法求出位置
int MaxSubsequenceSum(int *seque, int size)
{
    int maxSum=seque[0], subSum=0;
    for(int i=0; i<size; ++i)
    {
        subSum += seque[i];
        if(subSum > maxSum) maxSum = subSum;
        else if(subSum < 0) subSum = 0;
    }
    return maxSum;
}

算法五:

算法四的時間複雜度是最優的了,但是它有一個不足,就是隻能求出最大子序列的和,而不能求出其位置,因爲在實現上並沒有記錄終點。這裏再討論另外一種算法,時間複雜度同樣是o(n^2),但是同時也能求出位置信息。其算法實現依賴的是動態規劃的思想,設subSum[i]爲以a[i]終止且包含a[i]的最大序列的和,則有:

1、如果subSum[i]<=0,那麼對於subSum[i+1]來說,它將沒影響,甚至減少subSum[i+1]的值,那麼subSum[i+1]不應該考慮加上它,而應該直接等於a[i+1]

2、否則,如果subSum[i]>0,那麼它將增加subSum[i+1]的值,所以subSum[i+1]應該等於subSum[i] + a[i+1]

所以,可以得出轉移方程:subSum[1] = a[1],subSum[i+1] = subSum[i]>0 ? subSum[i]+a[i+1] : a[i+1]。實現代碼如下:

// o(n)算法
int MaxSubsequenceSum(int *seque, int size, int &left, int &right)
{
    int start, maxSum, subSum;
    // 初始化當前子序列和最大子序列爲seque[0]
    start = left = right = 0;
    maxSum = subSum = seque[0];
 
    for(int i=1; i<size; ++i)
    {
        if(subSum > 0) subSum += seque[i];
        else
        {
            // 拋棄當前子序列
            subSum = seque[i];
            // 開始新的子序列搜索
            start = i;
        }
 
        // 更新最大子序列
        if(maxSum < subSum)
        {
            maxSum = subSum;
            left = start;
            right = i;
        }
    }
    return maxSum;
}

對於最大子序列和問題還有一種擴展,即將一維序列擴展爲二維序列,詳見下一篇文章,傳送門(點擊打開鏈接)
--------------------- 
作者:潛水的瘋 
來源:CSDN 
原文:https://blog.csdn.net/u011787119/article/details/44737749 
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!

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