0-1背包问题描述
假设有n件物品,编号为1, 2...n。编号为i的物品价值为vi,它的重量为wi。简化问题,都是整数值。有一个背包,它能够承载的重量是W。我们希望往包里装这些物品,使得包里装的物品价值最大化,那么该如何来选择装的东西呢? 假定选取的物品每个都是独立的,不能选取部分。也就是要么选取,要么不能选取,不能只选取一个物品的一部分。
初步分析
对于这n个物品,每个物品可能会选,也可能不选,那么总共就可能有2^n种组合选择方式。整体的时间复杂度就达到指数级别的,不可行。 换一种思路。优先挑选那些单位价格最高的是否可行呢?比如有3种物品,他们的重量和价格分别是10, 20, 30 kg和60, 100, 120。背包可装最大重量为50kg 那么按照单位价格来算的话,我们最先应该挑选的是价格为60的元素,选择它之后,背包还剩下50 - 10 = 40kg。再继续前应该挑选价格为100的元素,这样背包里的总价值为60 + 100 = 160。所占用的重量为30, 剩下20kg。因为后面需要挑选的物品为30kg已经超出背包的容量了。我们按照这种思路能选择到的最多就是前面两个物品。 可是由于有一个背包重量的限制,这里只用了30kg,还有剩下20kg浪费了。选20+30的价值为220,选10+30的价值为180所以不行
动态规划
需要选择n个元素中的若干个来形成最优解,假定为k个。那么对于这k个元素a1, a2, ...ak来说,它们组成的物品组合必然满足总重量<=背包重量限制,而且它们的价值必然是最大的。假定ak是按照前面顺序放入的最后一个物品。它的重量为wk,它的价值为vk。既然前面选择的这k个元素构成了最优选择,如果把这个ak物品拿走,对应于k-1个物品来说,它们所涵盖的重量范围为0-(W-wk)。假定W为背包允许承重的量。假定最终的价值是V,剩下的物品所构成的价值为V-vk。这剩下的k-1个元素是不是构成了一个这种W-wk的最优解呢?
我们可以用反证法来推导。假定拿走ak这个物品后,剩下的这些物品没有构成W-wk重量范围的最佳价值选择。那么我们肯定有另外k-1个元素,他们在W-wk重量范围内构成的价值更大。如果这样的话,我们用这k-1个物品再加上第k个,他们构成的最终W重量范围内的价值就是最优的。这岂不是和我们前面假设的k个元素构成最佳矛盾了吗?所以我们可以肯定,在这k个元素里拿掉最后那个元素,前面剩下的元素依然构成一个最佳解。
现在我们经过前面的推理已经得到了一个基本的递推关系,就是一个最优解的子解集也是最优的。我们这样来看。假定我们定义一个函数c[i, w]表示到第i个元素为止,在限制总重量为w的情况下我们所能选择到的最优解。那么这个最优解要么包含有i这个物品,要么不包含,肯定是这两种情况中的一种。如果我们选择了第i个物品,那么实际上这个最优解是c[i - 1, w-wi] + vi。而如果我们没有选择第i个物品,这个最优解是c[i-1, w]。这样,实际上对于到底要不要取第i个物品,我们只要比较这两种情况,哪个的结果值更大不就是最优的么?
在前面讨论的关系里,还有一个情况我们需要考虑的就是,我们这个最优解是基于选择物品i时总重量还是在w范围内的,如果超出了呢?我们肯定不能选择它,这就和c[i-1, w]一样。
另外,对于初始的情况呢?很明显c[0, w]里不管w是多少,肯定为0。因为它表示我们一个物品都不选择的情况。c[i, 0]也一样,当我们总重量限制为0时,肯定价值为0。
这样,基于我们前面讨论的这3个部分,我们可以得到一个如下的递推公式:
式(1)表示,把前面i物体装入载重量为0的背包,或者把0个物体装入载重量为j的背包,得到的价值都为0。(2)式表明,如果第i个物体的重量大于背包的载重量,则装入前面i个物体得到的最大价值,与装入前面i – 1个物体得到的最大价值一样(第i个物体没有装入背包)。式(3)表明,当第i个物体的重量小于背包的载重量时,如果把第i个物体装入载重量为j的背包后总价值上升,那么就装入,否则不装入。
有了这个关系,后面的函数结果其实是依赖于前面的结果的。我们只要按照前面求出来最基础的最优条件,然后往后面一步步递推,就可以找到结果了。
我们再来考虑一下具体实现的细节。这一组物品分别有价值和重量,我们可以定义两个数组int[] v, int[] w。v[i]表示第i个物品的价值,w[i]表示第i个物品的重量。为了表示c[i, w],我们可以使用一个int[i][w]的矩阵。其中i的最大值为物品的数量,而w表示最大的重量限制。按照前面的递推关系,c[i][0]和c[0][w]都是0。而我们所要求的最终结果是c[n][w]。所以我们实际中创建的矩阵是(n + 1) x (w + 1)的规格。下面是该过程的一个代码参考实现: python代码1 收藏代码
def bag(n, c, w, v):
res = [[-1 for j in range(c + 1)] for i in range(n + 1)]
for j in range(c + 1):
res[0][j] = 0
for i in range(1, n + 1):
for j in range(1, c + 1):
res[i][j] = res[i - 1][j]
if j >= w[i - 1] and res[i][j] < res[i - 1][j - w[i - 1]] + v[i - 1]:
res[i][j] = res[i - 1][j - w[i - 1]] + v[i - 1]
return res
def show(n, c, w, res):
print('最大价值为:', res[n][c])
x = [False for i in range(n)]
j = c
for i in range(1, n + 1):
if res[i][j] > res[i - 1][j]:
x[i - 1] = True
j -= w[i - 1]
print('选择的物品为:')
for i in range(n):
if x[i]:
print('第', i, '个,', end='')
print('')
if __name__ == '__main__':
n = 5
c = 10
w = [2, 2, 6, 5, 4]
v = [6, 3, 5, 4, 6]
res = bag(n, c, w, v)
show(n, c, w, res)
python代码1
# 背包的载重量
m = 33
dp = [[-1 for j in range(m + 1)] for i in range(n)]
for i in range(n):
dp[i][0] = 0
for j in range(m + 1):
if w[0] <= j:
dp[0][j] = p[0]
else:
dp[0][j] = 0
def dp_fun(i, j):
if dp[i][j] != -1:
return dp[i][j]
if j >= w[i]:
dp[i][j] = max(dp_fun(i - 1, j), dp_fun(i - 1, j - w[i]) + p[i])
else:
dp[i][j] = dp_fun(i - 1, j)
return dp[i][j]
print('最大值为:' + str(dp_fun(n - 1, m)))
python代码2
# coding:utf-8
def bag(n, m, w, v):
res = [[0 for j in range(m + 1)] for i in range(n + 1)] # n+1 行,m+1列 值为0的矩阵
for i in range(1, n + 1):
for j in range(1, m + 1):
res[i][j] = res[i - 1][j] # 0->res[0][1]->res[1][1]
if j >= w[i - 1] and res[i][j] < res[i - 1][j - w[i - 1]] + v[i - 1]:
# 如果res[i-1][j]小于res[i-1][j-w[i-1]]+v[i-1],那么res[i][j]就等于res[i-1][j],否则就等于res[i-1][j-w[i-1]]+v[i-1]
res[i][j] = res[i - 1][j - w[i - 1]] + v[i - 1]
return res
def show(n, m, w, res):
print(u"最大值为%d" % res[n][m])
x = [False for i in range(n)]
j = m
for i in range(n, 0, -1):
if res[i][j] != res[i - 1][j]:
x[i - 1] = True
j -= w[i - 1]
print(u"选择的物品为")
for i in range(n):
if x[i]:
print(u"第%d个" % (i + 1))
if __name__ == "__main__":
# n种物品,承重量为m,w物品的重量,v 物品的价值
n = 4
m = 5
w = [2, 1, 3, 2]
v = [12, 10, 20, 15]
res = bag(n, m, w, v)
print(res)
show(n, m, w, res)
python代码3
import numpy as np
# def solve(vlist,wlist,totalWeight,totalLength):
# resArr = np.zeros((totalLength+1,totalWeight+1),dtype=np.int32)
# for i in range(1,totalLength+1):
# for j in range(1,totalWeight+1):
# if wlist[i] <= j:
# resArr[i,j] = max(resArr[i-1,j-wlist[i]]+vlist[i],resArr[i-1,j])
# else:
# resArr[i,j] = resArr[i-1,j]
# return resArr[-1,-1]
#
# if __name__ == '__main__':
# v = [0,60,100,120]
# w = [0,10,20,30]
# weight = 50
# n = 3
# result = solve(v,w,weight,n)
# print(result)
def solve2(vlist,wlist,totalWeight,totalLength):
resArr = np.zeros((totalWeight)+1,dtype=np.int32)
for i in range(1,totalLength+1):
for j in range(totalWeight,0,-1):
if wlist[i] <= j:
resArr[j] = max(resArr[j],resArr[j-wlist[i]]+vlist[i])
return resArr[-1]
if __name__ == '__main__':
v = [0,60,100,120]
w = [0,10,20,30]
weight = 50
n = 3
result = solve2(v,w,weight,n)
print(result)
至此,我们对于这种问题的解决方法已经分析出来了。它的总体时间复杂度为O(nw) ,其中w是设定的一个重量范围,因此也可以说它的时间复杂度为O(n)。