老生常谈:动态规划求解背包问题思路记录

1. 背包问题母题描述:

假设一个包载重量为m,有n个物品,重量为w_i,价值为v_i,1 ≤ i ≤ n,要求把物品装入背包,并使包内物品价值最大。

(参考文献:张玲玲.《python算法详解》[M].北京:北京邮电出版社.11.14背包问题, 292-294.)。

 这个问题可以产生很多的变形,比如

     游客打卡问题,把物品换成打卡点,重量换成需要时间,价值换为奖励分数,载重量换为时间;

     小人打仗、走路问题,把物品换成攻击对象,重量换成需要能量值,价值换为造成伤害,载重量换为总能量值……

     学会才能以不变应万变啊,下面先展示代码。

2. Show You Code

class Bag:
    def bag(self, n, m, w, v):
        res = [[0 for _ in range(m+1)] for _ in range(n+1)]
        for i in range(1, n+1):
            for j in range(1, m+1):
                if j >= w[i - 1]:
                    res[i][j] = max(res[i-1][j], res[i-1][j-w[i-1]] + v[i-1])
                else:
                    res[i][j] = res[i-1][j]
        return res[n][m]
        """
      res = [[0, 0, 0, 0, 0, 0, 0], 
             [0, 0, 4, 4, 4, 4, 4],
             [0, 0, 35, 35, 39, 39, 39],
             [0, 43, 43, 78, 78, 82, 82],
             [0, 43, 43, 78, 78, 88, 88]]
        """


if __name__ == "__main__":
    n, m, w, v = 4, 6, [2, 2, 1, 2], [4, 35, 43, 10]
    # 变量介绍: 
    # 物品数:n,载重量:m,每个物品的重量list: w,每个物品对应的价值list:v。
    # 打卡点数 , 总时间数,每个打卡点需要时间list,打卡奖励分值list
    # 敌人数,总活力值,每步所需时间list,造成伤害list。
    print(Bag().bag(n, m, w, v))

3. My viewpoint

1. 以求解母题“背包问题”为例,解题关键点:

      1)推理递推公式。即如何确定放入当前物品后 相比于 放入当前物品前 哪个总价值更大。判断之后,即可获取某一总重量下的最大价值。然后就可以不断在后序的步骤中被调用。

     递推公式: res[i][j] = max\left \{ res[i-1][j],v[i-1] + res[i-1][j-w[i-1]] \right \}

     公式理解:

     1️⃣ 当前最大价值 = max{ 不放当前物品 i 的最大价值, 放了当前物品 i 的最大价值 }

     2️⃣ 放了当前物品 i 的最大价值 = 当前物品的价值 v[i-1] (其中i-1是为了获取列表中对应角标的价值)  +  不放当前物品 且 载重条件满足下的最大价值
     3️⃣不放当前物品 且 载重条件满足 = 当前总载重 j - 当前物品 i 所占用重量 w[i-1]   (其中i-1是为了获取列表中对应角标的重量)

       2)构建二维列表做记录,运行时遍历的方向为 逐行从左往右。这个思路和从左上角走到右下角问题的求解思路相似。注意要多出来一列初始化为0的列。

2. 为什么遍历的方向为 逐行从左往右

     因为要获取不放当前物品 i 的最大价值,相当于要获取i-1行 的最大价值, 其对应角标也得是 j ,所以先生成出来,因此从整体上看是逐行从左往右的。

4. 此外 如果要返回具体背了哪些物品呢?

  增加一个show函数,从res右下角向左上角遍历即可。

class Solution:
    def bag(self, n, m, w, v):
        res = [[0 for _ in range(m+1)] for _ in range(n+1)]
        for i in range(1, n+1):
            for j in range(1, m+1):
                if j > w[i-1]:
                    res[i][j] = max(res[i-1][j], v[i-1]+res[i-1][j-w[i-1]])  # 减去当前重量的剩余重量的最大价值
                else:
                    res[i][j] = res[i-1][j]

        self.show(n, m, w, res)  # 展示选取的物品编号, 会得到一个逆序的序号列表:[3, 2, 1]
        return res[n][m]  # 返回结果:88

    def show(self,n, m, w,res):
        path = []  
        j = m
        for i in range(n, 0, -1):   # 思路是从右下角向左上角遍历并判断是否添加了物品
            if res[i][j] > res[i-1][j]:
                path.append(i-1)
                j -= w[i-1]  # 减去相应的重量比较
        print(path)  # path即为所求  


if __name__ == "__main__":
    n, m, w, v = 4, 6 , [2,2,1,2], [4,35,43,10]
    # n 物品数, m 总载重, w重量列表, 价值列表
    print(Solution().bag(n, m, w, v ))

 

你可能感兴趣的:(盘算法,Python探索笔记,动态规划,leetcode,背包问题)