這幾天一直在讀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