完全背包问题
文章目录
一、完全背包问题和01背包问题的区别
0/1 背包问题(0/1 Knapsack Problem)
-
物品的选择限制:
- 在 0/1 背包问题中,每种物品要么被选择放入背包,要么不被选择放入,即每个物品最多只能选一次(0表示不选,1表示选)。
-
限制条件:
- 0/1 背包问题通常具有限制条件,其中包括一个固定的背包容量限制(背包的最大容量),通常用一个正整数 W 表示。
- 问题的目标是选择物品,使得它们的总重量不超过背包容量限制 W,同时总价值最大化。
-
动态规划状态定义:
- 在 0/1 背包问题中,通常使用一个二维数组
dp[i][j]
来表示前 i 个物品在背包容量为 j 时的最大价值。 - 状态转移方程通常如下:
dp[i][j] = max(dp[i-1][j], dp[i-1][j - weight[i]] + value[i])
,表示在考虑第 i 个物品时,可以选择放入或不放入背包,取两者中的最大值。
- 在 0/1 背包问题中,通常使用一个二维数组
完全背包问题(Complete Knapsack Problem)
-
物品的选择限制:
- 在完全背包问题中,每种物品可以选择放入背包的次数是无限的,即每个物品可以被选择放入背包多次。
-
限制条件:
- 完全背包问题同样具有一个固定的背包容量限制 W,但每个物品可以选择放入多次,不像 0/1 背包中每个物品只能选一次。
-
动态规划状态定义:
- 在完全背包问题中,同样使用一个二维数组
dp[i][j]
来表示前 i 个物品在背包容量为 j 时的最大价值。 - 不同的是,状态转移方程稍有不同,通常如下:
dp[i][j] = max(dp[i-1][j], dp[i][j - weight[i]] + value[i])
,表示在考虑第 i 个物品时,可以选择放入或不放入背包,而且可以多次选择放入物品 i,取两者中的最大值。
- 在完全背包问题中,同样使用一个二维数组
总结区别
- 0/1 背包问题中,每个物品要么选中要么不选中,而在完全背包问题中,每个物品可以选择放入多次。
- 0/1 背包问题的限制条件中,每个物品只能被选中一次,而完全背包问题中,每个物品可以被选中多次。
- 解决 0/1 背包问题和完全背包问题的动态规划算法的状态转移方程略有不同,主要是在计算放入物品时的逻辑上有差异。
二、本例子中背包问题的描述
在这个例子中,我们有三个物品,它们的重量分别为1、3、4,对应的价值为15、20、30。背包的容量限制为4,每个物品可以选择放入多次。
三、动态规划思路
首先,我们需要定义一个状态,以便表示问题的子问题和最优解。在这个问题中,我们可以使用二维数组 dp[i][j]
来表示前i个物品中,在背包容量为j时所能获得的最大价值。其中,i表示物品的数量,j表示背包的容量。
我们需要初始化状态数组 dp
,和0/1背包初始化方式不同的是,完全背包中只有 dp[0][j]
应该初始化为0,因为没有物品可供选择;第一行是不需要进行初始化的,因为第一个物品可以放入多次,遍历后面的物品时使用相同的步骤即可。
接下来,我们需要找到状态之间的转移关系,即如何从子问题的最优解推导出原问题的最优解。在这个问题中状态转移方程如下:
- 如果当前背包容量
j
小于物品i
的重量weight[i]
,则无法将物品i
放入背包,此时dp[i][j]
等于上一个状态dp[i-1][j]
的值。 - 如果
j
大于等于weight[i]
,我们可以选择放入物品i
或不放入物品i
。因此,dp[i][j]
取两者中的最大值:- 不放入物品
i
,即dp[i-1][j]
。 - 放入物品
i
,即dp[i][j - weight[i]] + value[i]
,这里的dp[i][j - weight[i]]
表示在考虑物品i
时,剩余背包容量j - weight[i]
的最优解,加上物品i
的价值value[i]
(0/1背包中,因为不能使用同一种物品,所以是dp[i-1][j-weight[i]]+ value[i]
)。
- 不放入物品
通过上述状态转移方程,我们可以通过双重循环遍历所有的子问题,从而填充状态表格 dp
。外层循环遍历物品 i
,内层循环遍历背包容量 j
,根据状态转移方程更新 dp[i][j]
。
最后,我们可以通过 dp[weight.size() - 1][bagweight]
来获取问题的最优解,即在考虑所有物品并且背包容量为 bagweight
时的最大价值。
四、二维数组代码
-
首先,定义三个关键数组:
weight
:物品的重量数组。value
:物品的价值数组。dp
:动态规划状态表格,dp[i][j]
表示前i个物品在背包容量为j时的最大价值。
-
在
for
循环中,遍历每一个物品i
,以及每一个可能的背包容量j
,遍历顺序可以更换。 -
在内部的
if
-else
条件语句中,你根据状态转移方程更新dp[i][j]
:- 如果
j
小于物品i
的重量weight[i]
,则无法放入物品i
,所以dp[i][j]
等于上一个状态dp[i-1][j]
(表示不放入物品i
)。 - 否则,选择放入物品
i
或不放入物品i
,取两者中的最大值,这样就保证了在背包容量为j
时的最大价值。
#include <iostream> #include <vector> using namespace std; void completePack() { vector<int> weight = { 1, 3, 4 }; vector<int> value = { 15, 20, 30 }; int bagweight = 4; vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0)); for (int i = 0; i < weight.size(); i++) { for (int j = 0; j <= bagweight; j++) { if (j < weight[i]) { // 当前背包容量不足以放入物品 i dp[i][j] = (i > 0) ? dp[i - 1][j] : 0; } else { // 选择放入物品 i 或者不放入 dp[i][j] = max((i > 0) ? dp[i - 1][j] : 0, dp[i][j - weight[i]] + value[i]); } } } // dp[weight.size() - 1][bagweight] 即为问题的最优解 cout << "Maximum value: " << dp[weight.size() - 1][bagweight] << endl; } int main() { completePack(); return 0; }
- 如果
五、一维数组
void CompletePack() {
vector<int> weight = {1, 3, 4};
vector<int> value = {15, 20, 30};
int bagWeight = 4;
vector<int> dp(bagWeight + 1, 0);
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = weight[i]; j <= bagWeight; j++) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
cout << dp[bagWeight] << endl;
}
int main() {
CompletePack();
}
void CompletePack() {
vector<int> weight = {1, 3, 4};
vector<int> value = {15, 20, 30};
int bagWeight = 4;
vector<int> dp(bagWeight + 1, 0);
for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量,注意这里j不可以直接初始化成weight[i],因为第一个物品就有可能大于bagWeight
for(int i = 0; i < weight.size(); i++) { // 遍历物品
if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
cout << dp[bagWeight] << endl;
}
int main() {
CompletePack();
}