背包问题刨析(Python代码)

一、01背包问题

题目描述

有n个重量和价值分别为 w i w_i wi v i v_i vi的物品。从这些物品中挑选出总重不超过W的物品,求所有挑选方案中价值总和的最大值。

方法一:深度优先搜索

对于01背包问题,即每个物品有两种选择(选,不选)。那么我们可以依据此性质建立选与不选二叉树。代码如下:

class Solution:
    def zeronebag(self, n, W, w, v):
        def rec(i, j):
            if i == n:
                res = 0
            elif j < w[i]:
                res = rec(i+1, j)
            else:
                res = max(rec(i+1, j), rec(i+1, j-w[i])+v[i])
            return res
        return rec(0, W)
if __name__=='__main__':
    a = Solution().zeronebag(4, 5, [2, 1, 3, 2], [3, 2, 4, 2])
    print(a)

在这里插入图片描述

这种方法的搜索深度是n,而且每一层的搜索都需要两次分支,最坏就需要 O( 2 n 2^n 2n 的时间,当n比较大时就没办法解决。从上述图中可以发现橙色块rec以(3, 2)为参数调用了两次。参数相同返回结果相同,因此我们可以把第一次搜索的结果记录下来,这样下次搜索直接在记录中查找即可,这样就有如下优化方法。

方法二:记忆化搜索

创建dp数组,用于存储已经搜索过的rec值。则有如下代码:

class Solution:
    def zeronebag(self, n, W, w, v):
        def rec(i, j):
            if dp[i][j] > 0:
                return dp[i][j]
            if i == n:
                res = 0
            elif j < w[i]:
                res = rec(i+1, j)
            else:
                res = max(rec(i+1, j), rec(i+1, j-w[i])+v[i])
            dp[i][j] = res
            return res
        dp = [[0] * (w+1) for _ in range(n+1)]
        return rec(0, W)

if __name__=='__main__':
    a = Solution().zeronebag(4, 5, [2, 1, 3, 2], [3, 2, 4, 2])
    print(a)

上述方法参数的组合为nW种,而函数内只调用2次递归,所以只需要 O(nW) 的复杂度就能解决这个问题。

方法三:动态规划(DP)

事实上,观察上述记忆化数组,记 dp[i][j] 为根据rec的定义,从第i个物品开始挑选总重小于j时,总价值的最大值,有如下递推式:
在这里插入图片描述
如上所示,不用写递归函数,直接利用递推式将各项的值计算出来,简单地用二重循环也可以解决这一问题。代码如下:

class Solution:
    def zeronebag(self, n, W, w, v):

        dp = [[0] * (W+1) for _ in range(n+1)]
        for i in range(n-1, -1, -1):
            for j in range(W+1):
                if j < w[i]:
                    dp[i][j] = dp[i+1][j]
                else:
                    dp[i][j] = max(dp[i+1][j], dp[i+1][j-w[i]]+v[i])
        return dp[0][W]
if __name__=='__main__':
    a = Solution().zeronebag(4, 5, [2, 1, 3, 2], [3, 2, 4, 2])
    print(a)

这个算法的复杂度与前面相同,也是O(nW),但是简洁很多。
上述i的循环是逆向的,可以改变递推式,从0开始,代码如下。

class Solution:
    def zeronebag(self, n, W, w, v):

        dp = [[0] * (W+1) for _ in range(n+1)]
        for i in range(n):
            for j in range(W+1):
                if j < w[i]:
                    dp[i+1][j] = dp[i][j]
                else:
                    dp[i+1][j] = max(dp[i][j], dp[i][j-w[i]]+v[i])
        return dp[n][W]
if __name__=='__main__':
    a = Solution().zeronebag(4, 5, [2, 1, 3, 2], [3, 2, 4, 2])
    print(a)

事实上,我们可以只创建一维数组存储 j 时的最大价值和。代码如下:

class Solution:
    def zeronebag(self, n, W, w, v):
        dp = [0] * (W+1)
        for i in range(n):
            for j in range(W, w[i]-1, -1):
                    dp[j] = max(dp[j], dp[j-w[i]]+v[i])
        return dp[W]
if __name__=='__main__':
    a = Solution().zeronebag(4, 5, [2, 1, 3, 2], [3, 2, 4, 2])
    print(a)

二、完全背包问题

完全背包问题是不限制每个物品挑选的数量。类似找零钱问题,找零钱问题刨析。代码如下:

class Solution:
    def fullbag(self, n, W, w, v):
        dp = [0] * (W+1)
        for i in range(n):
            for j in range(w[i], W+1):
                    dp[j] = max(dp[j], dp[j-w[i]]+v[i])
        return dp[W]
if __name__=='__main__':
    a = Solution().fullbag(4, 5, [2, 1, 3, 2], [3, 2, 4, 2])
    print(a)

--------持续更新中--------

例题

某次漫展,已知有n个打卡点,每个打卡点的活动需要 m_i 分钟完成,完成后获得奖励点 r_i,已经打卡过的点不能再去。

需要在规定 m 分钟内完成,尽可能多的收获奖励点,求获得最多的奖励点数。

输入描述:
第一行两个整数,打卡点的数量 n 和限制时间 m

第 2 到 1 + n 行,每行两个整数 m_i,r_i

数字以空格分割,其中 0 < n <= 100,1 <= m <= 120,1 <= m_i <= 10,1 <= r_i <= 100

输出描述:
整数, 最大的奖励点数

输入例子1:
4 6
2 4
2 35
1 43
2 10

输出例子1:
88

  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值