注:本文如涉及到代码,均经过Python 3.7实际运行检验,保证其严谨性。
本文阅读时间约为8分钟。
博物馆大盗问题
大盗潜入博物馆,面前有5件不同的宝物,每件宝物都分别有自己的重量和价值。大盗的背包仅能负重20KG,请问如何选择某几件宝物,使得总价值最高?
宝物item 重量weight 价值value
1 2 3
2 3 4
3 4 8
4 5 8
5 9 10
分析:
贪心策略在这里是失效的,要解决问题,只能依靠动态规划。
创建一个函数m(i, w),这个函数表示的是,前i(1 <= i <= 5)个宝物中,组合不超过W(1 <= w <=20)重量,得到的最大价值。
m(i, w)应该是m(i-1, w)和m(i-1, w-wi)+vi两者的最大值(wi和vi中的i都是右下标)。
我们从m(1, 1)开始计算到m(5, 20),m(i, w)会有以下4种情况:
m(i, W)= { 0 if i = 0
0 if W = 0
m(i-1, w) if wi > w ……(wi中的i为w的右下标)
max{m(i-1, w), vi + m(i-1, w-wi)} otherwise ……(wi中的i为w的右下标)}
上述4种情况中,最关键的是在最后一种情况,max()函数的结果即是我们所求。
动态规划表格
根据上述分析,我们可以构建一个经过for i in range(1, 6)和for w in range(1, 21)双重遍历包含所有可能情况的这么一个表格。上节我们说过,问题的最优解包含了更小规模子问题的最优解,这是一个最优化问题能够用动态规划策略解决的必要条件。现在,我们具备了这个必要条件。
Pic-412-1 博物馆大盗问题的动态规划表格
按照上述思路,动态规划算法的程序代码如下:
# 动态规划算法解决博物馆大盗问题。
# 宝物的重量和价值,以字典形式展现。
tr = [None, {'w': 2, 'v': 3},
{'w': 3, 'v': 4},
{'w': 4, 'v': 8},
{'w': 5, 'v': 8},
{'w': 9, 'v': 10}]
# 大盗背包的最大负重。
max_w = 20
# 初始化二维表格m[(i, w)]
# 表示前i个宝物中,最大重量w的组合,所得到的最大价值。
# 当i什么都不取,或w上限为0,价值均为0。
m = {(i, w): 0 for i in range(len(tr))
for w in range(max_w + 1)}
# 逐个填写二维表格,囊括所有可能性的解,并在其中找到最优解。
for i in range(1, len(tr)):
for w in range(1, max_w + 1):
if tr[i]['w'] > w: # 装不下第i个宝物。
m[(i, w)] = m[(i - 1, w)] # 不装第i个宝物。
else:
# 不装第i个宝物,装第i个宝物,这两种情况下取最大值。
m[(i, w)] = max(m[i, w], m[(i - 1, w - tr[i]['w'])] + tr[i]['v'])
# 输出结果。
print(m[(len(tr)-1, max_w)])
<<<29
递归算法解决博物馆大盗问题
我们也可以用递归算法来解决博物馆大盗问题:
# 递归算法解决博物馆大盗问题。
# 宝物的重量和价值,以元组形式展现。
tr = {(2, 3), (3, 4), (4, 8), (5, 8), (9, 10)}
# 大盗背包的最大负重。
max_w = 20
# 初始化记忆化表格m。
# key是(宝物组合, 最大重量),value是最大价值。
m = {}
def thief(tr, w):
if tr == set() or w == 0:
m[(tuple(tr), w)] = 0 # tuple是key的要求。
return 0
elif (tuple(tr), w) in m:
return m[(tuple(tr), w)]
else:
vmax = 0
for t in tr:
if t[0] <= w:
# 逐个从集合中去掉某个宝物,递归调用。
# 选出所有可能的价值中的最大值。
v = thief(tr-{t}, w-t[0]) + t[1]
vmax = max(vmax, v)
m[(tuple(tr), w)] = vmax
return vmax
print(thief(tr, max_w))
<<<29
上面分别用动态规划和递归解决了博物馆大盗问题。动态规划当然能解决这类问题,但是,递归算法更符合人的直观思维,只要加上应用得当的记忆化,同样也能高效解决此类问题。
To be continued.