Python数据结构与算法36:动态规划案例分析

:本文如涉及到代码,均经过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.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容