问题描述
假设我有一个背包,希望在装得下的情况下,尽量装进价值更多的物品。那么我该怎么做呢?
问题抽象
假设背包的容量是m,就假设是4吧
# 表示背包容量4KG
m = 4
可选装进背包的物品有n个,物品的价值存储在prices里,重量存储在weight里
# 表示有3个物品可以选,分别价值是1500 3000 2000
prices = [1500, 3000, 2000]
# 物品重量分别是1kg 4kg 3kg
weight = [1, 4, 3]
显然,我们的目的是填满以下价值矩阵,res[i][j]表示在可选物品有i个的情况下,背包容量在j容量的情况下的最优解:
# 我们需要填满这个n 行 m列的矩阵
res = [[0] * m for _ in range(n)]
物品价值和重量 | 物品\背包容量 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
1500元 1kg | 物品1 | ||||
3000元 4kg | 物品1、物品2 | ||||
2000元 3kg | 物品1/2/3 |
求解过程
显然,第一行是最好求解的。当只有1个物品可选时,只要装得下,只能装它。所以第一行全是1500元。
物品价值和重量 | 物品\背包容量 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
1500元 1kg | 物品1 | 1500 | 1500 | 1500 | 1500 |
3000元 4kg | 物品1、物品2 | ||||
2000元 3kg | 物品1/2/3 |
当可选物品变成2个时,显然,由于物品2的重量是4KG,背包容量小于4的情况下,全都放不下,于是只有在背包容量达到4的时候,可以放一个物品2,这个格子的最优解是3000
物品价值和重量 | 物品\背包容量 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
1500元 1kg | 物品1 | 1500 | 1500 | 1500 | 1500 |
3000元 4kg | 物品1、物品2 | 1500 | 1500 | 1500 | 3000 |
2000元 3kg | 物品1/2/3 |
当可选物品变成3个的时候,在背包容量3的时候,可以选择物品3了,所以这一个格子的值是2000
物品价值和重量 | 物品\背包容量 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
1500元 1kg | 物品1 | 1500 | 1500 | 1500 | 1500 |
3000元 4kg | 物品1、物品2 | 1500 | 1500 | 1500 | 3000 |
2000元 3kg | 物品1/2/3 | 1500 | 1500 | 2000 |
最后一个格子比较特殊,我们来分别考虑
-
如果背包容量-当前最新物品的重量>0,说明还能装其他东西,所以这一个格子可能值是:
当前物品价值+ 上一行的 剩余背包容量最优解。再拿这个值,和上一行的该列比较,大的那个就是最优解
# 注意这里j+1,是因为是j从0开始,而背包容量从1开始 prices[i] + res[i-1][j+1-weight[i]] # res[i][j] = max(res[i-1][j], prices[i] + res[i-1][j+1-weight[i]])
-
如果背包容量-当前最新物品的重量=0,那么说明这时候只能装它了。最优解就是新物品重量和上一行比,大的那一个
res[i][j] = max(res[i - 1][j], prices[i])
-
如果背包容量-当前最新物品的重量<0,那么说明新物品对这个格子没有影响,还是取上一行的值
res[i][j] = res[i-1][j]
经过一番比较,我们可以知道这个格子最优是2000加上上一行的容量为1时的值
物品价值和重量 | 物品\背包容量 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
1500元 1kg | 物品1 | 1500 | 1500 | 1500 | 1500 |
3000元 4kg | 物品1、物品2 | 1500 | 1500 | 1500 | 3000 |
2000元 3kg | 物品1/2/3 | 1500 | 1500 | 2000 | 3500 |
最终代码
经过上面的举例,可以看出,大部分格子都遵循这三种情况。除了第一行,因为没法和上一行比较。最终代码如下:
def max_bag(n: int, m: int, weight: list[int], prices: list[int]):"""背包问题:param n: 可选物品总数量,矩阵的行:param m: 背包总重量,矩阵的列:param weight: 物品重量列表:param prices: 物品价格列表:return: 返回最大价值"""res = [[0] * m for _ in range(n)]# 第一行for j in range(m):if j + 1 - weight[0] >= 0:res[0][j] = prices[0]else:res[0][j] = 0for i in range(1, n):for j in range(m):# 大于零说明装了当前商品之后还有空位,那么最大值就是在res[i-1][j]和# 当前价值prices[i] + 剩余格子价值# 这两者之间取最大值if j + 1 - weight[i] > 0:res[i][j] = max(res[i-1][j], prices[i] + res[i-1][j+1-weight[i]])# 等于0,说明刚好能装这一个物品,所以最大值是要么上一行的这个格子,要么就是当前商品价值elif j + 1 - weight[i] == 0:res[i][j] = max(res[i - 1][j], prices[i])# 如果小于0,那就还是取上一行的else:res[i][j] = res[i-1][j]print(res)return res[n-1][m-1]
可以用如下输入来验证结果:
my_n = 3
my_m = 4
my_weights = [1, 4, 3]
my_prices = [1500, 3000, 2000]
print(max_bag(my_n, my_m, my_weights, my_prices))
本文参考了:https://blog.csdn.net/bohu83/article/details/91453227