關於最大子段和線性算法的證明


重復題目:

輸入一個整形數組,數組里有正數也有負數。
數組中連續的一個或多個整數組成一個子數組,每個子數組都有一個和。
求所有子數組的和的最大值。要求時間復雜度為O(n)。

此題最初載於

http://blog.csdn.net/v_JULY_v/article/details/6444021

我在文章中也對其做了總結,並對線性時間的算法做了自己的證明,這里重復如下(其實后面的遞歸式本身就證明了算法的正確性,這里只是希望通過暴力的方法嘗試對從另一個方面對它進行證明,即窮舉所有可能出現情況)

關於這道題的證明,我的思路是去證明這樣的掃描法包含了所有n^2種情況,即所有未顯示列出的子數組都可以在本題的掃描過程中被拋棄。

1 首先,假設算法掃描到某個地方時,始終未出現加和小於等於0的情況。

我們可以把所有子數組(實際上為當前掃描過的元素所組成的子數組)列為三種:

1.1 以開頭元素為開頭,結尾為任一的子數組

1.2 以結尾元素為結尾,開頭為任一的子數組

1.3 開頭和結尾都不等於當前開頭結尾的所有子數組

1.1由於遍歷過程中已經掃描,所以算法已經考慮了。1.2確實沒考慮,但我們隨便找到1.2中的某一個數組,可知,從開頭元素到這個1.2中的數 組的加和大於0(因為如果小於0就說明掃描過程中遇到小於0的情況,不包括在大前提1之內),那么這個和一定小於從開頭到這個1.2數組結尾的和。故此種 情況可舍棄

1.3 可以以1.2同樣的方法證明,因為我們的結尾已經列舉了所有的情況,那么每一種情況和1.2是相同的,故也可以舍棄。

2 如果當前加和出現小於等於0的情況,且是第一次出現,可知前面所有的情況加和都不為0

一個很直觀的結論是,如果子段和小於0,我們可以拋棄,但問題是是不是他的所有以此子段結尾為結尾而開頭任意的子段也需要拋棄呢?

答案是肯定的。因為以此子段開頭為開頭而結尾任意的子段加和都大於0(情況2的前提),所以這些子段的和是小於當前子段的,也就是小於0的,對於后面也是需要拋棄的。也就是說,所有以之前的所有元素為開頭而以當前結尾之后元素為結尾的數組都可以拋棄了。

而對於后面拋棄后的數組,則可以同樣遞歸地用1 2兩個大情況進行分析,於是得證。

這個算法的證明有些復雜,現在感覺應該不會錯,至少思路是對的,誰幫着在表達上優化下吧。:-)

后來在《編程珠璣》中看到了對此種算法的解釋,實際上,編程珠璣中對於sum小於0后舍棄的定義如下:

maxendinghere = max(maxendinghere, 0)

這里的maxendinghere就是網上常見算法的sum,其意義是以當前元素為結尾的最長字段的長度。

我們可以看到,這其實是一個動態規划解法,最優子結構可以歸納為:

  s[0] = max(a[0], 0)

  s[i] = max(s[i - 1] + a[i], 0)

即如果當前元素加上以前一個元素為結尾的子段和小於0,就把以當前元素為結尾的子段和設為0。實際上這個“子段”是一個空段。我不知道為什么非要區分0和非零,可能是在這道題里“小於0的元素會讓整個子段值變小”這個概念很顯眼吧,但是這種情況確實忽略如果所有輸入為負數的情況,雖然題目假設輸入有正有負。

於是我試圖改變最優子結構的構造,拋棄0的概念,定義為:

  s[0] = a[0]

  s[i] = max(s[i - 1] + a[i], a[i])

這樣看起來比較直觀,而且“以元素a[i]為結尾的最大子段和”就不會是一個空子段了。

這種規划方法我不知道是否有什么錯誤,看起來沒什么問題,我實現了一下:

#include <iostream>

using namespace std;

#define MAX_LEN 100

template<class T>
void maxSubArr(T *arr, int len, int &start, int &end, T &max_sum)
{
    if(len <= 0)
        return;

    T max_ending_sum;
    int i, starts[MAX_LEN]/*the start of the max sub array end with a[i]*/;

    max_ending_sum = arr[0];
    max_sum = arr[0];
    start = 0;
    end = 0;
    starts[0] = 0;
    for(i = 1; i < len; i ++)
    {
        // max_ending_sum here refers to the maximum subarray length ends with element a[i - 1]
        if(max_ending_sum + arr[i] > arr[i])
        {
            max_ending_sum = max_ending_sum + arr[i];
            starts[i] = starts[i - 1];
        }
        else
        {
            max_ending_sum = arr[i];
            starts[i] = i;
        }

        // max_ending_sum now refers to the maximum subarray length ends with element a[i]
        if(max_ending_sum > max_sum)
        {
            max_sum = max_ending_sum;
            start = starts[i];
            end = i;
        }
    }
}

int main()
{
    int a[] = {1, -2, 3, 10, -4, 7, 2, -5};
    int len = 8;
    //int a[] = {-1, -2, -3, -10, -4, -7, -2, -5};
    //int len = 8;
    int start, end, max_sum, i;

    maxSubArr(a, len, start, end, max_sum);

    cout << "maximum summary : " << max_sum << endl;
    cout << "the subarray : ";
    for(i = start; i <= end; i ++)
    {
        cout << a[i] << " ";
    }
}


輸出為:

maximum summary : 18
the subarray : 3 10 -4 7 2

 

特別的,當輸入變為:

int a[] = {-1, -2, -3, -10, -4, -7, -2, -5};

 

全部為負時,這個算法不用特殊考慮就可以得出正確結果:

maximum summary : -1
the subarray : -1

 

看起來沒什么錯~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM