背包类型
重点掌握01背包和完全背包,尤其是01背包
01背包问题 二维
前提:通过图形帮助理解递推公式和想象一维时的情况
-
确定dp数组以及下标的含义
dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。 -
确定递推公式
从两个方向推出来dp[i][j],分为不放物品i和放物品i去考虑:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]) -
dp数组如何初始化
for (int j = 0 ; j < weight[0]; j++) { // 当然这一步,如果把dp数组预先初始化为0了,这一步就可以省略,但很多同学应该没有想清楚这一点。
dp[0][j] = 0;
}
// 正序遍历
for (int j = weight[0]; j <= bagweight; j++) {
dp[0][j] = value[0];
}
- 确定遍历顺序
此时由于二维数组,已事先进行了dp数组初始化,则不管是先遍历物品还是遍历背包容量都不会影响dp[i][j]的值
模板
def test_2_wei_bag_problem1(bag_size, weight, value) -> int:
rows, cols = len(weight), bag_size + 1
dp = [[0 for _ in range(cols)] for _ in range(rows)]
# 初始化dp数组.
for i in range(rows):
dp[i][0] = 0
first_item_weight, first_item_value = weight[0], value[0]
for j in range(1, cols):
if first_item_weight <= j:
dp[0][j] = first_item_value
# 更新dp数组: 先遍历物品, 再遍历背包.
for i in range(1, len(weight)):
cur_weight, cur_val = weight[i], value[i]
for j in range(1, cols):
if cur_weight > j: # 说明背包装不下当前物品.
dp[i][j] = dp[i - 1][j] # 所以不装当前物品.
else:
# 定义dp数组: dp[i][j] 前i个物品里,放进容量为j的背包,价值总和最大是多少。
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - cur_weight]+ cur_val)
print(dp)
if __name__ == "__main__":
bag_size = 4
weight = [1, 3, 4]
value = [15, 20, 30]
test_2_wei_bag_problem1(bag_size, weight, value)
01背包问题(滚动数组)
主要讲dp定义+递推公式+遍历顺序
-
dp定义
dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j] -
递推公式
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) -
遍历顺序
必须遵从先遍历物品,再倒序遍历背包容量!
for(int i = 0; i < weight.size(); i++) { // 遍历物品
for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
按上述顺序(表格内为遍历顺序):
容量/物品 | 0 | 1 | 2 |
---|---|---|---|
0 | 3 | 6 | |
1 | 2 | 5 | |
2 | 1 | 4(左上角) |
若为正序(表格内为遍历顺序):
容量/物品 | 0 | 1 | 2 |
---|---|---|---|
0 | 1 | 4 | |
1 | 2 | 5(可能会考虑两次物品1) | |
2 | 3 |
二刷补充
关于遍历顺序:只看 递推公式 如何推出dp得每一个元素
- 如dp从 二维 到 一维,则在保证不覆盖原来值的情况下定遍历顺序
例:本题从左上角推出dp[i][j],于是选择对背包容量倒序遍历,则能保证新的dp[j]能与原来的dp[j]进行比较;
必须先遍历物品,再遍历背包,因为如相反,会导致dp[j - weight[i]] 此时没有更新仍为0