01背包问题

提出问题

现在我们有一个背包,需要往里边装一些物品, 这个背包的容量是固定的,每一个物品有它的重量和价值。现在我们假设,有以下4个物品。

编号i重量w价值v
123
234
345
456

背包的容量是8,现在在里边装物品,找到使背包中价值最大的装物品的方法。

问题分析:

现在我们有n个物品,背包的容量为m。对于每一个物体,我们可以将他们装到背包里,或者不装,下面我们用 X i X_{i} Xi表示该物品装还是不装,若 X i = 1 X_{i}=1 Xi=1表示装该物品,反之等于0表示不装这个物品。那么我们要解决的问题,就是一个约束最优化问题。我们要求得一个最优序列 &lt; X 1 , . . . , X i , . . . , X n &gt; &lt;X_{1},...,X_{i},...,X_{n}&gt; <X1,...,Xi,...,Xn>.在不超出背包容量m的情况下,使得背包的价值最大。
m a x &ThickSpace; X 1 w 1 + . . . + X i w i + . . . + X n w n s . t . &ThickSpace; X 1 v 1 + . . . + X i v i + . . . + X n v n &ThinSpace; &lt; = &ThinSpace; m max\; X_{1}w_{1}+...+X_{i}w_{i}+...+X_{n}w_{n} \\ s.t.\; X_{1}v_{1}+...+X_{i}v_{i}+...+X_{n}v_{n}\, &lt;=\,m maxX1w1+...+Xiwi+...+Xnwns.t.X1v1+...+Xivi+...+Xnvn<=m

建立状态矩阵

V(i,j):i表示第i个物品,j当前的背包容量,则V(i,j)表示当背包容量为j时,填充了前i个物品时的最大价值。

建立状态转移方程

初始状态,没有物品,或者背包容量为0的时候,那么V(i,j)=0:
i f &ThinSpace; i = 0 &ThinSpace; o r &ThinSpace; j = 0 &ThickSpace;&ThickSpace; V ( i , j ) = 0 if \, i = 0 \, or \, j = 0 \;\; V(i,j)= 0 ifi=0orj=0V(i,j)=0
下面开始判断每一个物品,是否应该被装进来,如果当前物品的重量大于背包容量,那么这个物品是不会被放进来的,所以V(i,j) = V(i-1,j)。
如果当前背包的重量小于背包容量,那么我们判断将这个物品放进来,还是不放的价值最大;
- 如果不放进来价值更大,那么V(i,j) = V(i-1,j)。
- 如果放进去价值更大,那么V(i,j) = V(i-1, (j-w(i)+v(i)))。
i f j &lt; w ( i ) &ThickSpace;&ThickSpace; V ( i , j ) = V ( i − 1 , j ) i f ≥ w ( i ) &ThickSpace;&ThickSpace; V ( i , j ) = m a x { V ( i − 1 , j ) , V ( i − 1 , ( j − w ( i ) ) + v ( i ) ) } if j&lt;w(i)\;\; V(i,j) = V(i-1,j) \\ if \geq w(i)\;\; V(i,j) = max\{ V(i-1,j), V(i-1, (j-w(i))+v(i))\} ifj<w(i)V(i,j)=V(i1,j)ifw(i)V(i,j)=max{V(i1,j),V(i1,jw(i))+v(i)}

最优子问题

观察这个递归式:
V ( i , j ) = m a x { V ( i − 1 , j ) , V ( i − 1 , ( j − w ( i ) ) + v ( i ) ) } V(i,j) = max\{ V(i-1,j), V(i-1, (j-w(i))+v(i))\} V(i,j)=max{V(i1,j),V(i1,jw(i))+v(i)}
如前所述,对于背包容量为j的背包填充了前i(包括i)个物品的子问题,如果没有在背包中填充物品i,那么该问题,只和在背包容量为j的背包中填充前i-1个物品的子问题有关;那么如果填充了改物品,该背包现在的容量为j-w(i),所以该问题只和背包容量为j-w(i)填充前i-1个物品的子问题有关。

def soultion(w,v,maxCap):
    dp = [[0 for _ in range(maxCap+1)] for _ in range(len(v)+1)]
    for i in range(1,len(v)+1):
        for j in range(1,maxCap+1):
            if j < w[i-1]: #该物品的重量小于当前的背包容量
                dp[i][j] = dp[i-1][j]
            else:    # 该物品的重量大于当前背包的容量
                     # 判断是否添加该物品,会使得背包的价值更大
                dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i-1]]+ v[i-1])
    return dp
### 优化空间
def soultion_1(w,v,maxCap):
    dp = [0]*(maxCap+1)
    for i in range(1,len(v)+1):
        for j in range(maxCap,0,-1):
            if j >= w[i-1]:
                dp[j] = max(dp[j],dp[j-w[i-1]]+v[i-1])
                
    return dp
w = [2,3,4,5]
v = [3,4,5,6]
V = soultion(w,v,8)
V
[[0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 3, 3, 3, 3, 3, 3, 3],
 [0, 0, 3, 4, 4, 7, 7, 7, 7],
 [0, 0, 3, 4, 5, 7, 8, 9, 9],
 [0, 0, 3, 4, 5, 7, 8, 9, 10]]
V1 = soultion_1(w,v,8)
V1
[0, 0, 3, 4, 5, 7, 8, 9, 10]

现在我们根据动态规划表来构造最优解.
对于一个初始的(i,j):
我们判断如果有V(i,j) == V(i-1,j),可以说明没有添加物品i.然后我们跳到V(i-1,j).
如果V(i,j) == V(i-1,j-w(i)) - v(i),可以说明添加了该物品,然后我们跳到V(i-1,j-w(i))
直到i == 0,跳出循环。
下面是一个递归解。

def whichid(ids,maxCap):
    if ids> 0:
        if V[ids][maxCap] == V[ids-1][maxCap]:
            items[ids] = 0
            whichid(ids-1,maxCap)
        elif maxCap >= w[ids-1] and V[ids][maxCap] == V[ids-1][maxCap-w[ids-1]]+v[ids-1]:
            items[ids] = 1
            whichid(ids-1,maxCap-w[ids-1])
    return
items = [0 for _ in range(len(w)+1)]
whichid(4,8)
items
[0, 0, 1, 0, 1]
一道面试题,是一个背包问题

小Q有X首长度为A的不同的歌和Y首长度为B的不同的歌,现在小Q想用这些歌组成一个总长度正好为K的歌单,每首歌最多只能在歌单中出现一次,在不考虑歌单内歌曲的先后顺序的情况下,请问有多少种组成歌单的方法。
我们假设现在有一个容量为K的歌单,将每一首歌看做一个物体,歌的长度看作是物体的重量,物体转换成,现在有一个容量为K的歌单,有X个重量为A的物体,也有Y个重量为B的物体,问将这个写物体放入背包,刚好把背包填满,一共有几种方法。
构建动态规划表:
V[i][j] 表示装到前i个物品,背包的容量为j时的装包的方法。
初始条件:
如果背包容量为0,物品0个,那么装满背包也只有一种方法,就是什么都不装,V[0][0]= 1.
构造递归方程:
我们对于问题V[i][j],现在我们考虑物品i,如果物品i的重量大于当前背包的容量,那么我们就不添加这个物品:
dp[i][j] = dp[i-1][j]
而如果物品i的重量小于当前背包的容量。那么我们可以选择添加当前物品或者不添加,把这两种情况的类别加起来。
dp[i][j] = dp[i-1][j] + dp[i-1][j-l[i]]
我们可以发现这个递归式和原始的背包问题很相似。

def solution(cap,a,x,b,y):
    dp = [[0]*(cap+1) for _ in range(x+y+1)]
    dp[0][0] = 1
    length = [a]*x + [b]*y
    for i in range(1,x+y+1):
        for j in range(cap+1):
            if j < length[i-1]:
                dp[i][j] = dp[i-1][j]%1000000007
            else:
                dp[i][j] = (dp[i-1][j] + dp[i-1][j-length[i-1]])%1000000007
    return dp
#节省空间
def solution_1(cap,a,x,b,y):
    dp = [0]*(cap+1)
    dp[0] =1
    length = [a]*x + [b]*y
    for i in range(1,x+y+1):
        for j in range(cap,-1,-1):
            if j >= length[i-1]:
                dp[j] = (dp[j]+ dp[j-length[i-1]])%1000000007
    return dp[-1]
def solution_1(cap,a,x,b,y):
    dp = [0]*(cap+1)
    dp[0] =1
    length = [a]*x + [b]*y
    for i in range(1,x+y+1):
        for j in range(cap,-1,-1):
            if j >= length[i-1]:
                dp[j] = (dp[j]+ dp[j-length[i-1]])%1000000007
    return dp[-1]
if __name__ == '__main__':
    while True:
        try:
            cap = int(input())
            lines = list(map(int,input().split()))
        except:
            break
        x = soultion(cap,lines[0],lines[1],lines[2],lines[3])
        print(x)
solution_1(5,2,3,3,3)
9
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值