从小偷背包问题出发
假如你是个小偷,背着一个可装四磅的背包,你可偷的商品如下, 为了让盗窃的商品价值更高,你该选择那些商品。
最简单的解法,列举出所有的商品组合,然后选择最优的方案 。但是这是一个np完全问题, 算法复杂度为O(2^n) 。商品数目少的时候可以,商品数目多的时候问题变得不可解。
动态规划的思想,就是将一个棘手的问题转化为小问题,然后不断扩大问题的规模 。即先解决子问题,再解决大问题。
确定上面的思路之后,事情就简单了, 那么对小偷背包问题,最简单的情况是什么呢?
肯定是不偷,不偷就不要选择 。
然后就是只有一件商品可以偷,比如吉他,
当小偷的背包只能装一磅时,那么最优的选择就是选择偷吉他。价值1500
当小偷的背包能装两磅时,那么最优的选择就是选择偷吉他。价值1500
当小偷的背包能装三磅时,那么最优的选择就是选择偷吉他。价值1500
当小偷的背包能装四磅时,那么最优的选择就是选择偷吉他。因为此时只有一个商品吉他可以偷 。价值1500。
可偷物品 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
吉他 | 1500 | 1500 | 1500 | 1500 |
当小偷可偷吉他和音响时,
小偷的背包只能装一磅时,那么最优的选择就是选择偷吉他。
当小偷的背包能装两磅时,那么最优的选择就是选择偷吉他。
当小偷的背包能装三磅时,那么最优的选择就是选择偷吉他。
当小偷的背包能装四磅时,那么最优的选择就是选择偷音响。
可偷物品 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
吉他 | 1500 | 1500 | 1500 | 1500 |
吉他+音响 | 1500 | 1500 | 1500 | 3000 |
关键点在于,当多了一件商品时,情况变的复杂了。但这时候我们只需要选择要不要偷这件商品 ?
只需要比较偷和不偷哪个获得的价值更。如何比较呢?
不偷,问题的最优解就是之前考虑的没有音响的小问题 。那么选择不偷的最优解就是只能偷吉他的最优解。
偷,小偷获得价值就是 音响的价值 加上背包剩下的空间的最优解的价值。 而剩下空间的最优解之前小问题都已经分析过了。
举个具体的例子, 当现在背包为4时,
是否偷音响,不偷,最优的价值就是1500。偷,就是音响的价值+背包剩下空间的价值( 0)= 3000。 那么偷更划算 。那就偷 。
之后的每一步都是这样 , 多新增一个物品时,只需要比较偷和不偷的价值哪个更优,得出当前最优解 。极大的简化了问题。
可偷物品 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
吉他 | 1500 | 1500 | 1500 | 1500 |
吉他+音响 | 1500 | 1500 | 1500 | 3000 |
吉他+音响+电脑 | 1500 | 1500 | 2000 | 3500 |
当背包三磅时,不偷电脑,最优价值1500 。偷,电脑价值(2000) + 剩余空间(0)的最优价值(0) = 2000 .所以偷电脑 。
当背包四磅时,不偷电脑,最优价值3000 。偷,电脑价值(2000) + 剩余空间(1)的最优价值(1500) = 3500 .所以偷电脑 。
逐渐增加更多是的商品也是如此,每一步只要简单的判断。极大的降低了算法的复杂性 。
与分而治之不同的是,动态规划中我们需要思考怎么让小问题更大 ,逐渐接近最终的问题 。
代码实现
小偷背包问题本质上就是这样一个模型, 从一个集合中选择一个子集 ,每个元素都有其价值和价格 。如何在有限的成本中选出价值最高的子集 。
import numpy as np
def dynamic_planning(dic, Money):
data = np.zeros([len(dic),len(Money)])
for item_th in range(1,len(dic)):
for i, money in enumerate(Money):
cost= dic[item_th][1]; value = dic[item_th][2]
#print(item_th, money)
if cost <= money :
join_value = value+data[item_th-1, Money.index(money-cost)]
else: join_value = 0
# print(join_value)
if data[item_th-1,i] <= join_value:
data[item_th, i] = join_value
else : data[item_th, i] = data[item_th-1,i]
print(data)
#-----
plan=[data[-1,-1]]
i, j = data.shape[0]-1,data.shape[1]-1
while j!=0 and i!=0:
if data[i,j] > data[i-1,j]:
#print(i,dic)#,dic[i][0])
plan.append(dic[i][0])
j = Money.index(Money[j] - dic[i][1])
i = i-1
else:
i = i-1
plan.reverse()
return plan
我该偷哪些商品?
dic_thief = { 0:['name','cost','value'],
1:['guita',1, 1500],
2:[ 'sound',4,3000],
3:['pc', 3,2000],
4:['iphone',1,2000]}
Money = list(range(5))
plan = dynamic_planning(dic_thief, Money)
[[ 0. 0. 0. 0. 0.]
[ 0. 1500. 1500. 1500. 1500.]
[ 0. 1500. 1500. 1500. 3000.]
[ 0. 1500. 1500. 2000. 3500.]
[ 0. 2000. 3500. 3500. 4000.]]
Out: ['pc', 'iphone', 4000.0]
解决类似问题
英国旅行去那些地方?
dic_trip = {0 :['scenic', 'cost','value'],
1:['高斯敏斯特教堂', 0.5,7],
2:['环球剧场', 0.5, 6],
3:['英国国家美术馆', 1, 9],
4:['大英博物馆', 2, 9],
5:['圣保罗大教堂',0.5,8]}
Money_trip= [0,.5, 1, 1.5,2]
plan = dynamic_planning(dic=dic_trip,Money=Money_trip)
[[ 0. 0. 0. 0. 0.]
[ 0. 7. 7. 7. 7.]
[ 0. 7. 13. 13. 13.]
[ 0. 7. 13. 16. 22.]
[ 0. 7. 13. 16. 22.]
[ 0. 8. 15. 21. 24.]]
Out: ['高斯敏斯特教堂', '英国国家美术馆', '圣保罗大教堂', 24.0]
旅行带哪些物品?
dic_trip = {0 :['scenic', 'cost','value'],
1:['水', 3,10],
2:['书', 1, 3],
3:['食物', 2, 9],
4:['夹克', 2, 5],
5:['相机',1,6]}
Money_trip= list(range(7))
plan = dynamic_planning(dic=dic_trip,Money=Money_trip)
[[ 0. 0. 0. 0. 0. 0. 0.]
[ 0. 0. 0. 10. 10. 10. 10.]
[ 0. 3. 3. 10. 13. 13. 13.]
[ 0. 3. 9. 12. 13. 19. 22.]
[ 0. 3. 9. 12. 14. 19. 22.]
[ 0. 6. 9. 15. 18. 20. 25.]]
Out: ['水', '食物', '相机', 25.0]