1.0-1背包问题
有 n 个物体,重量分别为 wi,价值为 vi,在总重量不超过容量 C 的情况下让总价值最高,
但不允许只取走部分物体,要拿走只能整个拿走。
从01背包开始就涉及到了动态规划的知识,动态规划的本质就是递推。
先打表进行观察:
表中 dp[ i ] [ j ] 表示容量为 j,可以在 0 号物品到 i 号物品中进行任意组合,这种情况下的最大值。
第一行与第一列都不用说,一眼就可以直接得到答案。
剩下的部分怎么进行选择呢?
比如 dp[2] [4] = 6,我们已经知道了 dp[1] [4] = 5,说明在没有考虑 2 号物品的时候最大价值为 5。
现在加入了 2 号物品需要考虑,(3,4)重量为 3,价值为 4。
那么必须要选择 2 号物品才有可能超过之前计算不考虑 2 号物品的最大价值 5。
而选择 2 号物品的最大价值为多少呢?
应该是 dp[2-1] [4-3] + 4 = dp[1] [1] + 4 = 2 + 4 = 6,
dp[1] [1] 为不考虑 2 号物品,容量为 1 的最大价值,这样正好可以装下重量为 3 的 2 号物品且价值最大。
因此我们只要比较加入 2 号物品的最大价值与不加入 2 号物品的最大价值即可,
即 max(dp[1] [1] + 4, dp[1] [4])。
一般情况:dp[i] [j] = max( dp[i-1] [j-w] + v, dp[i-1] [j] )。第i件物品的重量为w,价值为v
所以处理到最后 dp[2] [5] 就是我们要求的答案。
/**
* 0-1背包问题
* @param V 背包容量
* @param N 物品种类
* @param weight 物品重量
* @param value 物品价值
* @return
*/
public static String ZeroOnePack(int V,int N,int[] weight,int[] value){
//初始化动态规划数组
int[][] dp = new int[N+1][V+1];
//为了便于理解,将dp[i][0]和dp[0][j]均置为0,从1开始计算
for(int i=1;i<N+1;i++){
for(int j=1;j<V+1;j++){
//如果第i件物品的重量大于背包容量j,则不装入背包
//由于weight和value数组下标都是从0开始,故注意第i个物品的重量为weight[i-1],价值为value[i-1]
if(weight[i-1] > j)
dp[i][j] = dp[i-1][j];
else
dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-weight[i-1]]+value[i-1]);
}
}
//则容量为V的背包能够装入物品的最大值为
int maxValue = dp[N][V];
//逆推找出装入背包的所有商品的编号
int j=V;
String numStr="";
for(int i=N;i>0;i--){
//若果dp[i][j]>dp[i-1][j],这说明第i件物品是放入背包的
if(dp[i][j]>dp[i-1][j]){
numStr = i+" "+numStr;
j=j-weight[i-1];
}
if(j==0)
break;
}
return numStr;
}
0-1背包的优化解法:
/**
* 0-1背包的优化解法
* 思路:
* 只用一个一维数组记录状态,dp[i]表示容量为i的背包所能装入物品的最大价值
* 用逆序来实现
*/
public static int ZeroOnePack2(int V,int N,int[] weight,int[] value){
//动态规划
int[] dp = new int[V+1];
for(int i=1;i<N+1;i++){
//逆序实现
for(int j=V;j>=weight[i-1];j--){
dp[j] = Math.max(dp[j-weight[i-1]]+value[i-1],dp[j]);
}
}
return dp[V];
}
2.部分背包问题
有 n 个物体,重量分别为 wi,价值为 vi,在总重量不超过容量 C 的情况下让总价值最高,
每个物体可以只取走一部分,若取走部分物体则价值也会等比例减少。
这种背包问题也比较简单,可以定义一个变量 rate 表示各个物体的性价比,
性价比最高的物体先拿走,如果可以整个物体都拿走则直接拿走,若不能整个拿走就拿走部分。
import java.util.Arrays;
import java.util.Comparator;
public class GreedyAlgorithm {
/**
* 普通背包问题,可以将物品的一部分放入背包,比如黄金喽
* 简单贪心:将物品的单位价值降序排列
* @param s 物品个数,与物品重量,价值一一对应
* @param w 物品重量
* @param v 物品价值
* @param p 背包大小
* @return 返回最大价值
*/
public static double normalPackage(int s, double[] w, double[] v, double p){
double[][] vperw = new double[s][2];//[0]单位重量下的价值,[1]编号
for (int i = 0; i < s; i++) {
vperw[i][0] = v[i] / w[i];
vperw[i][1] = i;
}
//按照收益比排序
Arrays.sort(vperw, new Comparator<double[]>() {
@Override public int compare(double[] o1, double[] o2) {
return o2[0]-o1[0]>0?1:-1;
}
});
double tmpW = 0;//临时重量
double result = 0;//价值
for (int i = 0; i < s; i++) {
int b = (int) vperw[i][1];//当前物品的id
double thisW = w[b];
if (thisW < p-tmpW){//如果可以放下完整的物品
result += v[b];
tmpW += w[b];
System.out.println("物品 " + b +" 放入比例 1 获得收益 " + v[b]);
}else {
double canW = p-tmpW;//可以放的重量
result += vperw[i][0] * canW;
System.out.println("物品 " + b +" 放入比例 "+ canW/thisW +" 获得收益 " + vperw[i][0] * canW);
break;
}
}
return result;
}
public static void main(String[] args) {
double[] w = new double[]{18,15,10};
double[] v = new double[]{25,24,15};
double result = normalPackage(w.length, w, v, 20);
System.out.println("获取最大收益为:" + result);
}
}
3.完全背包问题
内容:有n件物品和容量为m的背包 给出i件物品的重量以及价值 求解让装入背包的物品重量不超过背包容量 且价值最大
特点: 每个物品可以无限选用
有二维和一维两种,但二维的有一定局限 所以只介绍一维方法
设f[j]表示重量不超过j公斤的最大价值 可得出状态转移方程 :
f[j]=max{f[j],f[j−w[i]]+v[i]}
区别:0,1背包是从大到小遍历,完全背包是从小到大遍历
代码如下:
// 完全背包:一维法-倒序
public static int bag3(int W, int[] w, int[] v) {
int n = w.length - 1;// 第一个值,不算
int[] f = new int[W + 1];
for (int i = 1; i <= n; i++)
for (int j = W; j >= w[i]; j--)
for (int k = 0; j - k*w[i] >=0; k++)
f[j] = Math.max(f[j], f[j - k*w[i]] +k* v[i]);
return f[W]; // 最优解
}
优化: 转换成从小到大,因为每个物品可以用无限次,只要不准超过重量,就不在乎之前i(上一层)是否用过。
// 完全背包:一维法-正序
public static int bag3(int W, int[] w, int[] v) {
int n = w.length - 1;// 第一个值,不算
int[] f = new int[W + 1];
for (int i = 1; i <= n; i++)
for (int j = w[i]; j <= W; j++)
f[j] = Math.max(f[j], f[j - w[i]] + v[i]);
return f[W]; // 最优解
}
4.多重背包
内容:有n件物品和容量为m的背包 给出i件物品的重量以及价值 还有数量 求解让装入背包的物品重量不超过背包容量 且价值最大
特点:每个物品都有了一定的数量
设f[j]表示重量不超过j公斤的最大价值 可得出状态转移方程 :
f[j]=max{f[j],f[j−k∗w[i]]+k∗v[i]}
代码如下:
// 多重背包:一维法
public static int bag4(int W, int[] w, int[] v, int[] num) {
int n = w.length - 1;// 第一个值,不算
int[] f = new int[W + 1];
for (int i = 1; i <= n; i++)
for (int j = W; j >= w[i]; j--)
for (int k = 0; k <= num[i] && j - k * w[i] >= 0; k++) {
f[j] = Math.max(f[j], f[j - k * w[i]] + k * v[i]);
}
return f[W]; // 最优解
}