1、问题描述
- 给定物品的重量 weights=[1, 2, 5, 6, 7]
- 对应的价值 values=[1, 6, 18, 22, 28]
- 背包能装的最大重量为 capicity=11
- 问:我们用这个背包装什么物品能获得最大价值?
- 注意1:每件物品只有一 件
- 注意2:最终重量不能超过背包所能承载的重量
参照视频详解:哔哩哔哩 0-1背包问题
本题采用动态规划, 因为问题的本身含最优子结构。什么是动态规划问题?
- 在现实生活中,有一类活动的过程,由于它的特殊性,可将过程分成若干个互相联系的阶段;
- 在它的每一阶段都需要作出决策,从而使整个过程达到最好的活动效果。
- 因此各个阶段决策的选取不能任意确定,它依赖于当前面临的状态,又影响以后的发展。
- 当各个阶段决策确定后,就组成一个决策序列,因而也就确定了整个过程的一条活动路线。
- 这种把一个问题看作是一个前后关联具有链状结构的多阶段过程就称为多阶段决策过程,这种问题称为多阶段决策问题。
- 在多阶段决策问题中,各个阶段采取的决策,一般来说是与时间有关的,决策依赖于当前状态,又随即引起状态的转移;
- 一个决策序列就是在变化的状态中产生出来的,故有“动态”的含义,称这种解决多阶段决策最优化的过程为动态规划方法。
2、解题思路
上述是对动态规划的简述。而背包问题就是一个典型的动态规划问题,存在诸多的限制条件,比如:
- 背包最大重量有限,capicity
- 每个物品的重量不同,weight
- 每个物品对应的价值不同,value
这样就会出现几种情况:
- 当前物品的重量 > 包的承载量,显然装不上,那它当前的最大价值 = 原有包中的价值(不装这个物品时的最大价值)
- 当前物品的重量 < 包承载量大。则说明当前这个物品可以装进去。 那我们就得考虑: 装这个物价值变大了,还是变小了?
那我们不妨把最大重量和物品都罗列出来,下面就展示了,横轴红箭头随着capacity最大重量从小到大的增加,和纵轴绿箭头物品的增加,最终最大价值的变化表格。
3、代码实现
3.1、得到最大价值表
将每一种可能的组合,得到的最大价值罗列成表,展示出来。包括最大重量0-11,物品数量0-7个。
import numpy as np
def bag(weights, values, capicity_max):
n = len(values)
# 初始化价值网格,全为0
f = [[0 for j in range(capicity_max+1)] for i in range(n+1)]
print(f)
print(np.array(f).shape)
# 按纵轴遍历,看加入新物品对价值的影响
for i in range(1, n+1):
# 按横轴遍历,看增加最大重量对价值的影响
for capicity in range(1, capicity_max+1):
# [1,1]开始,横轴看,每一个始终继承它前(左边)一个对值
f[i][capicity] = f[i-1][capicity]
# 如果当前对最大重量 大于 该物品重量,且当前对最大价值 小于 加入物品后对最大价值,则将该物品加入,更新该位置对最大价值
if capicity >= weights[i-1] and f[i][capicity] < f[i-1][capicity-weights[i - 1]] + values[i-1]:
f[i][capicity] = f[i-1][capicity-weights[i-1]] + values[i-1]
return f
def main():
weights = [1, 2, 5, 6, 7]
values = [1, 6, 18, 22, 28]
capicity_max = 11
f = bag(weights, values, capicity_max)
print('最大价值表')
for i in range(len(f)):
print(f[i])
if __name__ == '__main__':
main()
打印的内容如下:
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
f shape: (6, 12)
最大价值表
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[0, 1, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7]
[0, 1, 6, 7, 7, 18, 19, 24, 25, 25, 25, 25]
[0, 1, 6, 7, 7, 18, 22, 24, 28, 29, 29, 40]
[0, 1, 6, 7, 7, 18, 22, 28, 29, 34, 35, 40]
下面打印出来的表,就是最大价值的对应形式,其中,在该题提供的物品里面,最大重量为11时,最大价值为40。下面我们看看装进去的,都是哪几个物品。
3.2、找到装进去的物体
思考一个问题,如果这个物体突然的被装进去了,那么,在最大价值上面,会有什么体现?好比石子丢进了水里,判断石子丢进去的位置,需要根据平静水面上波动的涟漪,从而判断出落水位置。
看下图右下角,标蓝色框的位置,是最大重量为11,最大价值为40的位置。此时发现:对应物品重量6和7,最大重量11的最大价值,都是40,说明质量为6时候,就够最大价值的,7没有被放进来,否则最大价值将进一步发生改变。
那重量6放进来后,再塞进来的东西的最大重量,只剩下11-6=5了。所以,此时的问题变成了:最大重量为5时,对应的物体时哪个?
找到capacity=5对应的纵轴,发现最大价值7之后,都是18,没有发生改变,说明,发生改变的这一步,一定是放进来东西了。于是,这里就把重量为5的东西放进来了。
我们根据前面的推断判断出,最大重量为11时,加进来的两个物体分别时5和6时候,价值达到最大。
用代码实现如下:
def show(weights, capicity_max, f):
n = len(weights)
x = [False for i in range(n)]
j = capicity_max
for i in range(n, 0, -1):
if f[i][j] > f[i - 1][j]:
x[i - 1] = True
j -= weights[i - 1]
print(x)
print("背包中所装物品为:")
for i in range(n):
if x[i]:
print('第{}个'.format(i + 1))
def main():
weights = [1, 2, 5, 6, 7]
values = [1, 6, 18, 22, 28]
capicity_max = 11
f = bag(weights, values, capicity_max)
print('最大价值表')
for i in range(len(f)):
print('第{}个, 重量{}'.format(i + 1, weights[i]))
show(weights, capicity_max, f)
if __name__ == '__main__':
main()
打印内容如下:
[False, False, True, True, False]
背包中所装物品为:
第3个, 重量5
第4个, 重量6
4、总结
至此,0-1背包最大价值最优化问题解决。其中有两个地方值得思考:
- 最大重量为11,最后我们的背包为了实现最大价值,可能装进去的最大质量是小于或等于11的
- 5个物品也是为最大价值服务的,为了实现这个目标,任意0-5个都可以
- 所以的情况都罗列出来,查表法找到最优的组合
如果文中存在错误和不解的地方,欢迎评论指出,谢谢。