0-1背包问题python_0-1背包问题python版

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)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值