最大連續子數列和(線上處理演算法)
阿新 • • 發佈:2018-12-24
問題描述
最大連續子數列和一道很經典的演算法問題,給定一個數列,其中可能有正數也可能有負數,我們的任務是找出其中連續的一個子數列(不允許空序列),使它們的和儘可能大。我們一起用多種方式,逐步優化解決這個問題。
暴力方法
求出所有可能連續子列的和,時間複雜度O(N^3)
int MaxSubSequm1(int A[], int N)
{
int ThisSum, MaxSum = 0;
int i, j, k;
for (i = 0; i < N; i++) { // i 是子列左端的位置
for (j = i; j < N; j++){ //j 是子列右端的位置
ThisSum = 0; //*
for (k = i; k <= j; k++) //*
ThisSum += A[k]; //*
if (ThisSum>MaxSum) // 如果剛得到的子列和更大
MaxSum = ThisSum; // 更新結果
}
}// 迴圈結束
return MaxSum;
}
一個簡單的優化
很顯然上面的演算法中,在計運算元列和時,可以利用前一步計算和的結果,不需要每次從頭累加。時間複雜度O(N^2)。
int MaxSubSequm1(int A[], int N)
{
int ThisSum, MaxSum = 0;
int i, j, k;
for (i = 0; i < N; i++) { // i 是子列左端的位置
for (j = i; j < N; j++){ //j 是子列右端的位置
ThisSum +=A[j]; //*
// 對於相同i ,的不同j ,只要在j-1次迴圈的基礎上累加1 項即可
if (ThisSum>MaxSum) // 如果剛得到的子列和更大
MaxSum = ThisSum; // 更新結果
}
}
return MaxSum;
}
分而治之
- 將問題一分為二,縮小問題規模,遞迴求解。
- 此處求解過程中要從中間基準點開始,掃描求出跨界的最大連續子列和,然後和左右邊的解比較求出最終的結果。
- 時間複雜度O(N*logN)
int Max3( int A, int B, int C )
{ /* 返回3個整數中的最大值 */
return A > B ? A > C ? A : C : B > C ? B : C;
}
int DivideAndConquer( int List[], int left, int right )
{ /* 分治法求List[left]到List[right]的最大子列和 */
int MaxLeftSum, MaxRightSum; /* 存放左右子問題的解 */
int MaxLeftBorderSum, MaxRightBorderSum; /*存放跨分界線的結果*/
int LeftBorderSum, RightBorderSum;
int center, i;
if( left == right ) { /* 遞迴的終止條件,子列只有1個數字 */
if( List[left] > 0 ) return List[left];
else return 0;
}
/* 下面是"分"的過程 */
center = ( left + right ) / 2; /* 找到中分點 */
/* 遞迴求得兩邊子列的最大和 */
MaxLeftSum = DivideAndConquer( List, left, center );
MaxRightSum = DivideAndConquer( List, center+1, right );
/* 下面求跨分界線的最大子列和 */
MaxLeftBorderSum = 0; LeftBorderSum = 0;
for( i=center; i>=left; i-- ) { /* 從中線向左掃描 */
LeftBorderSum += List[i];
if( LeftBorderSum > MaxLeftBorderSum )
MaxLeftBorderSum = LeftBorderSum;
} /* 左邊掃描結束 */
MaxRightBorderSum = 0; RightBorderSum = 0;
for( i=center+1; i<=right; i++ ) { /* 從中線向右掃描 */
RightBorderSum += List[i];
if( RightBorderSum > MaxRightBorderSum )
MaxRightBorderSum = RightBorderSum;
} /* 右邊掃描結束 */
/* 下面返回"治"的結果 */
return Max3( MaxLeftSum, MaxRightSum, MaxLeftBorderSum + MaxRightBorderSum );
}
int MaxSubseqSum3( int List[], int N )
{ /* 保持與前2種演算法相同的函式介面 */
return DivideAndConquer( List, 0, N-1 );
}
下面是一個過程演示圖:
這裡最大的連續子列和是11。
線上處理
我們可以通過拋棄負子列,保證最大子列和遞增。當掃描一遍,最大子列和不再遞增時,當前的最大子列和即為我們的解。這是最優演算法,時間複雜度O(N)。
int MaxSubseqSum4(int A[], int N)
{
int ThisSum, MaxSum;
int i;
ThisSum = MaxSum = 0;
for (i = 0; i < N; i++){
ThisSum += A[i];//向右累加
if (ThisSum>MaxSum)
MaxSum = ThisSum; // 發現更大和則更新當前結果
else if (ThisSum < 0) // 如果當前子列和為負數
ThisSum = 0; // 則不可能使後面部分和增大,拋棄之
}
return MaxSum;
}