前言
之前思考过这个问题,细想无果,老办法:上网求助
在浏览了众多博客之后,有了一些想法,在此进行记录。仅作为学习笔记
如有错误之处,劳驾您指出来,您的指点是我的荣幸。
解法
题目:
假设山洞里共有e,d,c,b,a这5件宝物(不是5种宝物),它们的重量分别是4,5,6,2,2,它们的价值分别是6,4,5,3,6,现在给你个承重为10的背包, 怎么装背包,可以才能带走最多的财富?
思路
在此用Vi表示第 i 个物品的价值;Wi表示第 i 个物品的重量;
F(i,j)表示在当前背包容量为 j 的时候,从 i 个物品序列中进行选择能达到的最大价值
例如第3个物品指的是c,以此类推。
在添加一个新物品进背包的时候,有两种选择:
- 新添加的物品的重量大于当前背包容量,自然而然的我们无法将其加入背包中;
- 新添加的物品的重量小于当前背包容量,所以我们可以选择将新的物品加入背包或者选择不将其加入背包中。
注:下列所举的例子跟前文提及的题目无关。
第一种选择
对于第一种情况,新添加的物品重量大于当前背包的容量,所以不需要改变原来背包中的物品序列(例如原本的物品序列为:a,b),即:遇到新的物品后,其价值仍然是V(a)+V(b)我们可以将其转换成:在遇到第i个物品时有:F(i,j)=F(i-1,j);(ps:这是个赋值语句)
例如:
在背包容量为5的情况下,背包里有2个物品:a,b;当遇到第3个物品c后,发现其重量大于当前背包的重量(Wc>5),所以有F(3,5)=F(2,5)
所以可以得出:当Wi> j;有F(i,j)=F(i-1,j)
第二种选择
对于第二种情况,新添加的物品重量小于当前背包的容量,我们面临选择:要不要将其换入背包中
换与不换:
例如在背包容量为9的情况下,背包里面此时有3个物品:a,b,c;此时遇到第4个物品d,若要换入,其实换个想法就是我们默认了:在容量9的情况下,从a,b,c,d四个物品中选择具有最高价值的物品序列中一定有一个d,但换入以后是否真的是最高价值,还需要和Va+Vb+Vc进行比较(其实就是F(i-1,j))
即:当Wi<= j时,有:F(i,j)=max{ F(i-1,j),F(i-1,j-Wi)+Vi }
F(i-1,j)就是不换的情况下得到的最高价值
F(i-1,j-Wi)+Vi 指的是如果我们换,默认其是达到最高价值的最优序列中的一员,放入d之后,背包的容量为 j-Wi ,但此时又出现了一个新的问题A:原有背包中的a,b,c是否都不需要进行改动呢?这点我们不知道,只能通过计算得知。
但对于问题A,我们是不是能理解为:在背包容量为 j-Wi 的情况下,从a,b,c三个物品中怎么选择才能达到最高价值(不用考虑是否能够全部装下a,b,c),那么我们怎么得到背包容量为 j-Wi 的最优序列呢?待会揭晓…
动态规划
下图的表格:最上面一行代表“当前”背包容量( j ),第一列代表物品序列( i )
例如:e(i=1),d(i=2),c(i=3),b(i=4),a(i=5)
注:下面会出现例如:c-1,实际上就是3-1=2,也就是指d
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
---|---|---|---|---|---|---|---|---|---|---|
e | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
d | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 10 |
c | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 11 |
b | 0 | 3 | 3 | 6 | 6 | 9 | 9 | 9 | 10 | 11 |
a | 0 | 6 | 6 | 9 | 9 | 12 | 12 | 15 | 15 | 15 |
该怎么理解这个表格呢??
表格是从e行1列单元格,先列后行进行填写。
第b行0列代表的是:当背包容量为1的时候,从e,d,c,b四种物品中怎么挑选,才能达到最高价值?
第d行7列代表的是:当背包容量为7的时候,从e,d两种物品中怎么挑选,才能达到最高价值?
。
。
。
基于这个表格,
例如当我们计算第c行8列的时候:
Wc<j;F(c,j)=max{ F(c-1,j),F(c-1,j-Wc)+Vc }
亦即:
6<8;F(3,8)= max{ F(2,8),F(2,8-6)+5 }
=》F(3,8)=max{ F(2,8),F(2,2)+5 }
=》F(3,8)= max{ 6,0+5 }
=》F(3,8)= 6
而在我们计算F(3,8)之前,我们就已经计算了F(2,2),所以直接引用即可。这就是问题A的解决办法。
注:
动态规划方法仔细安排求解顺序,对每个子问题只求解一次,并将结果保存下来。如果随后再次出现此子问题的解,只需查找保存的结果,而不必重新计算。因此,动态规划算法是付出额外的内存空间来节省计算时间,是典型的时空权衡的例子。 -----《算法导论》
题目代码实现
/**
* 使用动态规划解决01背包问题
*/
public class finalDemo01 {
/**
* 假设山洞里共有e d c b a这5件宝物(不是5种宝物),它们的重量分别是4,5,6,2,2,它们的价值分别是6,4,5,3,6
* 现在给你个承重为10的背包, 怎么装背包,可以才能带走最多的财富
*
* @param args
*/
public static void main(String[] args) {
//定义矩阵
//假设有5个物品e d c b a背包容量为10
//其重量分别为:4,5,6,2,2
//价值为:6,4,5,3,6
//定义价值矩阵,横坐标为背包的容量
int[][] valueMartix = new int[6][11];
//为了方便后面的取值,将没有意义的行列置0,这样取值就不必考虑行列为0的情况
for (int a = 0; a < 11; a++) {
valueMartix[0][a] = 0;
}
for (int a1 = 0; a1 < 5; a1++) {
valueMartix[a1][0] = 0;
}
//定义物品的重量数组
int[] weight = {0, 4, 5, 6, 2, 2};
//定义价值数组
int[] value = {0, 6, 4, 5, 3, 6};
int capacity = 10;
//开始填写价值矩阵
int i = 1;
int j = 1;
while (j <= capacity) {
for (; i < weight.length; i++) {
if (j < weight[i]) {
valueMartix[i][j] = valueMartix[i - 1][j];
} else {
valueMartix[i][j] = max(valueMartix[i - 1][j], valueMartix[i - 1][j - weight[i]] + value[i]);
}
}
i = 1;
j++;
}
//输出最佳方案对应的最大价值
System.out.println(valueMartix[5][10]);
}
public static int max(int v1, int v2) {
if (v1 >= v2) {
return v1;
} else {
return v2;
}
}
}
总代码实现
再进一步的拓展:
/**
* 最终版本,删减了一些不必要的注释
* 使用动态规划方法解决01背包问题
* 自底向上
*/
public class finalDemo02 {
public static void main(String[] args) {
//定义货物的数量
int goodsNumber;
//定义背包的容量
int capacity;
//输入货物的数量和背包的总容量
Scanner sc = new Scanner(System.in);
System.out.print("请输入货物的数量");
goodsNumber = sc.nextInt();
System.out.print("请输入背包的总容量");
capacity = sc.nextInt();
//定义矩阵
//定义价值矩阵,横坐标为背包的容量
int[][] valueMartix = new int[goodsNumber + 1][capacity + 1];
//为了方便后面的取值,将没有意义的行列置0,这样取值就不必考虑行列为0的情况
for (int a = 0; a < capacity; a++) {
valueMartix[0][a] = 0;
}
for (int a1 = 0; a1 < goodsNumber; a1++) {
valueMartix[a1][0] = 0;
}
//定义重量数组
List weight = new ArrayList();
//定义价值数组
List value = new ArrayList();
//将首为置为0
weight.add(0);
value.add(0);
//用户输入货物的重量和其对应的价值
for (int goods = 1; goods <= goodsNumber; goods++) {
//输入重量
weight.add(sc.nextInt());
//输入价值
value.add(sc.nextInt());
}
//开始填写价值矩阵
int i = 1;
int j = 1;
while (j <= capacity) {
for (; i < weight.size(); i++) {
if (j < (Integer) weight.get(i)) {
valueMartix[i][j] = valueMartix[i - 1][j];
} else {
valueMartix[i][j] = max(valueMartix[i - 1][j], valueMartix[i - 1][j - (Integer) weight.get(i)] + (Integer) value.get(i));
}
}
i = 1;
j++;
}
myPrint(valueMartix,goodsNumber,capacity);
}
//比较换与不换两种情况下的价值,返回价值高的数字
public static int max(int v1, int v2) {
if (v1 >= v2) {
return v1;
} else {
return v2;
}
}
//输出最佳方案能得到的最大价值
public static void myPrint(int[][] valueMartix,int goodsNumber,int capacity){
System.out.println(valueMartix[goodsNumber][capacity]);
}
}