动态规划 dynamic programming
動態(tài)規(guī)劃dynamic programming
June,7, 2015
作者:swanGooseMan
出處:http://www.cnblogs.com/swanGooseMan/p/4556588.html
聲明:本文采用以下協(xié)議進(jìn)行授權(quán):?自由轉(zhuǎn)載-非商用-非衍生-保持署名|Creative Commons BY-NC-ND 3.0?,轉(zhuǎn)載請注明作者及出處。?
1. 什么是動態(tài)規(guī)劃?
- dynamic programming is a method for solving a complex problem by breaking it down into a collection of simpler subproblems.(引自維基百科)
- 動態(tài)規(guī)劃是通過拆分問題,定義問題狀態(tài)和狀態(tài)之間的關(guān)系,使得問題能夠以遞推(或者說分治)的方式去解決。
- 如何拆分問題,是動態(tài)規(guī)劃的核心。而拆分問題,靠的就是狀態(tài)和狀態(tài)轉(zhuǎn)移方程的定義。
- 動態(tài)規(guī)劃的本質(zhì)(兩個重要的概念):狀態(tài)(形如dp[i][j])、狀態(tài)轉(zhuǎn)移方程(形如dp[i][j] = dp[i - 1][j] + dp[i -1][j – 1])
動態(tài)規(guī)劃相關(guān)的其他幾個名詞:
a. “緩存”,“重疊子問題”,“記憶化”:
這三個名詞,都是在闡述遞推式求解的技巧。以Fibonacci數(shù)列為例,計算第100項的時候,需要計算第99項和98項;在計算第101項的時候,需要第100項和第99項,這時候你還需要重新計算第99項嗎?不需要,你只需要在第一次計算的時候把它記下來就可以了。
上述的需要再次計算的“第99項”,就叫“重疊子問題”。如果沒有計算過,就按照遞推式計算,如果計算過,直接使用,就像“緩存”一樣,這種方法,叫做“記憶化”,這是遞推式求解的技巧。這種技巧,通俗的說叫“花費(fèi)空間來節(jié)省時間”。都不是動態(tài)規(guī)劃的本質(zhì),不是動態(tài)規(guī)劃的核心。
b. "無后效性",“最優(yōu)子結(jié)構(gòu)”:
上述的狀態(tài)轉(zhuǎn)移方程中,等式右邊不會用到下標(biāo)大于左邊i或者j的值,這是"無后效性"的通俗上的數(shù)學(xué)定義,符合這種定義的狀態(tài)定義,我們可以說它具有“最優(yōu)子結(jié)構(gòu)”的性質(zhì),在動態(tài)規(guī)劃中我們要做的,就是找到這種“最優(yōu)子結(jié)構(gòu)”。
c. “遞歸”:
遞歸是遞推式求解的方法。
?
2. 怎么用動態(tài)規(guī)劃?
- 通常用來求解最優(yōu)化問題(optimization problem): 這類問題可以有很多可行的解,我們需要找出其中的最優(yōu)解。應(yīng)用于子問題重疊的情況。
- 動態(tài)規(guī)劃通過拆分問題,對每個子問題只求解一次,將其解保存在一個表格(數(shù)組)中,從而無需每次求解一個子子問題時都重新計算,當(dāng)前子問題的解將由上一次子問題的解推出,只需要多項式時間復(fù)雜度,因此它比回溯法、暴力法等要快許多。
通常按如下四個步驟來設(shè)計動態(tài)規(guī)劃算法:
刻畫一個最優(yōu)解的結(jié)構(gòu)特征
遞歸地定義最優(yōu)解的值
計算最優(yōu)解的值,通常采用自底向上(如Fibonacci從1開始)的方法。也可以自頂向下(如Fibonacci從n開始)進(jìn)行求解,但此時需要對解需要進(jìn)行記錄。//此3步構(gòu)成動態(tài)規(guī)劃解的基礎(chǔ)。
利用計算出的最優(yōu)解的信息構(gòu)造一個最優(yōu)解。//此步如果只要求計算最優(yōu)解的值時,可省略。
即對于具有最優(yōu)子結(jié)構(gòu)、重疊子問題的最優(yōu)化問題,通過拆分問題,找出滿足無后效性的狀態(tài)和狀態(tài)轉(zhuǎn)移方程。(這也是DP的難點(diǎn)所在)
?
3. 實(shí)例
DP1:1447?采藥
問題描述:
辰辰是個天資聰穎的孩子,他的夢想是成為世界上最偉大的醫(yī)師。為此,他想拜附近最有威望的醫(yī)師為師。醫(yī)師為了判斷他的資質(zhì),給他出了一個難題。醫(yī)師把他帶到一個到處都是草藥的山洞里對他說:“孩子,這個山洞里有一些不同的草藥,采每一株都需要一些時間,每一株也有它自身的價值。我會給你一段時間,在這段時間里,你可以采到一些草藥。如果你是一個聰明的孩子,你應(yīng)該可以讓采到的草藥的總價值最大?!?如果你是辰辰,你能完成這個任務(wù)嗎?
分析:
將問題分解成若干子問題,讓問題規(guī)模變小
對于最終最優(yōu)結(jié)果(達(dá)到最大價值),假如第N個藥品沒有采,那么最優(yōu)結(jié)果就是總時間為totalTim內(nèi)采n-1個物品的最大價值。
對于n-1如果不是最大價值(即有比它更大的),那么與最優(yōu)結(jié)果的假設(shè)(第N個藥品沒有采)矛盾,所以滿足最優(yōu)子結(jié)構(gòu)性質(zhì),可以使用動態(tài)規(guī)劃算法。
拆分問題:
在totalTime(70)時間內(nèi)采摘totalDrug(3)藥草,最終達(dá)到最大價值,根據(jù)第N(3)個物品是否采摘,可分為兩種情況:
子問題1:第N個物品沒有采摘。即在totalTime(70)時間內(nèi)繼續(xù)采摘totalDrug-1(2)藥草。
子問題2:第N個物品有采摘。即在totalTime – time(n)(69)時間內(nèi)繼續(xù)采摘totalDrug-1(2)藥草,此時最優(yōu)價值應(yīng)加上該物品的價值vlan(n)(2)。
狀態(tài): time時間內(nèi)采摘drug顆藥草的最大價值dp[drug][time]
狀態(tài)轉(zhuǎn)移方程:dp[drug][time] = max { dp[drug-1][time] , dp[drug-1][time - time(n)]+vlan(n) },即dp[n][m] = 較大值{dp[n-1][m], dp[n-1][m-time[n]]+value[n] }
AC源碼:
#include <iostream> #include <cstdio> using namespace std;//物品數(shù)組,結(jié)構(gòu)體,時間,價值 typedef struct {int time;int value; }Drug; Drug drug[101];int main() {int totalTime, totalDrug;//總采藥時間,總藥品數(shù)int dp[101][1001]={0};//記錄表格 dp[總藥品數(shù)][總采藥時間]//讀入數(shù)據(jù)// freopen("input.txt", "r", stdin);scanf("%d%d", &totalTime, &totalDrug);for (int i = 1; i <= totalDrug; i++) {scanf("%d%d", &drug[i].time, &drug[i].value);}//DPfor (int i = 1; i <= totalDrug; i++) {for (int j = 1; j <= totalTime; j++) { //取兩個子問題的最大值dp[i][j] = dp[i-1][j]; //子問題1: 第N個物品沒有采摘if (drug[i].time <= j && dp[i][j] < dp[i-1][j-drug[i].time] + drug[i].value) { //子問題2: 第N個物品有采摘dp[i][j] = dp[i-1][j-drug[i].time] + drug[i].value;}}}printf("%d\n", dp[totalDrug][totalTime]);return 0; } View Code?
DP2 :1448最長上升子序列(Longest Increasing Subsequence)
問題描述:
一個數(shù)的序列bi,當(dāng)b1 < b2 < ... < bS的時候,我們稱這個序列是上升的。對于給定的一個序列(a1, a2, ..., aN),我們可以得到一些上升的子序列(ai1, ai2, ..., aiK),這里1 <= i1 < i2 < ... < iK <= N。比如,對于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。這些子序列中最長的長度是4,比如子序列(1, 3, 5, 8).
你的任務(wù),就是對于給定的序列,求出最長上升子序列的長度。
拆分問題:
原問題含n項序列的LIS長度,等價于以第1,2,3,...,n項結(jié)尾的LIS長度集合中的最大值,由此拆分為n個子問題,最后求出n個LCS的最大者:
n個子問題:以第n項結(jié)尾的LIS的長度是:保證第i項比第n項小的情況下,以第i項結(jié)尾的LIS長度加一的最大值,取遍i的所有值(i小于n)。
即max{dp[i]+1},其中 1<=i<=n-1 且 array[i] < array[n]
狀態(tài):數(shù)列中以第n項結(jié)尾的最長上升子序列的長度 dp[n]
狀態(tài)轉(zhuǎn)移方程:
dp[1] = 1;(根據(jù)狀態(tài)定義導(dǎo)出邊界情況)
dp[n] = max{dp[i]+1},其中 1<=i<=n-1 且 array[i] < array[n]
AC源碼:
#include <iostream> #include <cstdio> using namespace std;int lcs(int array[],int n) {int dp[n];int max = 1; //整個序列的最長遞增子序列長度,至少為1for (int i = 0; i < n; ++i) { //遍歷整個序列,分別求出n個子問題的解dp[i]dp[i] = 1; //以第i項結(jié)尾的LIS長度,至少為1,下面進(jìn)行計算//dp[i]:保證第i項比第n項小的情況下,以第i項結(jié)尾的LIS長度加一的最大值.for (int j = 0; j < i; ++j) { //遍歷前0 ~ i-1 項if(array[j] < array[i] && dp[i] < dp[j] + 1)dp[i] = dp[j] + 1;}if(max < dp[i]) max =dp[i];}return max; }int main(int argc, char const *argv[]) { // #ifndef _OJ_ //ONLINE_JUDGE// freopen("input.txt", "r", stdin); // freopen("output.txt","w",stdout); // #endifint n;int array[1001];scanf("%d", &n);for (int i = 0; i < n; ++i)scanf("%d", &array[i]);printf("%d\n", lcs(array, n));return 0; } View CodeDP4:1450?最長公共子序列(Longest Common Subsequence)
問題描述:
需要你做的就是寫一個程序,得出最長公共子序列。
最長公共子序列也稱作最長公共子串(不要求連續(xù)),英文縮寫為LCS(Longest Common Subsequence)。其定義是,一個序列S ,如果分別是兩個或多個已知序列的子序列,且是所有符合此條件序列中最長的,則S 稱為已知序列的最長公共子序列。
問題拆分:
最長公共子序列的結(jié)構(gòu)有如下表示:
設(shè)序列X=<x1, x2, …, xm>和Y=<y1, y2, …, yn>的一個最長公共子序列Z=<z1, z2, …, zk>,則可分為以下三種情況:
若xm=yn,則zk=xm=yn且Zk-1是Xm-1和Yn-1的最長公共子序列;
若xm≠yn且zk≠xm ,則Z是Xm-1和Y的最長公共子序列;
若xm≠yn且zk≠yn,則Z是X和Yn-1的最長公共子序列。
其中Xm-1=<x1, x2, …, xm-1>,Yn-1=<y1, y2, …, yn-1>,Zk-1=<z1, z2, …, zk-1>。
狀態(tài):
用dp[i,j]記錄序列Xi和Yj的最長公共子序列的長度。其中Xi=<x1, x2, …, xi>,Yj=<y1, y2, …, yj>。
狀態(tài)轉(zhuǎn)移方程:
當(dāng)i=0或j=0時,空序列是Xi和Yj的最長公共子序列,故dp[i,j]=0。其他情況下,由定理可建立遞歸關(guān)系如下:
| 0 if i=0 or j=0
dp[i][j] = | dp[i-1][j-1] if i>0 , j>0 and Xi == Yj
| max{dp[i][j-1], dp[i-1][j]} if i>0 , j>0 and Xi != Yj
?AC源碼:
#include <iostream> #include <cstring> #include <algorithm> #include <cstdio> using namespace std;int lcs(char s1[], char s2[]) {int maxlen = 0;int len1 = strlen(s1), len2 = strlen(s2);int dp[len1 + 1][len2 + 1]; //狀態(tài): dp[i,j]記錄序列 Xi 和 Yj 的最長公共子序列的長度for (int i = 0; i < len1 + 1; ++i) dp[i][0] = 0; //根據(jù)狀態(tài)定義導(dǎo)出邊界情況 (任一序列與空序列的lcs為0)for (int i = 0; i < len2 + 1; ++i) dp[0][i] = 0;for (int i = 1; i < len1 + 1; ++i) { //算法核心, 根據(jù)狀態(tài)轉(zhuǎn)移方程, 自底向上計算.for (int j = 1; j < len2 + 1; ++j) {if (s1[i - 1] == s2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);maxlen = max(maxlen,dp[i][j]);}}return maxlen; }int main(int argc, char const *argv[]) { // #ifndef _OJ_ //ONLINE_JUDGE// freopen("input.txt", "r", stdin); // // freopen("output.txt", "w", stdout); // #endifint n;char s1[1010], s2[1010];scanf("%d", &n);while (n--) {scanf("%s%s", s1, s2);// gets(s1); gets(s2);printf("%d\n", lcs(s1,s2));}return 0; } View Code?
?
轉(zhuǎn)載于:https://www.cnblogs.com/swanGooseMan/p/4556588.html
總結(jié)
以上是生活随笔為你收集整理的动态规划 dynamic programming的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓学习 intent
- 下一篇: hdu5247 找连续数