**
**
最近在学习动态规划,对于一个问题解决的大致思路如下:根据已知问题分析出其状态转移方程 然后根据该方程先使用递归的暴力解法来解解决问题,由于该方式会产生大量的重叠子问题,于是在递归的基础上衍生出了一种记忆化搜索的方式,该方式有效地减少了递归函数的调用。
以上两种方式都是由一种自顶向下的方式进行思考,而动态规划问题则反其道而行之,通过自底向上的方法来解决问题。下面以动态规划的经典问题——0-1背包问题对三种方法进行实现
在我看来 解决问题的关键就是根据实际问题分析出状态转移方程。
下面给出问题描述:
有一个背包,它的容量为C(Capacity),现有n种不同的物品,编号为0……n-1,其中每一件物品的重量为w(i),价值为v(i)。问可以向这个背包中盛放哪些物品,使得在不超过背包容量的基础上,物品的总价值最大。
如果使用暴力解法,则每一件物品都可以放进背包也可以不放进背包,其时间复杂度为O(n * 2^n)
假设方程 F(n,C)代表将n个物品放进容量为C的背包,使其容量最大
则 对于任意的 F(i, C) 都有
因此,F(i, C)应为两种情况的最大值,即 F(i, C) = max(F(i - 1, C),v(i) + F(i - 1, C - w(i)) )
从而得到了该状态转移方程。
根据该方程 首先 有递归的暴力解法:
public class Knapsack01 { // 0-1 背包问题 暴力解法
public int kanpsack01 (int[] w, int[] v, int C) { // 自顶向下
if (w.length != v.length)
throw new IllegalArgumentException("Invalid w or v");
int n = w.length;
if (n == 0 || C == 0)
return 0;
memo = new int[n][C + 1];
for (int i = 0; i < n; i ++)
for (int j = 0; j < C + 1; j ++)
memo[i][j] = -1;
return bestValue(w, v, n - 1, C);
}
private int bestValue (int[] w, int[] v, int index, int c) {
if (index < 0 || c <= 0)
return 0;
int res = bestValue(w, v, index - 1, c);
if (c >= w[index])
res = Math.max(res, v[index] + bestValue(w, v, index - 1, c - w[index]));
return res;
}
}
记忆化搜索的方式 ,可以有效减少递归的次数
public class Knapsack01 { // 0-1 背包问题
private int[][] memo; //由于该问题被产品序号和背包容量两个因素所约束,故使用二维数组来标记
public int kanpsack01 (int[] w, int[] v, int C) { // 记忆化搜索方式 自顶向下
if ( w.length != v.length)
throw new IllegalArgumentException("Invalid w or v");
int n = w.length;
if (n == 0 || C == 0)
return 0;
memo = new int[n][C + 1];
for (int i = 0; i < n; i ++)
for (int j = 0; j < C + 1; j ++)
memo[i][j] = -1;
return bestValue(w, v, n - 1, C);
}
private int bestValue (int[] w, int[] v, int index, int c) {
if (index < 0 || c <= 0)
return 0;
if (memo[index][c] != -1)
return memo[index][c];
int res = bestValue(w, v, index - 1, c);
if (c >= w[index])
res = Math.max(res, v[index] + bestValue(w, v, index - 1, c - w[index]));
memo[index][c] = res;
return res;
}
}
最后,反其道而行之,由自底向上的方式去思考 即动态规划
public class Knapsack01 { // 0-1 背包问题
private int[][] memo;
public int kanpsack01_Dp (int[] w, int[] v, int C) { //动态规划方式 自底向上
if (w.length != v.length)
throw new IllegalArgumentException("Invalid w or v");
int n = w.length;
memo = new int[n][C + 1];
for (int i = 0; i < n; i ++) {
for (int j = 0; j < C + 1; j ++)
memo[i][j] = -1;
}
for (int j = 0; j <= C; j ++)
memo[0][j] = (j >= v[0] ? v[0] : 0 );
for (int i = 0; i < n; i ++) {
for (int j = 0; j <= C; j ++) {
//考虑0-i这些物品 且容积为j的背包所能容的最大价值
memo[i][j] = memo[i - 1][j];
if (j >= w[i])
memo[i][j] = Math.max(memo[i][j], v[i] + memo[i - 1][j - w[i]]);
}
}
return memo[n -1][C];
}
}