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