是一種用來最優化問題的方法,將乙個複雜問題分解成若干個子問題,再通過將每個子問題的最優解記錄下來,這樣在再次碰到子問題的時候,就有乙個直接的答案。重點就是其取消了重複計算環節。
通常使用遞迴和遞推兩種方法實現動態規劃,遞迴又叫做:記憶化搜尋。
可以從乙個fibonacci數列的計算為例:
計算斐波那契數列的經典方法就是遞迴,也就是如下:
intf(
int n)
這裡就涉及了重複計算的問題,例如我要計算f(5)。那麼要計算f(4)和f(3)而f4=f3+f2,f3=f2+f1;可以看到f2被計算了兩遍。如果可以把重複計算的值在計算一遍後儲存起來,則可以實現程式的優化。
分析其迭代過程:可以用乙個一位陣列dp[maxn]記錄被計算過的f(n)的值。
dp[i]=f(i-1)+f(i-2);
那麼可以將原函式改寫:
intf(
int n)
這裡就可以看出記憶化搜尋的意思來,就是用乙個陣列記錄需要被重複計算的部分,等到再次呼叫到的時候直接輸出。
記憶化搜尋能夠把複雜度降級。
這裡可以引出動態規劃的乙個要素,就是重疊子問題和最優子結構,它被分解為幾個子結構後,每個子結構每次決策過程中要有最優解。
下面以乙個問題為例,再分步思考動態規劃問題。
給定乙個數字序列a1,a2,a3...,an,求i,j(1<=i<=j<=n),使得ai+...+aj最大,輸出最大和
這裡使用動態規劃解法,假設乙個dp[i]一維陣列。
他的子問題是什麼?假設現在有
a1=-2, a2=11, a3=-4, a4=-3
暴力方法就是遍歷,其過程可以寫下為:
a[1->1]=-2 a[1->2]=-2+11 a[1->3]=-2+11-4 a[1->4]=-2+11-4-3
a[2->2]=11 a[2->3]=11-4 a[2->4]=11-4-3
a[3->3]=-4 a[3->4]=-4-3
a[4->4]=-3
總結可以得到其子問題:
在這裡需要考慮:需要得到的結果是最大值。可以將最大值設為dp[i],表示以a[i]為結尾的一系列最大值。
而每次計算得到的最大值是什麼?
dp[i]=dp[i-1]+a[i]
這裡再考慮如果a[i]+dp[i-1]而邊界條件根據i-1>=0,則i>=1,所以需要單獨考慮i=0的情況:
dp[0]=a[0]
這樣就不會發生在迭代的時候卡殼的問題。
整個邏輯完成了之後就可以開始**編寫了。
#include
#include
using
namespace std;
const
int maxn=
10010
;int a[maxn]
,dp[maxn]
;int
main()
//數列輸入完畢
dp[0]
=a[0];
for(
int i=
1;i)//已經完成,接下來比較輸出
int k=0;
for(
int i=
1;i)printf
(%d,
&dp[k]);
return dp[k]
;}
再來看乙個問題
給出乙個字串s,求s的最長回文子串長度
eg:字串"patzjujztaccbcc"的最長回文子串為"atzjujzta",長度為9。
怎麼判斷是不是回文串?這是首要的問題。
傳統判斷回文的方法:
兩頭夾:先判斷s[0] 與s[len-1],再是s[1]與s[len-1-1],。。。。以此迴圈
那麼判斷是不是回文的方法就確定了,這是乙個可以分解成子問題的過程。
暴力方法就是遍歷所有的子串組合,在這個過程中,有許多的重複計算。
在每次判定的組合裡,都會重複之前的步驟,判定某個子串是不是回文。
那麼是否需要乙個數列,儲存之前計算過的關於某子串是不是回文的資訊?
這就是優化的地方。
那麼判斷乙個子串是不是回文的dp寫法呢?
//偽**
bool dp[i][j];
if(s[i]==s[j])
dp[i][j]==dp[i+1][j-1]; //j>=1
else if(s[i]!=s[j])
dp[i][j]=0;
我判斷我自身是不是回文,只需要先比較我的兩頭是不是相等,再判斷我裡面是不是回文,而裡面是不是回文這個資訊已經在之前計算過了。
這是乙個遞迴的過程。
那麼其邊界條件需要考慮的:
1.自身與自身,長度為1的回文子串,不能讓不能讓i+1>j-1
2.根據i+1>j-1,則j>i+2;那麼j=i+1的情況呢?要設定為邊界條件。
//兩條都是根據j>i+2的條件來寫的
for(i=0;i完成了設定後就可以開始寫**了。
#include
#include
#define maxn 1010
using
namespace std;
intmain()
}//這裡進行篩選的方式也需要考慮,是根據i,j雙迴圈的方式進行?還是根據固定長度l,遍歷完乙個長度的序列後再遍歷l++的值呢
//看哪種方式能夠不卡殼,那麼i,j雙迴圈的方式明顯不行,選用固定長度方式
for(
int l=
3;lsize()
;l++)if
(dp[i]
[j]==1)
ans=j-i+1;
}return ans;
}
《演算法筆記》
動態規劃演算法(DP)
動態規劃演算法採用分治演算法的思想,將原問題分成若干個子問題,然後分別求解各個子問題,最後將子問題的解組合起來得到原問題的解。分治演算法遞迴地求解各個子問題,可能重複求解某些子問題。與分治演算法不同的是,動態規劃演算法不是遞迴地求解各個子問題,它是從簡單問題的解入手,逐步求解,直至求解出原問題。動態...
動態規劃演算法
一 動態規劃演算法原理 將待求解的問題分解成若干個相互聯絡的子問題,先求解子問題,然後從這些子問題的解得到原問題的解 對於重複出現的子問題,只在第一次遇到的時候對它進行求解,並把答案儲存起來。了不去求解相同的子問題,引入乙個陣列,把所有子問題的解存於該陣列中,這就是動態規劃所採用的基本方法。動態規劃...
動態規劃演算法
動態規劃 通過把原問題分解為相對簡單的子問題來求解複雜問題。動態規劃常常適用於有重疊子問題和最優子結構性質的問題。演算法總體思想 演算法的基本步驟 演算法的基本要素 最優子結構 重疊子問題 備忘錄方法 問題描述 子串行 公共子串行 最長公共子串行 lcs 問題 問題分析 動態規劃求解lcs問題 最長...