提出问题
现在我们有一个背包,需要往里边装一些物品, 这个背包的容量是固定的,每一个物品有它的重量和价值。现在我们假设,有以下4个物品。
编号i | 重量w | 价值v |
---|---|---|
1 | 2 | 3 |
2 | 3 | 4 |
3 | 4 | 5 |
4 | 5 | 6 |
背包的容量是8,现在在里边装物品,找到使背包中价值最大的装物品的方法。
问题分析:
现在我们有n个物品,背包的容量为m。对于每一个物体,我们可以将他们装到背包里,或者不装,下面我们用
X
i
X_{i}
Xi表示该物品装还是不装,若
X
i
=
1
X_{i}=1
Xi=1表示装该物品,反之等于0表示不装这个物品。那么我们要解决的问题,就是一个约束最优化问题。我们要求得一个最优序列
<
X
1
,
.
.
.
,
X
i
,
.
.
.
,
X
n
>
<X_{1},...,X_{i},...,X_{n}>
<X1,...,Xi,...,Xn>.在不超出背包容量m的情况下,使得背包的价值最大。
m
a
x
  
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
 
<
=
 
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}\, <=\,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
 
i
=
0
 
o
r
 
j
=
0
    
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
<
w
(
i
)
    
V
(
i
,
j
)
=
V
(
i
−
1
,
j
)
i
f
≥
w
(
i
)
    
V
(
i
,
j
)
=
m
a
x
{
V
(
i
−
1
,
j
)
,
V
(
i
−
1
,
(
j
−
w
(
i
)
)
+
v
(
i
)
)
}
if j<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(i−1,j)if≥w(i)V(i,j)=max{V(i−1,j),V(i−1,(j−w(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(i−1,j),V(i−1,(j−w(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