概述
本文是我大二算法分析实验的综合大实验,可能跟大家的比起来难度微不足道,希望能对你有帮助🤪
0/1背包
问题描述:
给定n种物品和一背包。物品i的重量是wi,其价值为vi,背包的容量为C。问应如何选择装入背包的物品,使得装入背包中物品的总价值最大?
对于一种物品,要么装入背包,要么不装。所以对于一种物品的装入状态可以取0和1.我们设物品i的装入状态为xi,xi∈ (0,1),此问题称为0-1背包问题。
1.递归求解
我们用F(n,C)表示将前n个物品放进容量为C的背包里,得到的最大的价值。
我们用自顶向下的角度来看,假如我们已经进行到了最后一步(即求解将n个物品放到背包里获得的最大价值),此时我们便有两种选择
- 不放第n个物品,此时总价值为F(n-1,C)
- 放置第n个物品,此时总价值为Vn+F(n-1,C-Wn)
两种选择中总价值最大的方案就是我们的最终方案:F(i,C)=max(F(i−1,C),v(i)+F(i−1,C−w(i)))
/**
* 递归解决
* @param w 物品的重量数组
* @param v 物品的价值数组
* @param index 当前待选择的物品索引
* @param capacity 当前背包有效容量
* @return 最大价值
*/
private static int recursion(int[] w,int[] v,int index,int capacity) {
//基准条件:如果索引无效或者容量不足,直接返回当前价值0
if(index<0||capacity<=0) {
return 0;
}
//不放第index个物品所得价值
int res = recursion(w, v, index-1, capacity);
//放第index个物品所得价值(前提是:第index个物品可以放得下)
if(w[index]<=capacity) {
res = Math.max(res, v[index]+recursion(w, v, index-1, capacity-w[index]));
}
return res;
}
public static int knapSack(int[] w, int[] v, int C) {
int size = w.length;
return solveKS(w, v, size - 1, C);
}
public static void main(String[] args){
int[] w = {
2,1,3,2};
int[] v = {
12,10,20,15};
System.out.println(knapSack(w,v,5));
}
2.记忆化搜索(自顶向下备忘录法)
我们可以将已经求得的子问题的结果保存下来,这样对子问题只会求解一次
/**
* 自顶向下备忘录法
* @param w
* @param v
* @param index
* @param capacity
* @return
*/
private static int top2buttom(int[] w,int[] v,int index,int capacity,int[][] memo) {
if(index<0||capacity<=0) {
return 0;
}
//如果此子问题已经求解过,则直接返回上次求解的结果
if(memo[index][capacity]!=0)return memo[index][capacity];
//不放第index个物品所得价值
int res = recursion(w, v, index-1, capacity);
//放第index个物品所得价值(前提是:第index个物品可以放得下)
if(w[index]<=capacity) {
res = Math.max(res, v[index]+recursion(w, v, index-1, capacity-w[index]));
}
//添加子问题的解,便于下次直接使用
memo[index][capacity] = res;
return res;
}
3.动态规划算法
通过反证法可证明此问题符合动态规划算法的最优子结构性质,可以通过动态规划来求解。了解动态规划
/**
* 动态规划(自底向上)算法
* @param w
* @param v
* @param C
* @return
*/
public static int buttom2top(int[] w,int[] v,int C) {
int size = w.length;
if(size == 0 || C<=0)return 0;
int[][] dp = new int[size][C+1];
//初始化第一行:仅考虑容量为C的背包放第0个物品的情况
for(int i=0;i<=C;i++) {
dp[0][i] = w[0]<=i? v[0]:0;//如果容量可以放,就是第一个的价值,否则放不了为0
}
//根据此逐步填充
for(int i=1;i<size;i++) {
//每个物品的循环
for(int j=0;j<=C;j++) {
dp[i][j] = dp[i-1][j];//要放此物品,说明前面的已经是最优解,根据这个最优解为基准看是增加还是不增加这个物品
if(w[i]<=j) {
//如果容量够叫放进去试试看
dp[i][j] = Math.