背包问题有很多种,是最简单的动态规划类型。
01背包
描述: 有 N 件物品和一个容量为 V 的背包。(每种物品均只有一件)第 i 件物品 的费用是 c[i],价值是 w[i]。求解将哪些物品装入背包可使价值总和最大。
解析:N件物品可以看做是有N种备选方案,容量为V的背包可以看做是成本为V,c[i]是每种方案的成本,价值是每种方案带来的效益。那么问题就变成了,在不超过总成本V的情况下,使用哪些备选方案,可以使效益最大。
不过,这里针对的是单一物品特性,如果一个复杂系统是多个组成部分,每个部分有多种不同的解决方案,然后成本和效能不一样,就不能使用简单01背包,不过它的每个部分是一个简单01背包。
首先定义物品节点
public class Thing {
private String name;
private Integer number;
private Integer weight;
private Integer income;
... ...
}
然后代码实现为:
import java.util.*;
public class BackPackUtil {
public static Set<Thing> backPack01(List<Thing> things, Integer totalCost) {
Map<Integer, Set<Thing>> ans = new HashMap<>();
int n = things.size();
int[] f = new int[totalCost+1];
for (int i = 0; i < n; i++) {
for (int j = totalCost; j >= things.get(i).getWeight(); j--) {
if ((f[j - things.get(i).getWeight()] + things.get(i).getIncome()) > f[j]) {
f[j] = f[j - things.get(i).getWeight()] + things.get(i).getIncome();
if (!ans.containsKey(j)) {
ans.put(j, new HashSet<>());
} else {
ans.get(j).clear();
}
if (!ans.containsKey(j - things.get(i).getWeight())) {
ans.put(j - things.get(i).getWeight(), new HashSet<>());
}
ans.get(j).addAll(ans.get(j - things.get(i).getWeight()));
ans.get(j).add(things.get(i));
}
}
}
return ans.get(totalCost);
}
public static void main(String[] args) {
String[][] a = {{"01-1","1","2","3"},{"01-2","1","4","7"},{"01-3","1","8","15"},{"01-4","1","16","31"}};
List<Thing> things = new ArrayList<>();
for (String[] b : a){
Thing thing = new Thing();
thing.setName(b[0]);
thing.setNumber(Integer.parseInt(b[1]));
thing.setWeight(Integer.parseInt(b[2]));
thing.setIncome(Integer.parseInt(b[3]));
things.add(thing);
}
for (int i=10;i<30;i++){
Set<Thing> solveMethod = backPack01(things,i);
StringBuffer thingss = new StringBuffer();
int totalValue = 0;
int totalWeight = 0;
for (Thing thing : solveMethod){
thingss.append(thing.getName()).append(" ").append(thing.getWeight()).append(" ").append(thing.getIncome()).append("\n");
totalValue = totalValue + thing.getIncome();
totalWeight += thing.getWeight();
}
System.out.println("计划成本为:"+i+" 总消耗为:"+totalWeight +" 总收益为:"+totalValue);
System.out.print(thingss.toString());
}
}
}
有的人可能会说了,这个问题我用贪心算法也可以解决,思路就是计算每种物品的性价比,然后根据性价比从大到小排序放到背包中。你可以朝着这个思路尝试一下,看下会出现什么问题,如果你发现确实更快,或者你发现的问题,都可以在评论区和我交流。
我们现在来分析分析这里的算法核心
public static Set<Thing> backPack01(List<Thing> things, Integer totalCost) {
Map<Integer, Set<Thing>> ans = new HashMap<>();
int n = things.size();
int[] f = new int[totalCost+1];
for (int i = 0; i < n; i++) {
for (int j = totalCost; j >= things.get(i).getWeight(); j--) {
if ((f[j - things.get(i).getWeight()] + things.get(i).getIncome()) > f[j]) {
f[j] = f[j - things.get(i).getWeight()] + things.get(i).getIncome();
... ...
}
}
}
... ...
}
核心算法实现在这两重循环,第一重循环的意图是枚举每一个物品,第二重循环的意图是如果算上当前这种物品,确定每种不同的成本的结果。那么显而易见,最终的结果就是遍历完所有物品,消耗为totalCost的结果。
上面的main函数里面的测试代码,可以进行剪枝,直接调用一次出所有的结果。如果你感兴趣,可以尝试一下。