(算法)0-1背包问题

实例1

问题描述:
有N件物品和一个容量为V的背包,每件物品只有一个。
其中第i件物品的体积为v[i],价值为w[i]
第一行输入为T,代表后续的实例数
下一行输入为N和V,对接下来两行输入为长度为N的v和w
求背包所能装物品最大价值为多少?
示例输入为:

1
3 6
4 2 3
4 2 3

1.回溯

在没接触过动态规划0-1背包问题时,我们可以采用dfs深搜回溯的方法计算,也就是暴力枚举方法。
代码如下:

T = int(input())

def dfs(v,w,i,c,value,n):
    #其中i和c分别代表目前枚举到物品数量和目前的剩余容量
    #value为目前所选组合的总价值,n为物品总数量
    ans = value
    for k in range(i,n):
        if v[k] <= c:#如果剩余容量还够
            value += w[k]
            ans = max(ans,dfs(v,w,k+1,c-v[k],value,n))
            #接下来就是回溯过程了。需要把之前赚的吐出来
            value -= w[k]
    return ans

for _ in range(T):
    #接下来输入N和V
    N,V = list(map(int,input().split()))
    #接下来是v和w
    v = list(map(int,input().split()))
    w = list(map(int,input().split()))
    ans = dfs(v,w,0,V,0,N)
    print(ans)

本质上,回溯的想法是穷举,如有5个物品。
目前已选择[1,3],接下来去递归到下一层,选择4或者5或者都选。
同时下一层的 i 必须大于目前所选择的物品对应的k,因为遍历[1,3]后又从1开始遍历是无意义的。

时间复杂度:O( 2 ^ N )
空间复杂度:O( 1 )

2.动态规划

选择动态规划后,我们需要考虑如何存储目前的状态,以及状态之间如何联系。

首先我们考虑如何存储目前的状态。
我们知道,目前我们的状态主要由两个因素决定,已经选择了那些物品和目前的背包所用的体积。通俗来说,我们选择物品的顺序是从前到后的,因此我们可以抽象出存储状态的二维数组为dp[i][c]代表考虑前i件物品时,使用容量不超过c时候背包内说装物品的最大价值。
考虑完状态如何存储后,我们需要明确我们的状态转移方程。
对于每一个状态来说,我们知道状态的 i 增加才是决定因素,当 i 增加后才有对c的判断。因此,我们可以明确的是,我们只需要在增加 i 的大小时候考虑第 i 件物品是否可以被选择即可。

那么我们就可以抽象出两种情况。
(1)在dp[i-1][c]的基础上,不选择第 i 个元素
那么有dp[i][c] = dp[i-1][c]
(2)在dp[i-1][c]的基础上,选择第 i 个元素
那么我们有dp[i][c] = dp[i-1][c - v[i]] + w[i]
意思是,在考虑前i-1个元素时,容量不超过c-v[i]的情况下,加入了体积为v[i]的物品后的情况。
为什么我们不考虑dp[i][c] = dp[i-1][c] + w[i],很显然,dp[i-1][c] 代表的意思是考虑前i-1个商品后容量不超过c的情况,这已经表明了我们目前已经把剩余空间占据了,哪怕我们有空间可用,但是他不代表我们一定能选择第i这个商品。
举例来说:
我们可能有部分商品选了后ac(代表实际考虑前 i - 1个元素我们所用的空间)+v[i]依然小于c,但这并不代表所有商品满足。

此外,我们还知道一个必然的通俗条件在于,c >= v[i]才可能选择第i个物品。也就是说,我们剩余的背包容量必定>=v[i]才能让我们选择第i个元素。

综合来说,我们所求的是价值的最大值,就是在遍历 i 递增的大前提下,考虑不同 c 中下,在是否选择第i个元素中取最大值。
这样我们就得到了我们的状态转移方程为:
dp[i][c] = max(dp[i-1][c],dp[i-1][c-v[i]] + w[i])

因此,我们可以写出动态规划代码为:

T = int(input())

def max_value(N,C,v,w):
    #定义列表形状
    dp = [[0 for _ in range(C+1)] for _ in range(N)]#shape=(N,C)
    #初始化只考虑一件物品的情况,由于代码中0为开始,我们也已0为开始
    for j in range(0,C+1):#为什么这里不考虑i-1,因为很显然当一件也不考虑的时候背包的价值肯定为0
        dp[0][j] = w[0] if j >= v[0] else 0#如果当前能考虑的最大容量>=这件物品的体积
    #接下来循环考虑所有物品
    for i in range(1,N):
        for j in range(0,C+1):
            #不选择该物品时
            dp1 = dp[i-1][j]
            #选择该物品时,可以理解为在选择第i件商品后,我们能够利用剩余空间获得最大价值与该商品价值总和和不选择该商品价值高
            dp2 = dp[i-1][j - v[i]] + w[i] if j >= v[i] else 0
            dp[i][j] = max(dp1,dp2)
    return dp[N - 1][C]


for _ in range(T):
    #接下来输入N和V
    N,V = list(map(int,input().split()))
    #接下来是v和w
    v = list(map(int,input().split()))
    w = list(map(int,input().split()))
    ans = max_value(N,V,v,w)
    print(ans)

时间复杂度:O( N * C )
空间复杂度:O( N * C )

我们注意到,每次的状态只与前一次状态有关系,实际上,我们会注意到绝大部分动态规划都有这样的特征,我们可以利用滑动窗口将空间复杂度优化到O©级别。具体来说,就是使用dp[2][C+1]大小的二维列表存储前后两次的状态即可。由于代码基本一致,这里不再提供。

不仅如此,我们甚至还可以对其空间复杂度再进行优化,我们注意到状态转移方程为:dp[i][c] = max(dp[i-1][c],dp[i-1][c-v[i]] + w[i])
对于每个i和c,他的状态只和他前一个元素里面c比他小的状态中选择,具体来说,我们可以知道,对于某个 i 来说,他的所有状态d[i][c] 只与他前一个元素中c比他小的状态dp[i-1][<=c]有关,因此我们可以更加明确的简化我们的空间为一维数组dp[c],从c = C ~0逆向遍历,这样能在更新状态时不会覆盖到比当前c小的新的状态有关的状态。
具体来说,代码如下:

T = int(input())


def max_value(N,C,v,w):
    #定义列表形状
    dp = [0 for _ in range(C+1)]#shape=(N,C)
    #初始化只考虑一件物品的情况
    for j in range(0,C+1):
        dp[j] = w[0] if j >= v[0] else 0#如果当前能考虑的最大容量>=这件物品的体积
    #接下来循环考虑所有物品
    for i in range(1,N):
        for j in range(C,v[i]-1,-1):#这里可以吧容量条件放到循环中
            #不选择该物品时
            dp1 = dp[j]
            dp2 = dp[j - v[i]] + w[i]
            dp[j] = max(dp1,dp2)
    return dp[C]


for _ in range(T):
    #接下来输入N和V
    N,V = list(map(int,input().split()))
    #接下来是v和w
    v = list(map(int,input().split()))
    w = list(map(int,input().split()))
    ans = max_value(N,V,v,w)
    print(ans)

时间复杂度:O( N * C )
空间复杂度:O( C )

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值