感谢这些朋友们的文章,给了我很大启发:
https://blog.csdn.net/songyunli1111/article/details/94778914
https://blog.csdn.net/na_beginning/article/details/62884939
https://blog.csdn.net/qq_39445165/article/details/84334970
这种情况下的背包问题是,给定物品种类N和背包重量W:
可用动态规划来求解。
行表示物品,如:第当i=3时,表示开始考虑前三个物品的情况。
列表示背包的重量,从0一直到W,如:当j=5时,表示当前背包重量是5。
行和列在一起的意义:当i=3,j=5时,表示在背包容量是5的情况下,装前三种物品,怎么装能价值最大。
分析:对于一个物品,对付他只有两个办法,to be or not to be,装还是不装?
所以,对于一个物品,我们总想看看它装入背包后和不装入的背包的总价值哪个大。这么说可能有点抽象,举例来说:假设背包容量是10kg,现在有一个物品,重7kg,价值是5块钱,由于这是第一个碰见的物品,自然要装(不装总价值就是0了,显然不对);又碰见另一个物品,重量是5kg,价值是4块钱,此时我们能想到的就是最好都能装进去,这样价值肯定最大,但是7+5=12>10,显然不能这么干,所以有如下考虑:
所以,应该选择不装,动态规划递推式如下:
dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i-1]] + value[i-1])
用这个公式来理解上面的例子,当i=2,j=7时表示背包容量是7,目前遇见了两个物品,怎么装使总价值最大?
dp[2][7] = max ( dp[1][7] , dp[1][7-5] + 4 )
不选第二个物品对应dp[1][7] , 选对应dp[1][7-5] + 4,意思是选了第二个之后,背包的容量由7变为2,但是价值多了4,我们只需要知道dp[1][2]是多少就行,而这个在之前已经算出来了。
import numpy as np
def solution(max_weight,weight,value):
dp = np.zeros((len(weight)+1,max_weight+1),dtype=int)
for i in range(1,len(weight)+1):
for j in range(1,max_weight+1):
if j >= weight[i-1]:
dp[i][j] = max(dp[i-1][j],dp[i-1][j-weight[i-1]] + value[i-1])
else:
dp[i][j] = dp[i-1][j]
print(dp)
return dp
def things(max_weight,dp,weight,value):
goods = []
raw = len(weight)
col = max_weight
remain = dp[raw][col]
while remain != 0:
if dp[raw][col] != dp[raw-1][col]:
remain -= value[raw-1]
col -= weight[raw-1]
goods.append(raw)
raw -= 1
print('装入的物品是:',goods)
weight = [7,4,3,2]
value = [9,5,3,1]
dp = solution(10,weight,value)
things(10,dp,weight,value)
程序运行结果如下:
[[ 0 0 0 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 9 9 9 9]
[ 0 0 0 0 5 5 5 9 9 9 9]
[ 0 0 0 3 5 5 5 9 9 9 12]
[ 0 0 1 3 5 5 6 9 9 10 12]]
装入的物品是: [3, 1]
上一节说的是是用二维数组来求解,这种方法的优点是可以找到装了哪些物品,缺点是占用的空间太大,所以如果不必知道装了哪些物品,使用下面的方式更为合适:
import numpy as np
import copy
def solution2(max_weight,weight,value):
dp = np.zeros(max_weight+1,dtype=int)
dp_next = np.zeros(max_weight+1,dtype=int)
for i in range(0,len(weight)):
for j in range(0,max_weight+1):
if weight[i] <= j:
dp_next[j] = max(dp[j],dp[j-weight[i]] + value[i])
dp = copy.copy(dp_next) # 其实这是浅拷贝,但是由于里面没有复杂元素,因此起到的作用和深拷贝一样
print(dp_next)
weight = [7,4,3,2]
value = [9,5,3,1]
输出结果:
[ 0 0 1 3 5 5 6 9 9 10 12]
分析程序:查看结果发现,这其实就是二维数组方法输出的最后一行,在二维数组中,下一行要用到上一行的数据。
举例来说:
这种方法其实就是舍弃了寻找哪些物品被装入来换取空间,由于下一行只需要用到上一行的信息,因此用两个一维数组反复迭代即可。
回顾以上两种解法,新的一行都要用到上一行的信息,和本行的信息没有关系,举例来说:新的一行j=7时,只会用到上一行j=7之前的信息,本行和上一行j=7之后的信息都不会被使用到,因此可用如下代码来解决:
def solution3(max_weight,weight,value):
dp = np.zeros(max_weight+1,dtype=int)
for i in range(0,len(weight)):
for j in range(max_weight,-1,-1):
if j >= weight[i]:
dp[j] = max(dp[j],dp[j-weight[i]] + value[i])
print(dp)
weight = [7,4,3,2]
value = [9,5,3,1]
结果是:
[ 0 0 1 3 5 5 6 9 9 10 12]
分析程序:注意j是从大到小的,这非常重要,因为如果从小到大,即
for j in range(0,max_weight+1):
if j >= weight[i]:
dp[j] = max(dp[j],dp[j-weight[i]] + value[i])
那么程序就会出错,不妨来分析一下:
发现什么不对没有?别说j=8,就算j=10都不可能出现前两种物品总价值是10的情况,为什么?
因为前面说过,“新的一行都要用到上一行的信息,和本行的信息没有关系”,如果你想让j从0开始,就破坏了这个原则。举例来说:假设j=4时更新了dp[4]=5,那么再到j=8时,dp[8-4]=dp[4]就不是上次i循环的信息了,而是这次更新过的信息;但是如果j从10开始,假设当前是第二次循环(物品2重4kg,价值是5),dp[10-4]=dp[6]用的就是第一次循环中的dp[6],因为本次循环的dp[6]还没有计算出来。