一般提问方式:给定背包容量V和n个物品,每个物品的重量和价值分别为w和
。选择若干物品放入背包,使得总价值最大且总重量不超过V。(白话:一个包只能装一定的物品,如何分配这些空间来装下最多价值的物品,就像高智商小偷进攻博物馆)
动态规划解决 0-1 背包问题的核心思想是利用“阶段性最优解的重叠子问题”,通过构建状态和递推公式,将一个复杂问题分解为多个小问题求解。
1. 问题描述
0-1 背包问题是一个经典的优化问题,要求从 n 个物品中选取一些放入容量为 V 的背包,使得总重量不超过 V 的前提下,总价值最大。
对于每个物品 i:
- 重量
- 价值
选择规则:
- 每个物品只能选一次(选或不选,即 0 或 1)。
- 背包的总重量不超过 V。
目标:
,使得
2. 动态规划的思想
(1) 状态定义
状态是动态规划的核心,用来表示问题的子结构。
定义 表示:
- 在前 i 个物品中,
- 背包容量为 j 时,
- 可以获得的最大价值。
最终目标:
f[n][V]即在考虑所有物品时,背包容量为 V 的最大价值。
(2) 转移方程
动态规划的递推关系基于是否选择当前物品 i:
-
不选择第 i 个物品:
最大价值相当于只考虑前 i−1 个物品时的结果,即: -
选择第 iii 个物品(前提是
):
最大价值等于前 i−1 个物品在剩余容量时的最大价值,加上当前物品的价值:
综合考虑两种情况,取最大值:
(3) 边界条件
- 当背包容量为 0 时,最大价值为 0:
- 当没有物品时,最大价值为 0:
(4) 状态表的填充
动态规划从小问题开始解决,逐步填充状态表:
- 行表示考虑的物品数量 i 。
- 列表示背包容量 j 。
3. 动态规划解决流程
-
初始化状态表:
- 构建一个大小为(n+1)×(V+1) 的二维表 f。
- 初始化第一行和第一列为 0(无物品或容量为 0 时最大价值为 0)。
-
逐步填表:
- 遍历物品 i 从 1 到 n 。
- 遍历容量 j 从 1 到 V 。
- 根据转移方程更新
。
-
获取最终结果:
为最大价值。
4. 示例讲解
示例问题
背包容量 V=8,有 4 个物品:
- 物品 1:重量 2,价值 3
- 物品 2:重量 3,价值 4
- 物品 3:重量 4,价值 5
- 物品 4:重量 5,价值 6
状态表填充
构建状态表 f[i][j](行表示物品数,列表示容量):
i\j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
0(无物品) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1(物品 1) | 0 | 0 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
2(物品 2) | 0 | 0 | 3 | 4 | 4 | 7 | 7 | 7 | 7 |
3(物品 3) | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 9 | 9 |
4(物品 4) | 0 | 0 | 3 | 4 | 5 | 7 | 8 | 9 | 10 |
- 最大价值为 10。
- 最优选择:选择物品 2 和物品 3(总重量为 3+4=7,总价值为 4+6=10)。
5. 时间与空间复杂度
-
时间复杂度:
- 遍历 n 个物品,每个物品遍历容量 V。
- 时间复杂度为
。
-
空间复杂度:
- 状态表大小为
。
- 优化:可以用一维数组优化空间至
。
- 状态表大小为
6. 总结
- 动态规划通过构建状态表和递推公式,避免了暴力枚举所有可能组合的时间浪费。
- 递推公式体现了“选择与不选择”的决策过程,确保每个状态的最优解。
- 通过自底向上的方式,将复杂问题逐步分解,最终求得全局最优解。
def knapsack(V,items):
n = len(items)
print(n)
f=[]
for i in range(n+1):
row = [0]*(V+1)
f.append(row)
#w为重量,v为价值,n为物品数量,V为背包容量
for i in range(0,n):
for j in range(0,V+1):
w = items[i][0]
v = items[i][1]
if w > j:
f[i+1][j]=f[i][j]
else:
f[i+1][j]=max(f[i][j],f[i][j-w]+v)
return f[n][V]
if __name__ == '__main__':
V = int(input("请输入背包容量:"))
n = int(input("请输入物品数量:"))
items=[]
for i in range(n):
w = int(input(f"第{i+1}个物品的重量:"))
v = int(input(f"第{i+1}个物品的价值:"))
items.append((w,v))
result=knapsack(V,items)
print(f"背包最大价值为:{result}")
运行实例结果如下:
与手写状态表结果一致。