前言
更新至01背包、完全背包。。。。
一、01背包
基础版(二维)
题目:
给定N个物品,每个物品有一个重量W和一个价值V。你有一个能装M重量的背包.问怎么装使得所装物品的总价值最大。每个物品只有一个。
①首先,创建二维数组bag01,int[n + 1][w + 1]存储更新最优解
int[] bag01=new int[n + 1][w + 1];
int[] values = new int[n + 1];//存储各个物品的价值
int[] costs = new int[n + 1];//存储物品i的重量
bag[i][j]代表有前i个物品,背包容量为j时可装下的最大总价值(这里及以下所指的容量即”背包可装的重量“)
②初始化bag01[0][k]=0,k∈[0,M],即没有物品可装,总价值为0。
bag01[k][0]=0,k∈[0,N],背包容量为0,也装不了东西,所以总价值为0.
因为java数组创建默认值为0,所以可以不必再进行初始化赋值。
③bag01[i][j] :容量为j (如果j>=costs[i],即能装下第i件物品的情况下)时取不取第i件物品。第i在取第i件物品与不取第i件物品中选中最大值
1> 不取第i件物品,即dp[i−1][j];最优解等价于没有第i件物品(反正也不取),容量为j的情况。
2> 取第i件物品(前提是能装下),那么只剩下j - costs[i]的容量来取前i-1件物品,前i-1件物品取得的最优解等价于bag01[i - 1][j - costs[i]],然后加上第i件物品的价值得到总价值:bag01[i - 1][j - costs[i]] + values[i]。
bag01[i][j] = Math.max(bag01[i - 1][j - costs[i]] + values[i], bag01[i - 1][j]);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= w; j++) {
if (j >= costs[i])
bag01[i][j] = Math.max(bag01[i - 1][j - costs[i]] + values[i], bag01[i - 1][j]);
else
bag01[i][j] = bag01[i - 1][j];
}
}
一维优化版
我们可以看二维递推式:
bag01[i][j]=Math.max(bag01[i-1][j-costs[i]]+values[i],bag01[i-1][j]);
由二维递推式知:bag01[i][j]的更新实际上只与bag01[i-1]有关,01背包求解的过程是一个填二维表的过程,第i行第j列的结果是bag01[i][j],第i行的结果只与第i-1行有关,所以只需要一维的数据即可完成表 的更新。
数组bag[j]:容量为j时可装得物品的最大价值。
- ①对于j<costs[i],bag01[i][j] = bag01[i - 1][j];即bag[j]不变,所以不用遍历更新。
- ②对于j>=costs[i].bag[j]更新后 =Max{ bag[j-1]更新前 + bag[j-costs[i]]更新前+value[i] } ,
- 第二层循环得逆序,才能拿到更新前的bag[j-1]和bag[j-costs[i]]。同时更新的bag[j],j将大于后面更新的,所以在后面更新的背包值在更新时也不会用到更新后的bag[j]。
// bag[j]=Max{ bag[j-1],bag[j-cost[i]]+values[i] }
for(int i=1;i<n;i++) {
for(int j=w;j>=costs[i];j--) {
bag[j] = Math.max(bag[j - costs[i]] + values[i], bag[j]);
}
}
完整代码
基础版
import java.util.*;
public class Bag01 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int w = sc.nextInt();
int[] values = new int[n + 1];
int[] costs = new int[n + 1];
for (int i = 1; i <= n; i++) {
values[i] = sc.nextInt();
}
for (int i = 1; i <= n; i++) {
costs[i] = sc.nextInt();
}
int[][] bag01 = new int[n + 1][w + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= w; j++) {
if (j >= costs[i])
bag01[i][j] = Math.max(bag01[i - 1][j - costs[i]] + values[i], bag01[i - 1][j]);
else
bag01[i][j] = bag01[i - 1][j];
}
}
System.out.println(bag01[n][w]);
}
}
一维优化版:
import java.util.*;
public class Bag01 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int w = sc.nextInt();
int[] values = new int[n + 1];
int[] costs = new int[n + 1];
for (int i = 1; i <= n; i++) {
costs[i] = sc.nextInt();
values[i] = sc.nextInt();
}
int[] bag = new int[w + 1];
Arrays.fill(bag, 0);
for (int i = 1; i <= n; i++)
for (int j = w; j >=costs[i]; j--)
bag[j] = Math.max(bag[j - costs[i]] + values[i], bag[j]);
System.out.println(bag[w]);
}
}
二、完全背包
题目:
设有n种物品,每种物品有一个重量及一个价值。但每种物品的数量是无限的,同时有一个背包,最大载重量为M,今从n种物品中选取若干件(同一种物品可以多次选取),使其重量的和小于等于M,而价值的和为最大。
总结:与01背包区别只有每种物品的个数是无限的
所以
基础版
递推公式
dp[i][j] =max{ dp[i-1][j] , dp[i][j-costs[i-1]]+values[i-1]}
- 不装入第i种物品,
即dp[i−1][j],同01背包; - 装入(或再次装入)第i种物品,
此时和01背包不太一样,
因为每种物品有无限个(但注意书包限重是有限的),
所以此时不应该转移到dp[i−1][j−w[i]]而应该转移到dp[i][j−w[i]],
即装入第i种商品后还可以再继续装入第i种商品。
注意:算法一个物品可能不止被取2次,当容量j足够大,而物品i是性价比足够高的,那么会在的dp[i][j] 中被选中,之前也会在dp[i][j-w[i]]中被选中,在dp[i][j-2w[i]]被选中…
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= w; j++) {
if (j >= costs[i])
bag01[i][j] = Math.max(bag01[i][j - costs[i]] + values[i], bag01[i - 1][j]);
else
bag01[i][j] = bag01[i - 1][j];
}
}
优化版
基础版递推公式
dp[i][j] =max{ dp[i-1][j] , dp[i][j-costs[i-1]]+values[i-1]}
我们有优化版递推公式
dp[j] = Math.max(dp[j],dp[j-costs[i]]+values[i]);
注意基础版第i行的数据的产生不只用到了第i-1行,还遇到了本次循环更新后的dp[j-costs[i-1],第二层循环要顺序执行
原因如下:
dp[j] *=max{ dp[j] , dp[j-costs[i-1]]+values[i-1] * },注:dp[j] *是指本轮循环中更新后的dp[j]值,用dp[i]代表未更新前的值
在产生dp[j] * 要用到dp[j-costs[i-1]] *,而不是dp[j-costs[i-1]] ,所以必须之前就先产生dp[j-costs[i-1]] *
又因为j>j-costs[i-1],所以如果产生dp[j-costs[i-1]],按下面的代码不会更新dp[j];所以不会影响dp[j] *的产生
完整代码
基础版
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int w = sc.nextInt();
int n = sc.nextInt();
int[] values = new int[n + 1];
int[] costs = new int[n + 1];
for (int i = 1; i <= n; i++) {
costs[i] = sc.nextInt();
values[i] = sc.nextInt();
}
int[][] bag01 = new int[n + 1][w + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= w; j++) {
if (j >= costs[i])
bag01[i][j] = Math.max(bag01[i][j - costs[i]] + values[i], bag01[i - 1][j]);
else
bag01[i][j] = bag01[i - 1][j];
}
}
System.out.println("max="+bag01[n][w]);
}
}
优化版
import java.util.*;
public class Bag_Complete {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int M = scanner.nextInt();
int N = scanner.nextInt();
int[] values = new int[N+1];
int[] costs = new int[M+1];
for (int i = 1; i <= N; i++) {
costs[i] = scanner.nextInt();
values[i] = scanner.nextInt();
}
int[] dp = new int[M+1];
for(int i = 1; i <=N; i++) {
for(int j = costs[i]; j <=M;j++){
dp[j] = Math.max(dp[j],dp[j-costs[i]]+values[i]);
}
}
System.out.println(dp[M]);
}
}