动态规划之背包问题

来源:互联网 发布:2016双十一大数据 编辑:程序博客网 时间:2024/06/18 06:51

01背包

1.问题描述

将N个物品,放入容量为C的背包之中,这N个物品的价值v与重量w都已知,求可以放入物品的最大价值。

2.算法思想

设:f[i][j] 为当容量为j,前i个物品备选时,背包可放入物品的最大值;w[i]为第i件物品的重量weight;v[i]为第i件物品的价值value。(i >= 0, w[0]  = 0, v[0] = 0,即“第0件物品的重量、价值都为0”)

“将前i件物品放入容量为j的背包中”,这个子问题,若只考虑第i件物品放或者不放,那么就转化为只牵扯前i件物品的问题:

1)不放第i件物品,那么问题就转化为“前i-1件物品放入容量为j的包中”  -> f[i-1][j]

2)放第i件物品,那么问题就转化为“前i-1件物品放入容量为j-w[i]的包中” -> f[i-1][j - w[i]] + v[i]

能放的条件是:第i件物品的重量w[i]小于等于此时背包的容量j。且,能放时,最大值为max(放,不放)。

3.数学表达

1) f[i][0] = f[0][j] = 0

2) f[i][j] = f[i-1][j],(w[i]>j)

3) f[i][j] = max{f[i-1][j],f[i-1][j-w[i]] + v[i]},(w[i]<=j)

4.java代码

public class DP {public static void main(String[] args) {DP dp = new DP();int[] weight = {0, 5 , 6, 4};int[] value = {0, 20, 10, 12};dp.solve(3, weight, value, 10);}void solve(int num, int[] weight, int[] value, int capacity) {int[][] f = new int[num + 1][capacity + 1];for (int i = 0; i <= num; i++) {for (int j = 0; j <= capacity; j++) {if(i == 0 || j == 0) {f[i][j] = 0;} else if (weight[i] <= j) {f[i][j] = Math.max(f[i -1][j], f[i - 1][j - weight[i]] + value[i]);} else {f[i][j] = f[i -1][j];}}}System.out.println(f[num][capacity]);}}

---------------------------------------------------------------------

5.优化

上面的方法是将每次外循环都计算出0~capacity个值存为二维数组的一行,因为计算下一行会用到上一行的值(f[i][j] = max{f[i-1][j],f[i-1][j-w[i]]+f[i]})。

由于一维数组也可以保存上一次循环的值,那么便可以将空间优化到O(capacity),这里需要保证在推f[i][j]时(也即在第i次外循环中推f[j]时)能够得到f[i-1][j]f[i-1][j-w[i]]的值。若按照以前的循环方式,则在第i次循环中的f[i-1][j]就等于f[i][j],可以得到 (值还未计算并覆盖)。关键是f[i-1][j-w[i]]。

若w[i] >0,即对于j前面的那些,上一次循环的值就已经在本次循环被计算并覆盖了。故,不可行。

因此,按照《背包九讲》里(不是我想到的...),采用逆序的方式。想想逆序为何不会覆盖?因为j-w[i]啊,逆序的时候小于j的值未被覆盖呢!!

这里要好好想想,最好自己动手填表试试,顺序填的表和逆序填的表不一样的。

6.优化后的数学表达

1) i == 0 || j == 0时,f[j] = 0;

2) f[j] = f[j],(w[i]>j)

3) f[j] = max{f[j],f[j-w[i]] + v[i]},(w[i]<=j)

7.优化后代码

void solve2(int num, int[] weight, int[] value, int capacity) {int[] f = new int[capacity + 1];for (int i = 0; i <= num; i++) {for (int j = capacity; j >= 0; j--) {if (j >= weight[i]) {f[j] = Math.max(f[j], f[j - weight[i]] + value[i]);} else {f[j] = f[j];//嗯,是句废话,为了思路清楚起见写出来}}}System.out.println(f[capacity]);}

完全背包

有N种物品和容量为V的背包,每种物品都有无限件可用。第i种物品的重量是w(i),价值是v(i)。求解将哪些物品装入背包可使这些物品的总重量不超过背包,且价值总和最大。

直接用一维数组的方法:

for i=1..N    for v=0..V        f[v]=max{f[v],f[v-c[i]]+w[i]}


你会发现,这个伪代码与P01的伪代码只有v的循环次序不同而已。为什么这样一改就可行呢?首先想想为什么要按照v=V..0的逆序来循环。这是因为要保证第i次循环中的状态f[i][v]是由状态f[i-1][v-c[i]]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑选入第i件物品这件策略时,依据的是一个绝无已经选入第i件物品的子结果f[i-1][v-c[i]]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑加选一件第i种物品这种策略时,却正需要一个可能已选入第i种物品的子结果f[i][v-c[i]],所以就可以并且必须采用v= 0..V的顺序循环。这就是这个简单的程序为何成立的道理。


void solve3(int num, int[] weight, int[] value, int capacity) {int[] f = new int[capacity + 1];for (int i = 0; i <= num; i++) {for (int j = 0; j <= capacity; j++) {if (j >= weight[i]) {f[j] = Math.max(f[j], f[j - weight[i]] + value[i]);}}}System.out.println(f[capacity]);}






参考:

http://www.cnblogs.com/jbelial/articles/2116074.html

http://blog.csdn.net/ls5718/article/details/52227908

http://www.wutianqi.com/?p=539


原创粉丝点击