1、 動態規劃(Dp問題):解決問題的關鍵點算法
1) 遞推公式:(最有子結構)數組
2) 數據的初始化 優化
用例分析:spa
a. 01 揹包的問題(Knapsack Problem).net
定義: 一個揹包的容量V; 存在N個物品:w[i]是揹包的的容量,v[i]是揹包的價值;求:揹包能存放的最大價值?code
hint: 每一個揹包存在兩個狀態:0不入包,1入包orm
分析: 定義 dp[i][j]:前i個物品 放入 容量爲j的揹包 時 的最大價值;blog
dp[i] 與 dp[i-1]的關係 =>對於物品i存在兩個狀態:0->不放入揹包;1->放入揹包;排序
* 0時: dp[i][j] = dp[i-1][j];ci
* 1時: dp[i][j] = dp[i-1][j-w[i]] + v[i];
因此遞推公式:dp[i][j] = max( dp[i-1][j], dp[i-1][ j-w[i] ] + v[i] );
初始化:i = 0時: dp[0][0-V] = 0;
j = 0時: dp[0-N][0] = 0; 取值範圍: 0<= i <= N; 0<= j <= V;
返回: return dp[N][V];
int knap01(vector<int> w, vector<int> v, int V) { if(w.size() < 0) return -1; int N = w.size(); //揹包的數量 //0<=i<=N; 0<=j<=V; vector< vector<int> >dp(N+1,vector<int>(V+1)); //init 初始化 for(int i=0; i<=N; i++) dp[i][0] = 0; for(int j=0; j<=V; j++) dp[0][j] = 0; //dp過程 for(int i=1; i<=N; i++) { for(int j=1; j<=V; j++) { if(j < w[i-1]){ dp[i][j] = dp[i-1][j]; cout << dp[i][j] << ' '; continue; } int v0 = dp[i-1][j]; //i是物品不入包的狀況 int v1 = dp[i-1][j-w[i-1]] + v[i-1]; //i物品入包的狀況 dp[i][j] = v0 > v1 ? v0 : v1; //取max cout << dp[i][j] << ' '; } cout << endl; } return dp[N][V]; }
aa. 揹包問題的升級: 即徹底揹包問題
與a的不一樣: 物品i能夠取無限個,而不是隻能取一個;
定義:一個揹包總容量爲V; 如今有N個物品,第i個 物品體積爲weight[i],價值爲value[i],每一個物 品都有無限多件; 如今往揹包裏面裝東西,怎麼裝能使揹包的內物品價值最大?
和01揹包問題惟一不一樣的是:01揹包問題是在前一個子問題(i-1種物品)的基礎上來解決當前問題(i種物 品),向i-1種物品時的揹包添加第i種物品;
而徹底揹包問題是在解決當前問題(i種物品),向i種物品時的揹包添加第i種物品。
dp[i] 與 dp[i-1]的關係 =>對於物品i存在兩個狀態:0->不放入揹包;1->放入揹包;
* 0時: dp[i][j] = dp[i-1][j];
* 1時: dp[i][j] = dp[i][j-w[i]] + v[i]; //代碼主要的區別
因此遞推公式:dp[i][j] = max( dp[i-1][j], dp[i][ j-w[i] ] + v[i] );
int knap01(vector<int> w, vector<int> v, int V) { if(w.size() < 0) return -1; int N = w.size(); //揹包的數量 //0<=i<=N; 0<=j<=V; vector< vector<int> >dp(N+1,vector<int>(V+1)); //init 初始化 for(int i=0; i<=N; i++) dp[i][0] = 0; for(int j=0; j<=V; j++) dp[0][j] = 0; //dp過程 for(int i=1; i<=N; i++) { for(int j=1; j<=V; j++) { if(j < w[i-1]){ dp[i][j] = dp[i-1][j]; cout << dp[i][j] << ' '; continue; } int v0 = dp[i-1][j]; //i是物品不入包的狀況 int v1 = dp[i][j-w[i-1]] + v[i-1]; //i物品入包的狀況; 徹底揹包問題 dp[i][j] = v0 > v1 ? v0 : v1; //取max cout << dp[i][j] << ' '; } cout << endl; } return dp[N][V];
a. Fibonacci序列的問題 DP 實現 http://my.oschina.net/u/573270/blog/479644
01揹包問題的優化:
a: 01揹包問題優化
計算f[i][j]能夠看出,在計算f[i][j]時只使用了f[i-1][0……j],沒有使用其餘子問題,所以在存儲子問題的解時,只存儲f[i-1]子問題的解便可。再進一步思考,計算f[i][j]時只使用了f[i-1][0……j],沒有使用f[i-1][j+1]這樣的話,咱們先計算j的循環時,讓j=M……1,只使用一個一維數組便可。
int knap02(vector<int>w, vector<int> v, int V) { if(w.size() < 0) return -1; int N = w.size(); vector<int> dp(V+1,0); //dp for(int i=1; i<=N;i++) { for(int j=V; j>=1; j--) //經過逆序,f[i-1][..] { if(j < w[i-1]){ dp[j] = dp[j-1]; continue; } int v0 = dp[j-1]; int v1 = dp[j - w[i-1]] + v[i-1]; dp[j] = v0 > v1 ? v0 : v1; cout << dp[j] << ' '; } } return dp[V]; }
aa: 徹底揹包問題的優化
int knap02(vector<int>w, vector<int> v, int V) { if(w.size() < 0) return -1; int N = w.size(); vector<int> dp(V+1,0); //dp for(int i=1; i<=N;i++) { for(int j=1; j<=V; j++) { if(j < w[i-1]){ dp[j] = dp[j-1]; continue; } int v0 = dp[j]; //徹底揹包 int v1 = dp[j - w[i-1] ] + v[i-1]; dp[j] = v0 > v1 ? v0:v1; } } return dp[V]; }
貪心算法求解揹包問題:
01揹包不能用貪心求解: 徹底揹包問題 能夠用貪心求解:
思路:
計算每一個揹包的單位重量價值v/w;從大到小排序; 選取價值大的物品加入揹包;若是超過揹包稱重V,則選區次重要的物品加入揹包;
struct good{ int w; int v; double p; }; bool cmp(good &g1, good& g2) { return g1.p < g2.p; } int greedy(vector<int>w, vector<int>v, int V) { vector<good> goods; for(int i=0; i<w.size(); i++) { good g; g.w = w[i]; g.v = v[i]; g.p = g.v / g.w; goods.push_back(g); } //sort sort(goods.begin(), goods.end(),cmp); //greedy int weight = 0; int value = 0; for(int i=0; i<goods.size(); i++) { while(weight + goods[i].w <= V){ weight += goods[i].w; value += goods[i].v; } } return value; //返回的值不必定是最優解 }
=>揹包問題能夠應用到不少的實際環境中;
最長公共字序列 VS 最長公共子串
最長公共字序列:
dp:
dp[i][j] 表示 str1的前i個字符 與 str2的前j個字符的公共字序列
遞推關係:
str1[i] = str2[j] 則, dp[i][j] = dp[i-1][j-1] + 1;
str1[i] != str2[j] 則,dp[i][j] 的值時dp[i-1][j] 與 dp[i][j-1]的最大值; dp[i][j] = max( dp[i-1][j], dp[i][j-1] );
//輸出長度 =>輸出公共子序列 int lcs(string str1, string str2) { if(str1.size() < 0 || str2.size() < 0) return 0; int row = str1.size(); int col = str2.size(); //init vector< vector<int > > dp(row+1, vector<int>(col+1)); for(int i=0; i<= row; i++) dp[i][0] = 0; for(int j=0; j<=col; j++) dp[0][j] = 0; //dp for(int i=1; i<=row; i++)//行 { for(int j=1; j<=col; j++) { if(str1[i-1] == str2[j-1]) dp[i][j] = dp[i-1][j-1] + 1; else{//str1[i-1] != str2[j-1] dp[i][j] = dp[i-1][j] > dp[i][j-1] ? dp[i-1][j] : dp[i][j-1]; } } } //輸出最長公共字序列:只是輸出了其中的一個 string substr=""; int i = row; int j = col; for(;i> 0 && j > 0;) { if(str1[i-1] == str2[j-1]){ substr+=str1[i-1]; i--; j--; } else{ if(dp[i-1][j] > dp[i][j-1]){ i--; // }else{ j--; } } } cout << substr << endl; return dp[row][col]; }
最長公共子串:
遞推公式
str1[i] = str2[j] 則 dp[i][j] = dp[i-1][j-1] + 1;
str1[i] <> str2[j] 則 dp[i][j] = 0;
int lcs2(string str1, string str2) { if(str1.size() < 0 || str2.size() < 0) return 0; int row = str1.size(); int col = str2.size(); vector< vector<int> > dp(row+1, vector<int>(col+1)); for(int i=0; i<=row; i++) dp[i][0] = 0; for(int j=0; j<=col; j++) dp[0][j] = 0; //dp int max = 0; for(int i=1; i<=row; i++) { for(int j=1; j<=col; j++) { if(str1[i-1] == str2[j-1]) dp[i][j] = dp[i-1][j-1] + 1; else{ dp[i][j] = 0; //區別的地方 } max = max > dp[i][j] ? max : dp[i][j]; } } //根據對角線就能求出子串: return max; }