前言
這幾天一直在讀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
——————————————————————————————————————
轉載請注明出處,AllZY的博客園: http://www.cnblogs.com/allzy/