动态规划——01背包问题理论
理论1-用二维数组实现
背包问题,有一种写法, 是使用二维数组,即dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
确定递推公式
有两个方向推出来dp[i][j],
若当前背包容量j<weight[i] (只放一个物品i都不够),此时只能从0-(i-1)物品中选取,所以等于dp[i-1][j];
若当前背包容量j>=weight[i]:可以选择放入物品[i],也可以不放
放入物品[i]:当前价值dp[i][j]=val[i] + dp[i-1][ j-weight[i] ]
注:放如i的总价值等于i的价值加上剩余背包容量 j-weight[i] 下放入0-(i-1)物品获得的最大价值
不放入物品[i]:当前价值dp[i][j] = dp[i-1][j]
即:等于相同背包容量下只放入0-(i-1)物品的最大价值
这种情况下两者取最大得到dp[i][j]
遍历顺序
由递推公式知,dp[i][j]由其左上角和上方元素计算得出,所以可以先按层遍历,也可以先按列遍历。
初始化
dp[i][j]都和 dp[i-1][j] 相关,所以需要dp[0][j],若物品0的重量小于等于包容量,则dp[0][j]相应位置为物品0的价值;若物品0的重量超过包容量,则物品0不能入包,价值为0
dp[i][j]还与 dp[i-1][ j-weight[i] ] 相关,所以初始化dp[i][0],易知当物品重量均大于0,包容量为0的情况下,dp[i][0]=0
其余位置在之后的遍历过程中都会被覆盖,为初始化的简单,可将整个数组先初始化为全0,再对dp[0][j]做修改
理论2-用一维数组(滚动数组)实现
根据上面的递推公式,dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);dp[i][j]仅与上一层的dp有关,所以不用维护整个数组,可只保存上一层dp和当前层dp,进一步,可压缩至只使用一个数组dp[j]:容量为j的背包,所背的物品价值最大为dp[j]
递推公式:
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
这里右端的dp[j] 相当于 二维dp数组中的dp[i-1][j],即不放物品i
初始化:
当j<weight[i],放不进物品i,当前能背的最大价值和不放物品i相同,所以当前位置不需要修改,只用讨论j>=weight[i]的情况
设想第一行,物品0,不管怎样只能放入一个物品,dp[j]要么是0要么是val[0],而递推公式中可以取最大,所以初始化为全0即可(dp[0]一定是0)
遍历顺序:
由于需要用到之前的dp,要从后向前遍历,防止前面的dp被覆盖而不能查询到上一层的dp
注:外层是遍历物品,内层遍历背包容量,顺序不可变
理由:若外层遍历背包,内存遍历物品,因为是倒序便利的,所以背包里只放入了一个物品(第0个),不满足要求
小试牛刀
背包最大重量为4。
物品为:
重量 价值
物品0 1 15
物品1 3 20
物品2 4 30
问背包能背的物品最大价值是多少?
# 二维数组
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)
# 一维数组
def test_1_wei_bag_problem():
weight = [1, 3, 4]
value = [15, 20, 30]
bag_weight = 4
# 初始化: 全为0
dp = [0] * (bag_weight + 1)
# 先遍历物品, 再遍历背包容量
for i in range(len(weight)):
for j in range(bag_weight, weight[i] - 1, -1): # 倒序 只用修改大于等于weight[i]的j
# 递归公式
dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
print(dp)
test_1_wei_bag_problem()
面试要求
写出二维背包代码
两个for循环嵌套顺序为什么这么写,能不能调换
初始化逻辑
写出一维背包代码
两个for循环嵌套顺序能不能调换
来源:
01背包问题
01背包问题一维数组实现