小白学习动态规划:完全背包
参考博客:
- https://blog.csdn.net/qq_38984851/article/details/81133840
- https://blog.csdn.net/na_beginning/article/details/62884939
完全背包与零一背包类似,不同的是每种物品有无限件,意味着选完一件物品放进背包后,有可能会继续选择同一件物品放入背包。
经典例题
物品 | A | B | C | D | E |
---|---|---|---|---|---|
Wealth | 2 | 4 | 7 | 8 | 12 |
Cost | 2 | 3 | 5 | 6 | 8 |
初步解题思路
定义状态与01背包相同,下标i代表前i件物品,下标j代表背包的总容量为j
所以,在01背包的基础上改变一下
掌握完全背包问题前必须清楚01背包的状态转移方程:
而完全背包的状态转移方程为:
需要乘上系数k的原因是如果背包的容量大于物品A的体积,那么放入一件A之后,有可能背包剩下的容量还可以继续放入A物品,每次放入都要放入尽可能多的物品。
例如:
背包容量V = 9,物品A的体积为Cost[A] = 2,那么拿1件A放入背包后,背包容量 = 10 - 2 = 8,还可以继续放A,直到背包容量为剩下1时(此时放了4件A),无法继续放入A了才结束。
代码实现:
/**
* @author Zeng
* @date 2020/2/12 22:34
*/
public class Solution {
public static int maxWealth(int[] cost, int[] wealth, int N, int V){
//给dp table加一行和一列避免复杂的初始化问题
int[][] dp = new int[N+1][V+1];
//初始化
for (int i = 0; i < dp.length; i++){
dp[i][0] = 0;
}
for (int i = 0; i < dp[0].length; i++){
dp[0][i] = 0;
}
for (int i = 1; i < N+1; i++){
for (int j = 1; j < V+1; j++){
//假设不选第i件
dp[i][j] = dp[i-1][j];
for (int k = 1; k*cost[i-1] <= j; k++){
//可以选k件
dp[i][j] = Math.max(dp[i][j], dp[i-1][j - k*cost[i-1]] + k*wealth[i-1]);
}
}
}
//观察dp table值
for (int i = 0; i < N+1; i++){
for (int j = 0; j < V+1; j++){
System.out.print(dp[i][j]+" ");
}
System.out.println();
}
return dp[N][V];
}
public static void main(String[] args) {
int[] cost = new int[]{2, 3, 4, 7};
int[] wealth = new int[]{1, 3, 5, 9};
int N = 4;
int V = 10;
int i = maxWealth(cost, wealth, N, V);
System.out.println(i);
}
}
输出结果:
0 0 0 0 0 0 0 0 0 0 0
0 0 2 2 4 4 6 6 8 8 10
0 0 2 4 4 6 8 8 10 12 12
0 0 2 4 4 7 8 9 11 12 14
0 0 2 4 4 7 8 9 11 12 14
0 0 2 4 4 7 8 9 12 12 14
14
此算法的空间复杂度为O(N²),共需要求解O(NV)个状态,求解每个状态的时间不是常数级了,因为每个物品并不是只有拿和不拿两种情况,而是拿k件(k≥1)或不拿的多种情况,求dp[i][j]所需要的时间是O(j/Cost[i]),所以总的时间复杂度为O(NVΣ(j/Cost[i]))。在这种复杂度较高的情况下一般是不能AC的。所以要优化时间复杂度。尝试将其转化为01背包问题。
优化解题思路(转化为01背包问题)
优化前的核心代码
for (int i = 1; i < N+1; i++){
for (int j = 1; j < V+1; j++){
//假设不选第i件
dp[i][j] = dp[i-1][j];
for (int k = 1; k*cost[i-1] <= j; k++){
//可以选k件
dp[i][j] = Math.max(dp[i][j], dp[i-1][j - k*cost[i-1]] + k*wealth[i-1]);
}
}
}
思路1
可以在每一个物品可以放多少个时,直接求出可以放入的最大值k,即每个物品要么不能放进去,要么放k件,等价于01背包中要么不能放入物品,要么可以放入1件物品。
优化代码:
for (int i = 1; i < N+1; i++){
for (int j = 1; j < V+1; j++){
//假设不选第i件
dp[i][j] = dp[i-1][j];
//假设能放k件
int k = j / cost[i-1];
//可以选k件
dp[i][j] = Math.max(dp[i][j], dp[i-1][j - k*cost[i-1]] + k*wealth[i-1]);
}
}
思路2
当物品可以放入时,可能还可以继续放入该物品,所以状态转移方程需要变化:
优化代码:
for (int i = 1; i < N+1; i++){
for (int j = 1; j < V+1; j++){
//假设不选第i件
dp[i][j] = dp[i-1][j];
if(cost[i-1] < j){
//可以放进去
dp[i][j] = Math.max(dp[i][j], dp[i][j - k*cost[i-1]] + k*wealth[i-1]);
}
}
}
思路3(二进制思想)(还没看懂)
时间有限,能力有限,如有错误欢迎指正!乐意与大家交流!