一.基本概念
分治法的基本步驟:
1.分解問題(Divide):把原問題分解為若干個與原問題性質相類似的子問題;
2.求解字問題(Conquer):不斷分解子問題並求解;
3.合并子問題的解(Combine).
分治法的運用條件:
1.原問題可以分解為若干與原問題的解;
2.子問題可以分解並可以求解;
3.子問題的解可以合并為原問題的解;
4.分解後的子問題應互相獨立,即不包含重疊子問題(如菲不那切豎列)。
求解遞迴函式的方法:
1.代換法
1)猜測解的行為;
2)數學歸納法證明。
2.遞迴樹法
在遞迴樹中,每一個結點都代表遞迴函式調用集合中一個子問題的代價。將樹中每一層內的代價相加得到一個每層代價的集合,再將每層的代價相加得到遞迴是所有層次的總代價。
3.主方法
主要是記憶三種情況,根據各種情況直接寫出遞迴函式。
T(n) = aT(n/b)+h(n)
a >=1 ; b >1 ; h(n) : 不參與遞迴的複雜度函數
判斷n^log b (a)與h(n)的大小關係
= Θ(h(n)) :該方法的複雜度為 Θ(h(n)*lg(n))
> Θ(h(n)) :該方法的複雜度為 Θ(n^(log a/log b))
< Θ(h(n)) :該方法複雜度為 Θ(h(n))
這樣可以協助你快速的分析出你得演算法的複雜度是否符合要求。
二.最大子序列問題
對於一個包含負值的數字串array[1...n],要找到他的一個子串array[i...j](0<=i<=j<=n),使得在array的所有子串中,array[i...j]的和最大。
這裡我們需要注意子串和子序列之間的區別。子串是指數組中連續的若干個元素,而子序列只要求各元素的順序與其在數組中一致,而沒有連續的要求。
1.暴力解法,時間複雜度O(n~2)。
i表示子序列起始下標,j表示內部迴圈開始下表,遍曆子序列的開頭和結束下標,計運算元序列的和,
int MaxSubSum (int a[], int n){int i, j, maxSum = 0;for (i = 0; i < n; i++){int thisSum = 0;for (j = i; j < n; j++){thisSum += a[j];if (thisSum > maxSum)maxSum = thisSum;}}return maxSum;}
2.Kadane演算法,時間複雜度(0(n)).
原理:將數組從左至右分割為若干子串,使得除了最後一個子串之外,其餘子串的各元素之和小於0,
且對於所有子串array[i...j]和任意k(i<=k<j),有array[i...k]的和大於0。滿足條件的和最大子串,
只能是上述某個子串的首碼,而不可能跨越多個子串。
原理詳細可參考:http://blog.csdn.net/joylnwang/article/details/6859677
執行流程:從頭到尾遍曆目標數組,將數組分割為滿足上述條件的子串,同時得到各子串的最大首碼和,然後比較各子串的最大首碼和,得到最終答案。
以array={-2, 1, -3, 4, -1, 2, 1, -5, 4}為例,來簡單說明一下演算法步驟。通過遍曆,可以將數組分割為如下3個子串(-2),(1,-3),(4,-1,2,1,-5,4),
這裡對於(-2)這樣的情況,單獨分為一組。各子串的最大首碼和為-2,1,6,所以目標串的最大子串和為6。
int KadaneMax (int a[], int n){int i, j, curMax, max, curLeft, curRight;curMax = max = curLeft = curRight = 0;for (i = 0; i < n; i++){curMax += a[i];if (curMax > 0){curRight = i;//更新最大值if (max < curMax)max = curMax;}//重新分割子串else{curMax = 0;curLeft = curRight = i + 1;}}return max;}
3.遞迴實現,時間複雜度為O(nlogn)。
本期的主角上場。
分治的思想:最大子序列和可能出現在三處。或者整個出現在輸入資料的左半部,或者整個出現右半部,或者跨越輸入資料的中部從而佔據左右兩個半部分。前兩種情況遞迴求解。第三種情況的最大和可以通過求出前半部分的最大和(包含前半部分的最後一個元素)以及後半部分的最大和(包含後半部分的第一個元素)而得到,然後將這兩個和加在一起,求出三個值的最大值。
int recursionMax (int a[],int left, int right){int i,j;if (left == right)//base caseif (a[left] > 0)return a[left];elsereturn 0;int center = (left + right) / 2;// //每次遞迴返回時,該值為該子段的最終左最大子序列和 int maxLeftSum = recursionMax (a, left, center);//每次遞迴返回時,該值為該子段的右最大自序列和 int maxRightSum = recursionMax(a, center + 1, right);//從中間向左擴充求子段的最大子序列和,必然包括子段的最右端數 int maxLeftBorderSum = 0, leftBorderSum = 0;for (i = center; i >= left; i--){leftBorderSum += a[i];if (leftBorderSum > maxLeftBorderSum)maxLeftBorderSum = leftBorderSum;}int maxRightBorderSum = 0, rightBorderSum = 0;for (j = center + 1; j <right; j++){rightBorderSum += a[j];if (rightBorderSum > maxRightBorderSum)maxRightBorderSum = rightBorderSum;}int tmp=maxLeftSum>maxRightSum?maxLeftSum:maxRightSum;if (tmp>=maxLeftBorderSum + maxRightBorderSum) return tmp;else return maxLeftBorderSum + maxRightBorderSum;}
main函數調用:
int main (){int a[] = { -2, 1, -3, 4, -1, 2, 1, -5, 4,0};printf ("MaxSubSum is:%6d.\n", MaxSubSum(a, 9));printf ("KadaneMax is:%6d.\n", KadaneMax(a, 9));printf ("recursionMax is:%6d.\n", recursionMax(a,0, 9));return 0;}
運行結果: