01 背包问题的 Python 解法
回溯思想解 01背包 问题
基础问题 —— 最大重量
已知:有 n 个物品需要装进背包,items[i] ( 0<= i <= n) 为第 i 个物品的重量 ,背包最大容量为 w。
求:背包最多可装入物品的总重量。
例如, 有 3 个物品,重量分别为 [3, 5, 8], 背包最大容量为 9, 则最大装入物品的总重量为 8,装入方案有 (3, 5) 或 (8),
n
n
n 个物品则具有
2
n
2^n
2n 种可能的装法(每个物品都有装入或者不装入两种状态),剪枝后会降低一定的复杂度。
# 基本 0-1 背包问题,回溯 + 剪枝
class Package:
def __init__(self):
self.max_weight = 0 # 保存计算结果
# 计算给定条件下的能装入背包的物品最大重量。
# 输入:i -> int,当前物品编号
# cw -> int, 当前背包内物品总重量
# items -> list[int], 物品重量数组
# w -> int: 背包最大容量
# 输出:无
def calculate_max_weight(self, i: int, cw: int, items: list, w: int):
n = len(items)
if i == n or cw == w: # 物品遍历完 or 背包容量满
if cw > self.max_weight:
self.max_weight = cw
return
self.calculate_max_weight(i + 1, cw, items, w) # 不放入当前物品
if cw + items[i] <= w: # 重量超过 w 则做剪枝
self.calculate_max_weight(i + 1, cw + items[i], items, w) # 放入当前物品
if __name__ == '__main__':
items = [3, 5, 8, 9]
pack = Package()
pack.calculate_max_weight(0, 0, items, 23)
print(pack.max_weight)
上述问题可以通过一个 flag[n][weight + 1]
来标记重复状态, 对于已经计算过的状态可以直接 return
,没计算过的进行计算然后更新 flag
数组。
基础问题变体 1——最大价值
在基础问题上附加条件,每个物品价值不同,如何在不超过背包最大重量的情况下使得装入背包得物品价值最大。
每个物品具有(i, v, w)三个属性,i 为标号、v 为价值, w 为重量。package_volume 为背包容量。
解法如下,只需要对上述基本程序做几点修改 。
- 增加物品类 Item(也可以用字典),每个 Item 类对象包括 value 和 weight 两个属性。
- 对
Package
类中的计算最大重量部分做修改,具体见calculate_max_value
与calculate_max_weight
的不同。 - 测试用例如下表,其中第一行输入为背包最大容量,后面每行输入为 value weight, 表示每件物品的价值和重量。
测试用例1 | 测试用例2 | 测试用例3 | 测试用例4 |
---|---|---|---|
20 | 30 | 40 | 50 |
3 5 | 8 9 | 8 10 | 15 25 |
4 7 | 3 7 | 5 7 | 9 16 |
2 8 | 10 9 | 18 21 | 3 2 |
1 6 | 2 8 | 7 5 | 8 8 |
8 3 | 3 6 | 3 9 | 4 6 |
- 对应输出如下表,输出为不超过最大容量下,最大价值
测试用例1 | 测试用例2 | 测试用例3 | 测试用例4 | |
---|---|---|---|---|
输出 | 15 | 21 | 33 | 32 |
# 物品类,每个物品包含 value 和 weight 两个属性
class Item:
def __init__(self, value: int, weight: int):
self.value = value
self.weight = weight
# 基础背包问题变体——最大价值
class Package:
def __init__(self):
self.max_value = 0 # 保存计算结果
# 计算给定条件下的能装入背包的物品最大重量。
# 输入:i -> int,当前物品编号
# cv -> int, 当前背包内物品总价值
# items -> list[Item], 物品数组
# w -> int: 背包最大容量
# 输出:无
def calculate_max_value(self, i: int, cw: int, cv: int, items: list, w: int):
n = len(items)
if i == n or cw == w: # 物品遍历完 or 背包容量满
if cv > self.max_value:
self.max_value = cv
return
self.calculate_max_value(i + 1, cw, cv, items, w) # 不放入当前物品
if cw + items[i].weight <= w: # 若将当前物品装入,重量不超过 w 则继续,超过的则剪枝
self.calculate_max_value(i + 1, cw + items[i].weight, cv + items[i].value,items, w) # 放入当前物品
if __name__ == '__main__':
items = []
package_volume = int(input())
while True:
try:
value, weight = map(int, input().split())
except:
break
item = Item(value, weight)
items.append(item)
pack = Package()
pack.calculate_max_value(0, 0, 0, items, package_volume)
print(pack.max_value)
动态规划解背包问题
设置一个 states[n][volume + 1]
数组, 初始化为全
0
0
0,
n
n
n 为物品个数,
v
o
l
u
m
e
volume
volume 为背包最大容量。states[i][j]
保存的是第
i
i
i 个物品,总重量为
j
j
j 时背包内物品价值最大的状态。对于
i
>
0
i > 0
i>0 有:
s
[
i
]
[
j
]
=
{
m
a
x
(
s
[
i
−
1
]
[
j
−
w
[
i
]
]
+
v
[
i
]
,
s
[
i
−
1
,
j
]
)
(
j
−
w
[
i
]
≥
0
)
s
[
i
−
1
]
[
j
]
(
j
−
w
[
i
]
<
0
)
s[i][j]=\left\{ \begin{aligned} & max(s[i - 1][j - w[i]] + v[i], s[i - 1, j]) & &(j - w[i] \geq 0)\\ & s[i - 1][j]& &(j - w[i] < 0) \end{aligned} \right.
s[i][j]={max(s[i−1][j−w[i]]+v[i],s[i−1,j])s[i−1][j](j−w[i]≥0)(j−w[i]<0)
其中
s
s
s 表示 states 数组,
v
v
v 和
w
w
w 分别表示价值和重量数组。
# 动态规划解 01背包 问题
# 物品类 Item
class Item:
def __init__(self, value, weight):
self.value = value
self.weight = weight
# 背包类 Package
class Package:
def __init__(self, volume):
self.volume = volume
self.max_value = 0
# 动态规划解决 01背包 问题
# 输入:items 物品数组,每个物品包含 value, weight 属性
# 输出:无
def calculate_max_value(self, items):
n = len(items) # 物品个数
weights = []
values = []
for item in items: # 获取 values 和 weights 数组
values.append(item.value)
weights.append(item.weight)
# 状态数组 states[n][volume + 1]
states = [[0 for j in range(self.volume + 1)] for i in range(n)]
states[0][0] = 0 # state[0][i] 要单独处理
if weights[0] <= self.volume:
states[0][weights[0]] = values[0]
for i in range(1, n): # 动态规划移动
for j in range(weights[i]):
states[i][j] = states[i - 1][j]
for j in range(weights[i], self.volume + 1):
states[i][j] = max(states[i - 1][j], states[i - 1][j - weights[i]] + values[i])
self.max_value = max(states[n - 1]) # 更新 package 装入物品的最大价值
if __name__ == '__main__':
items = [] # 物品列表
package_volume = int(input()) # 背包最大容量
while True:
try:
value, weight = map(int, input().split())
except:
break
item = Item(value, weight)
items.append(item)
pack = Package(package_volume)
pack.calculate_max_value(items)
print(pack.max_value)