最优子结构特性
对于一个问题,如果能从子问题的最优解求得较大规模同类子问题的最优解,最终得到给定问题的最优解,这就是问题最优解的最优子结构特性。当一个问题的最优解中包含了子问题的最优解时,则称该问题具有最优子结构特性。 请注意区分最优解与最优解值
动态规划是一种求解最优化问题的策略。动态规划通顾自底向上的方式,先求出并保存子问题的最优解,后面求解大规模的问题时可以直接把子问题的最优解拿来用,减少了重复计算。
动态规划的本质也是将规模较大的问题分解为规模较小的同类子问题。但分治法的子问题相互独立,可能会产生重复计算。动态规划法解决了这种问题。一个典型的例子就是Fibnacci数列。
f(n) = f(n - 1) + f(n - 2)
使用分治法递归求解时会产生重复计算,而使用记忆化搜索(动态规划的变种)会把子问题的解保留下来,保证了子问题的解只被计算一次。
贪心法与动态规划都常用于求解最优化问题。贪心法不能保证一定能求得最优解,但可以求出近似解,比如0/1背包问题。当最优量度标准不存在时,贪心便求不出最优解。此时动态规划可以进行求解。因为贪心法每一步决策
其实并不依赖于子问题的解,而依赖于求解完子问题时产生的状态。比如部分背包问题:贪心法进行决策时不会考虑子问题的解,只考虑求解子问题导致的当前状态(背包容量,剩余物品数目)。而动态规划法的每一步决策
依赖于子问题的解。
常用但不限于
求解最优化问题。非最优化问题:青蛙跳阶、Fibonacci等也可用DP求解,这不属于最优化问题,但问题的解依赖于子问题的解。最优化问题如:0/1背包,LCS,LIS,多段图,floyd, 矩阵连乘,最优二叉树等等。
cost(i, j) = min{cost(i + 1, p) + c(j , p)},i代表Vi组,j是该组内的顶点,cost(i,j)表示第i组的j号点到汇点的最短距离。
可以省去维度 i。
int MultGraph(int n){
for(int i = n - 2; i >= 0; i --){
cost[i] = INF;
for(auto p = a[i]; p; p=p->next){
int j = p->adjvex;
cost[i] = min(cost[i],cost[j] + p->weight);
}
}
return cost[0];
}
cost(i , j) = min{cost(i - 1, p) + c(p, j)},cost(i, j)表示j到源点的最短距离
int MultGraph(int n){
memset(cost, 0x3f, sizeof cost);
cost[0] = 0;
for(int i = 0; i <= n - 2; i ++){
for(auto p = a[i]; p; p = p->next){
int j = p->adjvex;
cost[j] = min(cost[j],cost[i] + p->weight);
}
}
return cost[n - 1];
}
2.矩阵连乘问题
典型区间DP, O ( n 3 ) O(n^3) O(n3)
状态转移方程
m(i, j) = min{ m(i , k) + m(k + 1, j) + p i p k + 1 p j p_ip_{k+1}p_j pipk+1pj}, m(i, j)表示矩阵 A i . . . A j A_i...A_j Ai...Aj相乘的最小次数
int MatrixChain(int n){ // 动态规划法
for(int k = 2; k <= n; k ++){ // 枚举区间长度
for(int i = 0; i + k - 1 < n; i ++){
int j = i + k - 1; //区间右端点
m[i][j] = m[i][i] + m[i+1][j] + p[i]*p[i+1]*p[j+1]; // 参照元 k = i
for(int k = i + 1; k < j; k ++){ // 区间分割点
m[i][j] = min(m[i][j], m[i][k]+m[k+1][j] + p[i]*p[k+1]*p[j+1]);
}
}
}
return m[0][n-1];
}
int solve2(int i, int j){ // 备忘录方法
if(i == j) return 0;
if(m[i][j]) return m[i][j];
m[i][j] = solve2(i+1,j) + p[i]*p[i+1]*p[j+1];
for(int k = i + 1; k < j; k ++){
m[i][j] = min(m[i][j],solve2(i, k) + solve2(k+1,j) + p[i]*p[k+1]*p[j+1]);
}
return m[i][j];
}
if a[i] == b[j] then
f(i , j) = f(i - 1, j - 1) + 1;
else
f(i, j) = max{f(i - 1, j), f(i, j - 1)}
int LCS(int i, int j){
if(i < 0 || j < 0) return 0;
if(f[i][j]) return f[i][j];
if(a[i] == b[j])
return f[i][j] = 1 + LCS(i - 1, j - 1);
else
return f[i][j] = max(LCS(i-1,j), LCS(i, j - 1));
}
int LCS(){
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++){
if(a[i] == b[j])
f[i][j] = f[i-1][j-1] + 1;
else
f[i][j] = max(f[i-1][j], f[i][j-1]);
}
}