动态规划算法解决背包问题思路及 Python 实现 | 注:仅有分析过程代码,没有解释代码

本文背包问题选自 《算法图解》一书

问题示例(背包问题)

n/a

简单解法

n/a

动态规划

n/a
n/a
n/a
n/a

注:中间图示略。以下跳到最后一行:

n/a
n/a

n/a

Python 代码实现(动态规划)

注意,这里的一步一步实现过程很详细。
但是目前没有用语言解释代码,请悉知。

#                 weigth, worth
product = [{"guitar": (1, 1.5)},
           {"box": (4, 3)},
           {"laptop": (3, 2)}]
#       1kg,   2kg,  3kg,  4kg    use kg instand of pound
CELL = [[None, None, None, None],  # guitar
        [None, None, None, None],  # box
        [None, None, None, None],]  # laptop

CELL[0][0] = product[0]
CELL

n/a

判断改行商品的重量是否能够放入:

CELL[0][1] = product[0] if (1 + 1) >= list(product[0].values())[0][0] else None

n/a

CELL[0][2] = product[0]; CELL[0][3] = product[0]

因为知道第一行没有问题,所以暂时用结论写出这个代码。
后面在一步步手动填充好 CELL 数组之后,熟悉了算法的实现之后,
会返回来更新真实的实现。

n/a
第二行

CELL[1][0] = product[1] if (0 + 1) >= list(product[1].values())[0][0] and list(product[1].values())[0][1] >= list(CELL[0][0].values())[0][1] else CELL[0][0]

是否使用该行对应的 product 需要判断当前行的 product 是否重量放得下小背包(列),以及价值是否比上一行的本列更高

n/a
第二行第二列

CELL[1][1] = product[1] if (1 + 1) >= list(product[1].values())[0][0] and list(product[1].values())[0][1] >= list(CELL[0][1].values())[0][1] else CELL[0][1]

第二行第三列

CELL[1][2] = product[1] if (2 + 1) >= list(product[1].values())[0][0] and list(product[1].values())[0][1] >= list(CELL[0][2].values())[0][1] else CELL[0][2]

第二行第四列

CELL[1][3] = product[1] if (3 + 1) >= list(product[1].values())[0][0] and list(product[1].values())[0][1] >= list(CELL[0][3].values())[0][1] else CELL[0][3]

? boom ? 这一列的背包放入了当前行的商品
n/a
第三行
第一列

# column: 0; row: 2(0, 1, 2)
if (0 + 1) >= list(product[2].values())[0][0]:  # weight OK
    if list(product[2].values())[0][1] >= list(CELL[1][0].values())[0][1]:
        CELL[2][0] = product[2]
if not CELL[2][0]:
    CELL[2][0] = CELL[1][0]

第三行第二列

# column: 1; row: 2(0, 1, 2)
if (1 + 1) >= list(product[2].values())[0][0]:  # weight OK
    if list(product[2].values())[0][1] >= list(CELL[1][1].values())[0][1]:
        CELL[2][1] = product[2]
if not CELL[2][1]:
    CELL[2][1] = CELL[1][1]

第三行第三列

# column: 2; row: 2(0, 1, 2)
if (2 + 1) >= list(product[2].values())[0][0]:  # weight OK
    if list(product[2].values())[0][1] >= list(CELL[1][2].values())[0][1]:
        CELL[2][2] = product[2]
if not CELL[2][2]:
    CELL[2][2] = CELL[1][2]

n/a
可以看到,第三行的商品是笔记本电脑,而它在第三列(3kg)被放入背包

第三行第四列

# column: 3; row: 2(0, 1, 2)
if (3 + 1) >= list(product[2].values())[0][0]:  # weight OK
    if list(product[2].values())[0][1] >= list(CELL[1][3].values())[0][1]:
        CELL[2][3] = product[2]
if not CELL[2][3]:
    CELL[2][3] = CELL[1][3]

这里会放入音响,因为它可以放入第四列 4kg的背包中,而且单品价值最高。
结果不是语言的,因为上面的代码中还没有考虑放入该行对应的商品(笔记本电脑)之后剩余的重量可以放入的最大价值,再和本列的上一行比较。

加入剩余重量可以放入的商品价值再决定 - 正确的算法

CELL[2][3] = None
# column: 3; row: 2(0, 1, 2)
if (3 + 1) >= list(product[2].values())[0][0]:  # weight OK
    product_worth = list(product[2].values())[0][1]
    left_wigth = ((3+1) - list(product[2].values())[0][0])
    left_wigth_worth = list(CELL[2][left_wigth - 1].values())[0][1]
    if  (product_worth + left_wigth_worth)>= list(CELL[1][3].values())[0][1]:
        CELL[2][3] = [product[2], CELL[2][left_wigth -1]]
if not CELL[2][3]:
    CELL[2][3] = CELL[1][3]

n/a

最后的整理的算法实现:
将数组清空

#       1kg,   2kg,  3kg,  4kg    use kg instand of pound
CELL = [[None, None, None, None],  # guitar
        [None, None, None, None],  # box
        [None, None, None, None],]  # laptop

先初始化第一行

for i in range(4):
    CELL[0][i] = product[0] if (i + 1) >= list(product[0].values())[0][0] else None

n/a
动态规划算法

for r in range(1, 3):
    for c in range(4):
        if (c + 1) >= list(product[r].values())[0][0]:  # weight OK
            product_worth = list(product[r].values())[0][1]
            left_wigth = ((c + 1) - list(product[r].values())[0][0])
            if left_wigth > 0:
                left_wigth_worth = list(CELL[r][left_wigth - 1].values())[0][1]
                if  (product_worth + left_wigth_worth)>= list(CELL[r - 1][c].values())[0][1]:
                    CELL[r][c] = [product[r], CELL[r][left_wigth -1]]
            else:
                if product_worth >= list(CELL[r - 1][c].values())[0][1]:
                    CELL[r][c] = product[r]
        if not CELL[r][c]:
            CELL[r][c] = CELL[r - 1][c]

在这里插入图片描述
掌声响起来 ? ? ? ? ?

最后一点点提醒,算法中有几处需要改进的:

  1. 因为这里的小背包是整数,而且每个精细度都计算过,所以这里 left_wigth_worth = list(CELL[r][left_wigth - 1].values())[0][1] 不会发生 IndexError, 但是未必每个动态规划都可以这样,所以这是一个代码中省略过去的问题。
  2. 可以看到上面放入组合商品和只有一个商品的“类型”不一样,组合商品使用的列表。实际上应该全部都是列表,这一点只需要在代码中做细微的调整就可以 (同时这种调整在那种很有可能会超过 2 个组合的情况下很重要)。
  3. 上面的结果显得有点儿依赖顺序,实际上和商品放在哪一行用来放入背包无关。上面的顺序设计是为了从简单的情况一步步到复杂的情况(对于要放入背包的商品组合有更多的判断条件),便于理解和分析。在掌握之后,和最终的实现上,它是和商品在哪一行的顺序无关的。
  4. 上面的代码没有考虑一个商品在表格中的某一格使用多次的情况,实际上这是不可能发生的,因为比如当你考虑 2kg 的背包的时候,你可以放入两个 1kg 的吉他,但是吉他只有一把。所以如果调整了上面已经为了方便理解而设计过的顺序,会出现这这里说的情况,这是在实际应用中的代码中需要完善的。
  5. 调整了商品在哪一行的顺序之后,会出现一些错误,这是因为可能重量超过了小背包的重量,放不下值为 None,而后面的格子又依赖前面的格子取值来判断,就会发生 AttributeError 这样的情况,所以改进代码的时候也要考虑这一点。
  6. 可能还有其它我在实现的过程中一时没有注意到的问题…

不过为了实现方便,前面代码中没有完善上面提到的问题。这里就是做一下提醒。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值