一、 动态规划(Dp问题):解决问题的关键点
1) 递推公式:(最有子结构)
2) 数据的初始化
用例分析:
a. 01 背包的问题(Knapsack Problem)
定义: 一个背包的容量V; 存在N个物品:w[i]是背包的的容量,v[i]是背包的价值;求:背包能存放的最大价值?
hint: 每个背包存在两个状态:0不入包,1入包
分析: 定义 dp[i][j]:前i个物品 放入 容量为j的背包 时 的最大价值;
dp[i] 与 dp[i-1]的关系 =>对于物品i存在两个状态:0->不放入背包;1->放入背包;
* 0时: dp[i][j] = dp[i-1][j];
* 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; }