如何理解动态规划?
Posted 大话信号处理
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何理解动态规划?相关的知识,希望对你有一定的参考价值。
来源:知乎
15=1×11+4×1 (贪心策略使用了5张钞票)
15=3×5 (正确的策略,只用3张钞票)
取11:cost = f(4) + 1 = 4 + 1 = 5
取5:cost = f(10) + 1 = 2 + 1 = 3
取1:cost = f(14) + 1 = 4 + 1 = 5
f(n) 只与f(n-1),f(n-5),f(n-10)的值相关
我们只关心 f(w) 的值,不关心是怎么凑出 w 的
思考题:请稍微修改代码,输出我们凑出w的方案。
无后效性
一旦 f(n) 确定,“我们如何凑出 f(n) ”就再也用不着了。
要求出 f(15),只需要知道 f(14), f(10), f(4) 的值,而 f(14), f(10), f(4) 是如何算出来的,对之后的问题没有影响。
“未来与过去无关”,这就是无后效性。
严格定义:如果给定某一阶段的状态,则在这一阶段以后过程的发展不受这阶段以前各段状态的影响。
最优子结构
回顾我们对 f(n) 的定义:我们记“凑出 n 所需的最少钞票数量”为 f(n)。f(n)的定义就已经蕴含了“最优”。
利用 w=14, 10, 4 的最优解,我们即可算出 w=15 的最优解。
大问题的最优解可以由小问题的最优解推出,这个性质叫做“最优子结构性质”。
引入这两个概念之后,我们如何判断一个问题能否使用DP解决呢?
能将大问题拆成几个小问题,且满足无后效性、最优子结构性质。
问题很简单:给定一个城市的地图,所有的道路都是单行道,而且不会构成环。每条道路都有过路费,问您从S点到T点花费的最少费用。
一张地图,边上的数字表示过路费
这个问题能用DP解决吗?
我们先试着记从 S 到 P 的最少费用为 f(P)。想要到 T,要么经过 C,要么经过 D。从而:
好像看起来可以DP。
现在我们检验刚刚那两个性质:
无后效性:对于点 P,一旦 f(P) 确定,以后就只关心 f(P) 的值,不关心怎么去的。
最优子结构:对于 P,我们当然只关心到 P 的最小费用,即 f(P) 。如果我们从 S 走到 T 是 S-P-Q-T,那肯定 S 走到 Q 的最优路径是 S-P-Q。对一条最优的路径而言,从S走到沿途上所有的点(子问题)的最优路径,都是这条大路的一部分。这个问题的最优子结构性质是显然的。
既然这两个性质都满足,那么本题可以 DP。式子明显为:
其中R为有路通到P的所有的点,为R到P的过路费。
代码实现也很简单,拓扑排序即可。
DP 的核心思想
DP 为什么会快?
无论是DP还是暴力,我们的算法都是在可能解空间内,寻找最优解。
来看钞票问题。
暴力做法是枚举所有的可能解,这是最大的可能解空间。
DP 是枚举有希望成为答案的解,这个空间比暴力的小得多。
也就是说:DP 自带剪枝。
DP 舍弃了一大堆不可能成为最优解的答案。譬如:
15 = 5+5+5 被考虑了。
15 = 5+5+1+1+1+1+1 从来没有考虑过,因为这不可能成为最优解。
从而我们可以得到 DP 的核心思想:尽量缩小可能解空间。
在暴力算法中,可能解空间往往是指数级的大小;如果我们采用DP,那么有可能把解空间的大小降到多项式级。
一般来说,解空间越小,寻找解就越快。这样就完成了优化。
DP的操作过程
一言以蔽之:大事化小,小事化了。
将一个大问题转化成几个小问题;求解小问题;推出大问题的解。
如何设计DP算法
下面介绍比较通用的设计DP算法的步骤:
首先,把我们面对的局面表示为 x。这一步称为设计状态。
对于状态 x,记我们要求出的答案(e.g. 最小费用)为 f(x)。我们的目标是求出 f(T)。
找出 f(x) 与哪些局面有关(记为p),写出一个式子(称为状态转移方程),通过f(p)来推出f(x)。
DP 三连
设计DP算法,往往可以遵循 DP 三连:
我是谁? ——设计状态,表示局面
我从哪里来?
我要到哪里去? ——设计转移
设计状态是 DP 的基础。接下来的设计转移,有两种方式:
一种是考虑我从哪里来(本文之前提到的两个例子,都是在考虑“我从哪里来”)。
另一种是考虑我到哪里去,这常见于求出 f(x) 之后,更新能从 x 走到的一些解。这种 DP 也是不少的,我们以后会遇到。
总而言之,“我从哪里来”和“我要到哪里去”只需要考虑清楚其中一个,就能设计出状态转移方程,从而写代码求解问题。前者又称 pull 型的转移,后者又称 push 型的转移。
思考题:如何把钞票问题的代码改写成“我到哪里去”的形式?
提示:求出 f(x) 之后,更新 f(x+1), f(x+5), f(x+11)。
扯了这么多形而上的内容,还是做一道例题吧。
最长上升子序列(LIS)问题:给定长度为 n 的序列 a,从 a 中抽取出一个子序列,这个子序列需要单调递增。问最长的上升子序列(LIS)的长度。
e.g. 1,5,3,4,6,9,7,8的 LIS 为1,3,4,6,7,8,长度为6。
如何设计状态(我是谁)?
我们记 f(x) 为以 ax 结尾的 LIS 长度,那么答案就是 max{f(x)}。
状态x从哪里推过来(我从哪里来)?
考虑比x小的每一个 p:如果 ax > ap,那么 f(x) 可以取 f(p)+1。
解释:我们把 ax 接在 ap 的后面,肯定能构造一个以 ax 结尾的上升子序列,长度比以 ap 结尾的 LIS 大1。那么,我们可以写出状态转移方程了:
至此解决问题。两层for循环,复杂度 O(n^2)。
从这三个例题中可以看出,DP 是一种思想,一种“大事化小,小事化了”的思想。带着这种思想,DP 将会成为我们解决问题的利器。
最后,我们一起念一遍DP三连吧——我是谁?我从哪里来?我要到哪里去?
·END·
你可能还感兴趣:
信号君:寻求简单之道
以上是关于如何理解动态规划?的主要内容,如果未能解决你的问题,请参考以下文章
详细实例说明+典型案例实现 对动态规划法进行全面分析 | C++