感謝原作者的勞動: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
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!