DP之背包问题+记忆递归
2个问题:
1)背包问题的动态规划解法
2)动态规划的另一种实现方式,记忆递归(dp的第一篇文章就提到过,professional中也提到过),在这里来讲解,加上全面所有的文章,这一点应该可以算是动态规
划里最后一个没有详细介绍的关键点了
---------------------------------------------------------------------------------------------------------------------------------------------------
给定一组物品:
重量为 w1 , w2 , .......wn
价值为 v1 , v2 , .........vn
和一个称重量为W的书包。
求这些物品的一个最有价值的子集,可以装到书包中去。
--------------------------------------------------------------------------------------------------------------------------------------
1,背包问题
设 V[ i , j ]表示能够放进称重量为 j 的背包的前 i 个物品的最有价值子集的价值,目标是求V[ n , W ]
根据 V[ i , j ] 的最佳子集中是否包含物品 i,可以得到下列递推式:
这个递推式的意思是:如果 i 的重量已经比 j 大了,那显然不含 i ( j-wi < 0 ), 如果 i 的重量比 j 小,那么可能包含 i (如果包含能去到最大值的话)。
这个表可以按行也可以按列填:
实现:
package Section8;
/*第八章 动态规划 背包问题*/
public class BackPack {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] w = { 2, 1, 3, 2 }; // 重量数组
int[] v = { 12, 10, 20, 15 }; // 价值数组
int W = 5;
int[][] result = backPack(w, v, W);
for (int i = 0; i < result.length; i++)
{
for (int j = 0; j < result[0].length; j++)
System.out.print(result[i][j] + " ");
System.out.println();
}
}
public static int[][] backPack(int[] w, int[] v, int W) {
// w是物品重量数组,v事物品价值数组,W是背包重量
// 返回表达背包问题求解过程的矩阵
int n = w.length; // w和v的长度是相同的
int[][] result = new int[n + 1][W + 1]; // 前i个物品(i从0到n),W从0到W
// 初始条件:result[0][j] = 0;result[i][0] = 0;
for (int i = 0; i <= W; i++)
result[0][i] = 0;
for (int i = 0; i <= n; i++)
result[i][0] = 0;
// 根据动态规划的状态转移方程填表:这个表格可以一行一行的填,也可以一列一列的填,这里采用一行一行的填
// 注意填表方式是动态规划里面非常重要的一个东西,当你填某一个位置时,它需要用到的其他位置必须都已经填好
// 所以填表的方式是跟状态转移方程相关滴,深层次来说,是跟动态规划构造解的生成过程相关的
for (int i = 1; i <= n; i++) // 行数从1到n
{
for (int j = 1; j <= W; j++) // 列数从1到W
{
// 此时决定了一个i,j的位置要填:result[i][j]
if (j - w[i - 1] < 0)
result[i][j] = result[i - 1][j];
else
result[i][j] = max(result[i - 1][j], v[i - 1] + result[i - 1][j - w[i - 1]]);
}
}
return result;
}
public static int max(int m, int n) {
if (m >= n)
return m;
return n;
}
}
结果:
0 0 0 0 0 0
0 0 12 12 12 12
0 10 12 22 22 22
0 10 12 22 30 32
0 10 15 25 30 37
写了这么几个动态规划,已经可以发现,有了递推式后,其编程就显得非常easy,基本上就是照着公式操作矩阵。
因此,应重点关注动态规划的思想,怎么去描述和刻画问题,发现最有子结构,得到其状态转移方程。
-------------------------------------------------------------------------------------------------------------------------------------------------
2,记忆递归--动态规划的另一种实现方式
关于什么什么是记忆递归,它的出发点是什么,为什么要记忆递归,这些在前面都提过,这里再简单说下:
动态规划的核心思想之一就是记忆(或者记录)来避免对重复子问题进行求解,然而,它并没有避免对不必要子问题进行求解。
若将递归与动态规划结合起来,就可以得到一种动态规划的递归实现方式,这种方式避免了对不必要子问题进行求解。
实际上就是在递归的时候,在动态规划表中先检查要递归的项是否已经求出来了,如果已经求出来了,就直接返回答案,否则才去递归求解。
背包问题的记忆递归实现:
实际上,就是在V[ i , j]还没有求出来的时候(一旦一个V[ i , j]求出来了,就不会再变,这点很重要),才去递归,否则就直接返回值。
想想斐波拉契数列也可以记忆递归的去实现。
-------------------------------------------------------------------------------------------------------------------------------------------------
总结:
1) 背包问题的时间复杂度和空间复杂度都是 nW,2个循环,见代码
2)一个关键点:动态规划的另一种实现方式,记忆递归