1. 什麼是動態規劃?
和分治法一樣,動態規劃(dynamicprogramming)是通過組合子問題而解決整個問題的解。
分治法是將問題劃分成一些獨立的子問題,遞歸地求解各子問題,然後合併子問題的解。
動態規劃適用於子問題不是獨立的情況,也就是各子問題包含公共的子子問題。
此時,分治法會做許多不必要的工作,即重複地求解公共的子問題。動態規劃算法對每個子問題只求解一次,將其結果保存起來,從而避免每次遇到各個子問題時重新計算答案。
2. 動態規劃算法的設計
兩種方法:
自頂向下(又稱記憶化搜索、備忘錄):基本上對應着遞歸函數實現,從大範圍開始計算,要注意不斷保存中間結果,避免重複計算
自底向上(遞推):從小範圍遞推計算到大範圍
動態規劃的重點:
遞歸方程+邊界條件
3. 爬樓梯問題
一個人每次只能走一層樓梯或者兩層樓梯,問走到第80層樓梯一共有多少種方法。
設DP[i]爲走到第i層一共有多少種方法,那麼DP[80]即爲所求。很顯然DP[1]=1, DP[2]=2(走到第一層只有一種方法:就是走一層樓梯;走到第二層有兩種方法:走兩次一層樓梯或者走一次兩層樓梯)。同理,走到第i層樓梯,可以從i-1層走一層,或者從i-2走兩層。很容易得到:
遞推公式:DP[i]=DP[i-1]+DP[i-2]
邊界條件:DP[1]=1 DP[2]=2
(a)自頂向下的解法:
- long long dp[81] = {0};/*用於保存中間結果
- 否則會重複計算很多重複的子問題*/
- long long DP(int n)
- {
- if(dp[n])
- return dp[n];
- if(n == 1)
- return 1;
- if(n == 2)
- return 2;
- dp[n] = DP(n-1) + DP(n-2);
- return dp[n];
- }
(b)自底向上的解法:
- int i;
- long long dp[81]; /* 注意當n超過75時,結果值將超過int範圍 */
- dp[1] = 1;
- dp[2] = 2;
- for(i=3; i <= 80; i++)
- dp[i] = dp[i-1] + dp[i-2];
4. 最長上升子序列
對於序列:4 1 2 24,它的最長上升子序列是1 2 4,長度爲3。
對於序列:4 2 4 25 6,它的最長上升子序列是2 4 5 6,長度爲4
設a[i]表示原序列,設DP[i]表示以第i個數結尾的最長上升序列的長度,那麼很顯然想導出DP[i]的值,需要在DP[k](1<=k<i)中找出滿足a[k]<a[i]最大的一項。假設第kk項是我們找到的答案,那麼第i個數就可以接在第kk個數之後,成爲以第i個數結尾的最長升序列。如果沒有找到答案,換言之第i個數比前面的數都要小,那麼DP[i]=1,也即生成了從自己開始又以自己結尾的最長升序列。綜上,我們很容易得出:
遞推公式:DP[i]=max(DP[k]+1,DP[i]) 1<=k<i
邊界條件:DP[i]=1 1<=i<=n
算法複雜度爲O(n^2)
- void RiseSequence(int Array[], int num)
- {
- #define MAX_LENGTH 30
- struct
- {
- int SequenceValue; /* max length ending with this num */
- int PreviousIndex; /* record the previous number */
- }ArrayInfo[MAX_LENGTH], temp;
- int i;
- for(i = 0; i < num; i++)
- {
- int j;
- ArrayInfo[i].SequenceValue = 1;
- ArrayInfo[i].PreviousIndex = -1;
- for(j = 0; j < i; j++)
- {
- if(Array[j] < Array[i] && (ArrayInfo[j].SequenceValue + 1 > ArrayInfo[i].SequenceValue))
- {
- ArrayInfo[i].SequenceValue = ArrayInfo[j].SequenceValue + 1;
- ArrayInfo[i].PreviousIndex = j;
- }
- }
- }
- temp.SequenceValue = ArrayInfo[0].SequenceValue;
- for(i = 1; i < num; i++)
- {
- if(temp.SequenceValue < ArrayInfo[i].SequenceValue)
- {
- temp.SequenceValue = ArrayInfo[i].SequenceValue;
- temp.PreviousIndex = i;
- }
- }
- for(i = 0; i < temp.SequenceValue; i++)
- {
- printf("%d ", Array[temp.PreviousIndex]); /* in reverse order */
- temp.PreviousIndex = ArrayInfo[temp.PreviousIndex].PreviousIndex;
- }
- printf("\nthe max rising sequence length is %d\n", temp.SequenceValue);
- }
5. 最長公共子序列
給定兩個序列X和Y,稱序列Z是X和Y的公共子序列如果Z既是X的一個子序列,又是Y的一個子序列。例如,如果X={a,b,c,b,d,a,b} Y={b,d,c,a,b,a} 那麼序列{b,c,a}就是X和Y的一個公共子序列,但是它並不是X和Y的最長公共子序列,因爲它的長度爲3。而同爲X和Y公共子序列的{b,c,b,a},長度爲4,因爲找不到長度爲5或更大的公共子序列,所以X和Y的最長公共子序列長度就爲4。
假設兩個序列數組分別爲a,b。定義f(i,j)爲計算到a數組第i個數、b數組第j個數時所得到的最長公共子序列的長度。這時有兩種情況:
1.假如a[i]=b[j],那麼f(i,j)=f(i-1,j-1)+1
2.假如a[i]!=b[j],那麼f(i,j)=max(f(i-1,j),f(i,j-1))
邊界條件爲:f(i,0)=0 1<=i<=len(a)
f(0,j)=0 1<=j<=len(b)
算法複雜度:O(n^2),len(a)表示數組a的長度。