今天解决动态规划里面最著名的问题之一:背包问题。
背包问题的描述是:已知:有一个容量为V的背包和N件物品,第i件物品的重量是w[i],收益是c[i]。
问题:在不超过背包容量的情况下,最多能获得多少价值或收益。
首先应该找到状态表示方程。首先看题目中描述的两个状态:容量,收益,以及最后要求的,在背包容量为V的情况下,i个物品放入背包里面的最大收益! 很容易想到,用一个数组f[i][j]表示,在容量为j的情况下,把i个物品放入背包里面的最大收益。
找到状态方程了(其实就是问题的数组表示,一般就是一维数组或者二维数组),然后就应该找到状态方程转换公示。个人觉得其实找状态方程比较重要,找准状态方程,转换公示就比较容易了。
首先,前i-1个物品放入到所有容量的背包的最大收益都已经算出来了(动态规划里面的假设),那么怎么求将i-1个物品放入到容量为j的背包?
首先应该确认一点:求i个物品放入容量为j的背包的最大收益,只和i-1有关,和i-2是无关的,因为i-2在求f[i-1]的时候已经确认了。
把物品放入背包有两种情况:放,或者不放。如果不放或者放不下,那么f[i][j]应该等于f[i-1][j],如果放,那么f[i][j] = max{f[i-1][j-w(i)] + c[i],f[i-1][j]};
解释一下为啥:首先,将第i个物品放入背包,那么背包里必须有足够的剩余空间。不必管背包里是否需要去掉一个物品,f[i - 1][j - w(i)]是什么意思?这是指前i-1个物品放入到容量为j-w(i)的背包的时候获得的最大收益,令他加上c(i),即是让第i个物品放入背包里的最大收益。
f[i-1][j]就是第i个物品不放入背包的最大收益。两者取大的,就是前i个物品放到容量为j的背包的最大收益。
其实这很像贪心法。。。 不过他是二维贪心。如果物品是3个,背包容量是3,那么他的处理流程是这样的:
第1步 | 计算前1个物品放入容量为1的背包的最大收益 |
第2步 | 计算前1个物品放入容量为2的背包的最大收益 |
第3步 | 计算前1个物品放入容量为3的背包的最大收益 |
第4步 | 计算前2个物品放入容量为1的背包的最大收益 |
第5步 | 计算前2个物品放入容量为2的背包的最大收益 |
第6步 | 计算前2个物品放入容量为3的背包的最大收益 |
第7步 | 计算前3个物品放入容量为1的背包的最大收益 |
第8步 | 计算前3个物品放入容量为2的背包的最大收益 |
第9步 | 计算前3个物品放入容量为3的背包的最大收益 |
每一步都是最优解,每一步都尽可能利用前一步的结果,最终结果也是最优解。
代码:
/** * DPTest2 : 动态规划练习2 背包问题联系 * * 背包问题描述: 已知:有一个容量为V的背包和N件物品,第i件物品的重量是w[i],收益是c[i]。 * * 限制:每种物品只有一件,可以选择放或者不放 * * 问题:在不超过背包容量的情况下,最多能获得多少价值或收益 * * * * @author xuejupo [email protected] * * create in 2016-1-14 下午6:59:53 */ public class DPTest2 { //物品的件数 private static final int size = 3; //包裹的容量 private static final int packSize = 5; private int[] w = new int[size + 1]; private int[] c = new int[size + 1]; //定义中间变量 private int[][] f = new int[size + 1][packSize + 1]; //初始化 { for(int i = 0; i < size + 1; i++){ w[i] = new Random().nextInt(3) % 3 + i; c[i] = new Random().nextInt(3) % 3 + i; } System.out.println("背包分别的重量和对应收益为:"); for(int i = 1; i < size + 1; i++){ System.out.print("第"+i+"件背包:重量为"+w[i] + ";收益为:"+c[i]); System.out.println(); } } /** * main: * * @param args * void 返回类型 */ public static void main(String[] args) { // TODO Auto-generated method stub DPTest2 dp = new DPTest2(); dp.packTest(); System.out.println(dp.f[size][packSize]); } /** * 假设f[i][v] 是前i个物品放入到容量为v的背包时,最大的收益 * 比如f[1][1] 是指第一个背包放入到容量为1的背包时的最大收益, * * packTest1: 背包问题练习 * void 返回类型 */ private void packTest(){ for(int i = 1; i <= size; i++){ for(int j = 1; j <= packSize; j++){ //背包的容量是j if(j >= w[i]){ f[i][j] = Math.max(f[i - 1][j], f[i - 1][j - w[i]] + c[i]); }else{ f[i][j] = f[i - 1][j]; } } } } }
结果:
背包分别的重量和对应收益为: 第1件背包:重量为1;收益为:2 第2件背包:重量为4;收益为:4 第3件背包:重量为3;收益为:4 6