一、01背包
内容:有n件物品和容量为m的背包 给出i件物品的重量以及价值 求解让装入背包的物品重量不超过背包容量 且价值最大
特点:每个物品只有一件供你选择放还是不放
1. 二维解法
设f[i][j]表示前i件物品 总重量不超过j的最大价值 可得出状态转移方程 :
f[i][j]=max{f[i-1][j-w[i]]+v[i],f[i-1][j]}
w[i]:重量数组,v[i]:价值数组
代码如下:
// 0,1背包:二维法
public static int bag1(int W, int[] w, int[] v) {
int n = w.length - 1;// 第一个值,不算
int[][] f = new int[n + 1][W + 1];
for (int i = 1; i <= n; i++)
for (int j = W; j >= w[i]; j--)
f[i][j] = Math.max(f[i - 1][j], f[i - 1][j - w[i]] + v[i]);
return f[n][W]; // 最优解
}
缺点:数据大时,无法处理。
2.一维解法
用一维数组代替二维数组就解决了二维数组的问题
设f[j]表示重量不超过j公斤的最大价值 可得出状态转移方程 :
f[j]=max{f[j],f[j−w[i]]+v[i]}
代码如下:
// 0,1背包:一维法
public static int bag2(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--)
f[j] = Math.max(f[j], f[j - w[i]] + v[i]);
return f[W]; // 最优解
注意:这里第二个for循环为什么从大到小?
因为按正常二维解法,这个 f[i][j]=max{f[i-1][j-w[i]]+v[i],f[i-1][j]} 是要跟上一层i-1比较的,如果一维也用从小到大,不能保证上一层没有遍历过(即不包含vi,每个只能用一次),这样就乱了,因此要从大到小顺序遍历。
二、完全背包
内容:有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]; // 最优解
}
三、多重背包
内容:有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]; // 最优解
}
二进制优化做法:
首先,可以把它转化为一个01背包的问题。每个物品有s件,我们可以把它差分成s份,每份物品当做不同的个体,即只能选一次,这就转化为了01背包物品,但是这样的话,复杂度还是很大。
继续优化,一个物品的数量是s的话,只要把s拆分成一些数字,使它们能够表示出1-s中任意一个数字,就可以,没必要把它拆成s个1。
那么这样的数字最少需要多少个呢?最少需要log(s)个,向上取整。
比如7,它最少需要3个数字来表示:
即 1(2^0=1 ), 2(2^1=2), 4(2^2=4)。原因:每个数字有2种可能选或不选,那么可以表示的不同数字个数就是 2 * 2 * 2 = 8。但是还需要注意一个问题,就是有些数字可能能够表示出来一些大于s的数字,但是这件物品最多只有s件,那么就需要特殊处理一下最后一个数。
比如10,若用1,2, 4, 8表示,可能会表示出来大于10的数字,例如:4+8=12。那么如果最后一个数字加上前面数的总和会大于s,就将它替换为剩下的物品个数,即将8替换为3,这时正好能表示出1-s所有的数,-> 1, 2,4可以表示7以内的所有数,这些数加上3就可以表示10以内的所有数啦。
——参考背包九讲
// 多重背包:一维法-二进制优化
public static int bag4(int W, int[] w, int[] v, int[] num) {
int n = w.length;
int[] f = new int[W + 1];
int[] vv = new int[n*W];
int[] ww = new int[n*W];
// 复制w和v
for(int i=0;i0)
vv[k]=v[i]*s,ww[k++]=w[i]*s;
}
for (int i = 1; i <= k; i++) //01背包
for (int j = W; j >= ww[i]; j--)
f[j] = Math.max(f[j], f[j - ww[i]] + vv[i]);
return f[W]; // 最优解
}
四、背包问题应用在,数组中找固定和的问题
1. 判断是否有和为100的组合
public static boolean findSum100(int[] a) {
boolean[] dp = new boolean[101];
dp[a[0]] = true;
for (int i = 1; i < a.length; i++) {
for (int j = 100; j >= a[i]; j--) {
if (dp[j - a[i]]) {
dp[j] = true;
}
}
if (dp[100]) {
return true;
}
}
return false;
}
2.查找和为100的所有组合
方法一:DFS(2^n)
public static void dfs(int current, int sum, int[] questions, ArrayList path,
HashSet> result) {
if (sum == 100) {
result.add(new ArrayList<>(path));
return;
}
if (sum > 100) {
return;
}
for (int i = current; i < questions.length; i++) {
path.add(questions[i]);
dfs(i + 1, sum + questions[i], questions, path, result);
path.remove(path.size() - 1);
}
}
方法二:动态规划 O(n^2)
// 打印和为n的组合,动规法,O(n^2)
public static List> findSums(int[] a, int n) {
boolean[] dp = new boolean[n + 1];
List>[] list = new ArrayList[n + 1];
for (int i = 0; i < list.length; i++) {
list[i] = new ArrayList<>();
}
List
更多打印的问题:
打印和为sum的组合,动规法+DFS+迭代法 - CSDN博客 :https://blog.csdn.net/qq_19446965/article/details/81775702