动态规划算法
动态规划(Dynamic programming)是一种在数学、计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。 动态规划常常适用于有重叠子问题和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法。 动态规划背后的基本思想非常简单。大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。 通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。 这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。 关于动态规划最经典的问题当属背包问题。
性质
- 最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。
- 子问题重叠性质。子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。
- 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
步骤:
- 划分:按照问题的特征,把问题分为若干阶段。注意:划分后的阶段一定是有序的或者可排序的
- 确定状态和状态变量:将问题发展到各个阶段时所处的各种不同的客观情况表现出来。状态的选择要满足无后续性
- 确定决策并写出状态转移方程:状态转移就是根据上一阶段的决策和状态来导出本阶段的状态。根据相邻两个阶段状态之间的联系来确定决策方法和状态转移方程
- 边界条件:状态转移方程是一个递推式,因此需要找到递推终止的条件
即:
【初始状态】→【决策1】→【决策2】→…→【决策n】→【结束状态】
注意:
- 问题阶段
- 每个阶段的状态
- 相邻两个阶段之间的递归关系
背包问题
问题描述:假设我们有n种类型的物品,分别编号为1, 2...n。其中编号为i的物品价值为vi,它的重量为wi。为了简化问题,假定价值和重量都是整数值。现在,假设我们有一个背包,它能够承载的重量是Cap。现在,我们希望往包里装这些物品,使得包里装的物品价值最大化,那么我们该如何来选择装的东西呢?注意:每种物品只有一件,可以选择放或者不放。初始化数据为:n=5,w={2,2,6,5,4},v={6,3,5,4,6},Cap=10
解法如下:
- 描述最优解的结构
设子问题:f[i][v]表示允许前i件物品放入容量为v的背包时可以获得的最大价值。注:这里的i从0到5,v从0到10
为了能够得到已经计算过的,更小规模的子问题,我们可以根据当前限重来只考虑第i件物品放或者不放,那么就可以转化为涉及前i-1件物品的问题,
#n:物品件数;c:最大承重为c的背包;w:各个物品的重量;v:各个物品的价值
#第一步建立最大价值矩阵(横坐标表示[0,c]整数背包承重):(n+1)*(c+1)
#技巧:python 生成二维数组(数组)通常先生成列再生成行
def bag(n,c,w,p):
res=[[-1 for j in range(c+1)]for i in range(n+1)]
for j in range(c+1):
#第0行全部赋值为0,物品编号从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]
#生成了n*c有效矩阵,以下公式w[i-1],p[i-1]代表从第一个元素w[0],p[0]开始取。
if(j>=w[i-1]) and res[i-1][j-w[i-1]]+p[i-1]>res[i][j]:
res[i][j]=res[i-1][j-w[i-1]]+p[i-1]
return res
#以下代码功能:标记出有放入背包的物品
#反过来标记,在相同价值情况下,后一件物品比前一件物品的最大价值大,则表示物品i#有被加入到背包,x数组设置为True。设初始为j=c。
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,'个,'
print''
if __name__=='__main__':
n=5
c=10
w=[2,2,6,5,4]
p=[6,3,5,4,6]
res=bag(n,c,w,p)
show(n,c,w,res)
附: 算法图解
这个解释更清晰明了