背包问题详解 动态规划 DP

本文深入讲解背包问题的各种类型,包括0-1背包、完全背包及多重背包,并提供详细的算法实现,帮助读者理解如何通过动态规划解决这些经典问题。

背包问题九讲

0-1背包

在这里插入图片描述
N件物品
w[ i ]:i-th件物品的重量 ( 重量 w = [1, 1, 2, 2] )
v[ i ]:i-th件物品的价值 ( 价值 v = [1, 3, 4, 5] )
背包容量W,使得价值最大,每个物品可以被使用0或者1次
记忆化递归
在这里插入图片描述
overlap:不同重量的组合可以组成其他重量。
把记忆化递归写成DP
在这里插入图片描述
- 二维DP

def backpack01(w, v, W, N):
    dp = [[0] * (W+1) for _  in range(N)]
    for i in range(N):  # item:0th, [0th, 1th], [0th, ..., N-1th]
        for j in range(W+1):    # weight = 0, 1, ..., W
            if w[i] <= j:
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i])
            else:	# 如果使用当前item就超重,所以取之前的item(i-1)的最大值
                dp[i][j] = dp[i-1][j]
    return max(dp[N-1]) # 所有item可能组合出来的最大value (0,1, ..., N-1)可以用

if __name__ == '__main__':
    w = [1, 1, 2, 2]
    v = [1, 2, 4, 5]
    W = 4
    N = 4
    print(backpack01(w, v, W, N))
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i])

只考虑第 i 件item的策略:使用 ith or 不使用
使用前 i 个item占用空间为 j 时可以得到的最大value = max( 放入 i-th,不放入 i-th)

dp[i - 1][j]

不把 i-th放入背包,weight不变,价值 = 前 i - 1item的结果

dp[i - 1][j - w[i]] + v[i]

前i-1件物品放入剩下的容量为v-c[i]的背包中 + i-th的价值

- 优化为一维DP
dp[ i ]: 体积是 i 的情况下最大价值是多少

def backpack01(w, v, W, N):
    dp = [0 for i in range(W + 1)]
    for i in range(N):  # item:0th, [0th, 1th], [0th, ..., N-1th]
        for j in range(W, w[i] - 1, -1):    # weight = 0, 1, ..., W
        	dp[j] = max(dp[j], dp[j - w[i]] + v[i])
    return dp # 所有item可能组合出来的最大value (0,1, ..., N-1)可以用
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i])
dp[j] = max(dp[j], dp[j - w[i]] + v[i])

因为注意到二维数组dp实际上只有前后两层被用到dp[ i ][ ]和dp[ i - 1 ] [ ],所以将遍历倒叙,直接引入上下层保证 i 用的是 i - 1。

完全背包

有N种物品和一个容量为W的背包,每种物品都有无限件可用。

N件物品
w[ i ]:i-th件物品的重量 ( 重量 w = [1, 1, 2, 2] )
v[ i ]:i-th件物品的价值 ( 价值 v = [1, 3, 4, 5] )
背包容量W,不超过背包容量,使得价值最大。

从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取**0件、取1件、取2件……**等很多种

dp[ i ][ j ]=max( dp [ i-1 ][ v-k*w[ i ] ] + k * w[ i ], dp[ i -1] [ j ]) ……………k 为取多少件
但这样由于每个子问题求解的状态已经不是常数了,时间复杂度:O(Σ(W/w[i])*W)

dp[ i ]:体积是 i 的情况下,最大价值是多少。

def unbound_backpack(w, v, W, N):
    dp = [0 for _ in range(W+1)]
    for i in range(N):	# 前 i 个数
        for j in range(w[i], W + 1):	# 从物品i的单个占用空间 ~ 空间上限循环:代表着这个物品数量的所有情况
        							    # 从w[i]:一件 ~ W: W/w[i]件,这样完成了每件物品无限使用可能(不超过上限)的遍历
        	dp[j] = max(dp[j], dp[j - w[i]] + v[i])
    return dp[-1]

在这里插入图片描述

多重背包

N件物品
W重量上限
i-th物品最多有si件,重量wi, 价值vi

dp[ i ]: 总体积 i 最大价值多少

一般解法–三重循环

def multi_backpack(w, v, W, N):
    dp = [0 for _ in range(W+1)]
    for i in range(N):                      # 所占空间i
        for j in range(W, w[i] - 1, -1):    # 每个item(排除w[i] > = W的情况)
            k = 1
            while k <= s and k * v <= j:    # i-th item使用k个
                dp[j] = max(dp[j], dp[j -k * w[i]] + k * v[i])
                k += 1
    return dp[-1]

if __name__ == '__main__':
    w = [1, 2, 3, 4]    # 所占空间
    v = [2, 4, 4, 5]    # 价值
    s = [3, 1, 3, 2]    # 可用次数
    W = 5	# 重量上限
    N = 4	# 物品个数
    print(unbound_backpack(w, v, W, N))

时间复杂度O ( n 3)

拆解为01背包:
把每个item拆成si个01物品

利用二进制进行分解,任何数可以表示成 2*k的组合。一个s分成log2(s)份
s - 1 - 2 - 4 - 8 -… 一直减到为负数,余数拿出来(10 - 1 - 2 - 4 = 3 --> 10 由 1 2 4 3 组成)

O( log (n) * n 2)

def multi_backpack(w, v, W, N, s):
    dp = [0 for _ in range(W+1)]
    goods = []  # 储存二进制划分的结果
    for i in range(N):                      
        k = 1
        while k <= s[i]:   # 开始进行二进制划分
            s[i] -= k      # 一点点逼近s,找到该物体可以有几个
            goods.append([w[i] * k, v[i] * k])
            k *= 2
        if s[i] > 0:   # 余数
            goods.append([w[i] * s[i], v[i] * s[i]])

    # 将二进制划分的结果进行01背包
    for good in goods:
        for j in range(W, good[0] - 1, -1):
            dp[j] = max(dp[j], dp[j - good[0]] + good[1])
    return dp[-1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值