目录
1.问题描述
题目来源于《算法图解》第9章练习题9.2,如下图所示。

对于背包问题,通常的做法有列举法、贪婪算法和动态规划
(1)列举法:列举出所有的可能情况,再选择最优解,但当情况很多时,这种算法复杂度很高
(2)贪婪算法:在容量允许范围内,每次都拿剩余物品中价值最高的 ,贪婪算法能够快速解决复杂度很高的问题,但通常得到的是次优解,但就对这个题目而言,恰好可以得到最优解的
(3)动态规划:对于解决背包问题,最好的算法是动态规划,可以得到最优解。动态规划和贪婪算法相似,都是将问题分解为若干小问题,自底向上解决问题,但贪婪算法目标是取得局部最优,而动态规划要取得全局最优。
2.动态规划之网格法
《算法图解》一书中对于求解动态规划问题,使用了网格法。
对于背包问题,如下表格,网格的列表示背包容量的划分,具体的划分粒度取决于物品的最小重量,本题中,物品最小重量为1,所以划分的最小单位就是1。中间每个网格对应的是一个子问题,网格里填的数字就是我们要优化的值。换句话说,以(水,1)这个单元格为例,所对应的问题就是将水放到容量为1磅的背包中,能够获得的最大价值是多少?
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 水 | ||||||
| 书 | ||||||
| 食物 | ||||||
| 夹克 | ||||||
| 相机 |
建立网格后,一个关键的问题就是更新网格,更新规则为 当前单元格的价值=max{上一个单元格的价值,当前物品的价值+剩余空间的价值}。这个规则中,上一个单元格的价值,当前物品的价值都很容易得到,关键是剩余空间的价值。剩余空间的价值需要对剩余物品进行组合,然后找出组合中的最大值吗?答案是否定的,这个网格中有一个重要的性质是:更新第n行时,n-1行的单元格的值是目前情况下的最大值。因为网格依次向下更新,保存的永远是最大值。例如,对于6磅的背包,我当前装了1磅的物品,还有5磅的剩余空间,那我直接到前一行找到5磅这列的值,就是当前5磅背包所得到的最大价值。
因此,我们不断向下更新,直到最后一行,便可以得到各种容量背包所能得到的最大价值,这其中当然也包括问题的解——6磅背包的最大价值。此外,在更新过程中,还要记录单元格数值对应的物品,这样才能知道要拿哪些东西。不然,就会出现最后算出了最大价值,但不知道该拿哪些东西的尴尬情况。
3.python实现
在python编程实现这个算法,由于采用了网格,因此采用pandas库能够比较直观的表现出这个过程,同时可以便捷的扩展到其他类似问题。代码如下所示
"""
算法图解第9章,9.2题动态规划解决背包问题
"""
import pandas as pd
import numpy as np
def dynamic_plan():
need_goods=[['0']* (row*column)] #该列表存放每个单元格对应的物品,仅用一个下标表示
last_line=pd.DataFrame(np.zeros((1,column)))#这个pandas表格用于存放上一行的值,初始化为0,便于处理更新第一行的情况
last_line.rename(columns={0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6}, inplace=True)
count=0
for key,dict in goods.items():#迭代更新每一行
weight=dict['weight']
value=dict['value']
for i in divide_capacity: #依次和每列的重量比较
if weight<i:
remain_weight=i-weight
loc=divide_capacity.index(remain_weight)#找到前一行的该重量的位置
new_value=last_line.at[0,divide_capacity[loc]]+value#当前价值+剩余价值
if new_value>last_line.at[0,i]:
net.at[key,i]=new_value
if count>5:
need_goods[0][count]=need_goods[0][count-column-(count%column-loc)]+','+key #前面的物品加上当前物品
else:
need_goods[0][count] = key # 正好是当前物品,count<5说明更新第一行
else:
net.at[key,i]=last_line.at[0,i]
need_goods[0][count] = need_goods[0][count - column] # 正好是前一行的同列
elif weight==i:
if value > last_line.at[0,i]:
net.at[key, i] = value
need_goods[0][count]=key # 正好是当前物品
else:
net.at[key, i] = last_line.at[0,i]
need_goods[0][count] = need_goods[0][count - column] # 正好是前一行的同列
else:
net.at[key, i] = last_line.at[0,i]
if count>5:
need_goods[0][count]=need_goods[0][count-column]#正好是前一行的同列
else:
need_goods[0][count]='0'
count += 1
print(net)
last_line.loc[0]=net.loc[key]
print('need goods is:')
print(need_goods[0][count-1])
# tmp.rename(index={key:0},inplace=True)
# last_line[0]=tmp
if __name__=="__main__":
row=5#网格行数
column=6#网格列数
capacity=6#背包总容量
net_array=np.zeros((row,column))
net=pd.DataFrame(net_array) #创建pandas数据
items=['water','book','food','jack','camera']
divide_capacity=[i+1 for i in range(column)]
net.index=items #修改pandas表格的行名
net.rename(columns={0:1,1:2,2:3,3:4,4:5,5:6},inplace=True)#修改pandas表格的列名
goods={'water':{'weight':3,'value':10},'book':{'weight':1,'value':3},
'food':{'weight':2,'value':9},
'jack':{'weight':2,'value':5},
'camera':{'weight':1,'value':6}} #建立物品字典,每样物品都有重量和价值属性
dynamic_plan()#进行动态规划求解
运行结果如下图所示
更新第一行 water

更新第二行 book

更新第三行food

更新第四行jack

更新第五行camera,得到最终的结果,6磅背包对应的最大价值是25

选择装入的物品

本文介绍了如何使用动态规划的网格法解决背包问题,详细阐述了问题描述、动态规划的原理以及Python代码实现,通过实例展示了如何找到背包问题的最优解,并给出了运行结果。

8697

被折叠的 条评论
为什么被折叠?



