动态规划(一)

动态规划(一)

动态规划技术已经应用于多种组合优化问题的算法设计分析中,比如图的多起点与多终点的最短路径问题、矩阵链乘问题、最大投资效应问题、背包问题、最长公共子序列问题、图像压缩问题、最大子段和问题、最优二分检索树问题等等。

背包问题

问题描述
问题建模

第一步,思考我们有哪些策略,并分析其正确性和效率:
策略1.按商品价格由高到低排序,优先挑选价格高的商品
策略2.按商品体积大小排序,优先挑选体积小的商品
策略3.按商品的价值与体积的比由高到低排序,优先挑选比值高的商品
前三种策略
由上图可见前三种策略都不能准确求出最优解。
策略4.蛮力枚举(2^n-1)
在这里插入图片描述
由上图可见蛮力枚举的过程,我们的确可以找到最优解,但是效率过于低下。
策略5.蛮力枚举:递归优化(重点)
我们首先引入一个递归函数:KnapsackSR(h,i,c)
在选择第h个到第i个商品中,容量为c时的最优解
以最后一位啤酒为例:
选择啤酒:KnapsackSR(1,4,3) + 24 (容积-10,价格+24)
不选择啤酒:KnapsackSR(1,4,13) (未进行选择,参数不改变)
从二者中选择一个最优解进行递归
通用公式
在这里可以固定第一个参数h,设置初始值为第一件物品,h=1,就可以进行简化,将h这一参数省略。伪代码如下图:
伪代码
当未递归到第一个物品,且剩余体积大于零时(递归出口)进行递归。
将p1(选择当前物品)和p2(不选择当前物品)的最大值返回给p
递归树
通过上面的递归树,我们可以看到,存在大量的重复子问题,进行了大量重复的计算。
针对上述问题,我们希望进行改进,得到优化,当务之急是减少(子问题)重复运算,我们可以通过建立一个备忘录,记住子问题的解,避免重复计算,优化后的递归树如下图所示:
优化递归树
优化后的伪代码如下:
优化后的伪代码
接下来我们对备忘录进行构造:
横轴代表剩余体积(c)从左到右0~13,纵轴代表当前商品的索引(i)从上到下
0~n。表格中的值p[i,c] 表示前i个商品,背包容量不超过c时的价值。备忘录的第一行和第一列都被初始化为0,因为没有商品,和没有体积时,无法产生价值。每个方格间的关系如图,p[i,c] = max{p[i - 1,c - vi] + pi,p[i -1,c]} 我们可以说,每个方格的值来源于上侧方格以及上一行c-vi处方格的最大值。
备忘录
由此我们可以确定备忘录的建立方式:从左向右,自上而下,一行一行进行建立(上一行的数值,是下一行数值的基础),最优解的位置在p[n,13]
接下来我们正式开始:
五件物品的属性

牢记递推公式

在建立备忘录时我们发现第一行i = 1,只能选择第一个物品啤酒(v1 = 10 )此时前九个格子只能填零,因为c<=9时,没办法放下体积为10的啤酒,然而当c > 10之后我们可以将相应价值24填入表格
来到第二行,此时物品二v2 = 3按照刚才的方法先将前两个方格填入零,接下来需要借助公式p[2,c] =max{p[1,c-vi]+pi,p[i -1,c]} ,我们将目光送回第一行,进行比较,得出第二行相应位置的数值

着重注意一下p[2,13]的值,此时将上侧p[1,13]与p[1,10]+2比较,得到较大值26,说明此时添加第二件物品进入背包(已经存在第一件物品)
按照上述做法,我们将备忘录补全,得到下图:最终结果
此时得到最优解p[5,13] = 28,但是又产生了另外一个疑问,我们究竟选择了哪些商品作为最优解的一部分呢?
为了解决这个问题我们引入一个新的数组Rec[i,c],选择当前物品Rec[i,c] = 1,未选择Rec[i,c] = 0。有了Rec数组后,再从最后一位出发,倒序回溯(寻找Rec[i,c] = 1)的方法,就可以找到选择了哪些物品。
在这里插入图片描述
Rec数组最终结果如图,回溯路径已用红色箭头标识由此看出我们选择了第三件第四件和第五件物品回溯Rec数组

KnapsackDP(n,p,v,c)
//创建二维数组P[],Rec[]
for(i = 0 to i = c)
     P[0,i] = 0
for(i = 0 to i = n)
     P[i,0] = 0

//构造表格
for(i = 1 to n){
   for(c = 1 to C){
       if(V[i] <= c&&(p[i - 1,c - V[i]] + p[i]) > P[i - 1,c]){  //比较两个P的大小择优选取,即上侧方块和左上侧方块PK大小
			P[i , c] = P[i - 1,c - V[i]] + p[i]
			Rec[i,c] = 1
       }else{
       		P[i,c] = P[i - 1,c]
       		Rec[i,c] = 0
       }
   }
}
//输出最优方案
K = C
for(i = n to 1){
if(Rec[i,K] == 1){
	print 选择商品i
	K = K - V[i]
	}else{
	print 不选商品
	}
}
return P[n,c]

伪代码如上(请勿copy直接使用,用了也没法编译,我连分号都没加)
接下来给出一个概念:最优子结构(我们不给出最优子结构的定义,希望大家可以通过实例去感知最优子结构)


最优子结构反映在刚刚的题目中:max{p[i - 1,c - vi] + pi,p[i -1,c]}也就是这个问题的max,每一步都依赖于上一步的max,本步骤的max又给予了下一步判断的依据,环环相套,每一步都是最优,这就是最优子结构的想法,大家可以多体会体会。
接下来给出动态规划分析的四个步骤,可以结合刚才讲的例子反复体会
在这里插入图片描述
时间限制,未完待续。。。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值