转自:http://blog.csdn.net/mayao11/article/details/7444586
动态规划英文名Dynamic Programming,这个名称总让人有一种时曾相识的感觉,可能是因为容易和“线性规划”之类的概念搞混。
首先,适用动态规划的问题十分广泛和常见——地图路径搜索(深度优先、广度优先、A*),填充容器使价值最大化(例如背包体积固定V,有不同的物体具有各自的体积和价值),文本比较算法(常用的diff工具),以及最短路径之类的求最优解的问题,幕后都有一只叫做DP的黑手操纵着。
一个算法非常常用,说明两点:这个算法效果较好,时、空复杂度相对较好;.现实中大量问题符合这个算法的适用范围。前者并无什么特别之处,而后者正是动态规划可以称为“算法之王”的原因。
不严谨的看,世界上的算法问题有三种:特别简单的,中等的,特别复杂的。
特别复杂的问题,很不严谨的说就是只有穷举才能解决的问题。算法理论中被称为NP问题就是这种,没有“N的多项式时间内”的解法。这种问题往往不好被人们利用。
特别简单的问题,解决策略往往比较明显。要么是可以简单的用贪婪法直接做,要么就是有特定的数学方法可以很快得到结果。其中能用贪婪法的问题是这样的问题:局部最优解的简单叠加即为全局最优解。举两个例子:
1、一个小偷潜入到一个仓库里,他只能从仓库里拿三样东西,问怎样才能使小偷的利益最大化?(抢答:挑三个价值最大的货物。)
2、你去超市购物花了N元(N为正整数),你的钱包里有1、5、10、50、100五种钞票足够多,问怎样付款能让付款的钞票张数最少?(抢答:根据价格,从100元到1元从大到小付清为止。比如128元,付100+10+10+5+1+1+1即可。)
你一定会觉得上面两个例子既白痴又不实际,那么,我们把他们改的实际一点。
1、一个小偷潜入到一个仓库里,他只带了一个包,包的容量为10公斤,仓库里有单件1公斤、2公斤、3公斤的货物价值分别为60元、200元、310元。问怎样带使小偷的利益最大化?
这个问题变得非常实际了,如果小偷一着急,从3公斤的开始装,不足的空间用1公斤的补,是310+310+310+60 = 970元,还不错知足了。但是,其实装5个2公斤的,就是1000元!白赚30元。
如果把它抽象成:包裹容量为V,货物重量weight = [w1, w2, ..., wn],货物价值value = [v1, v2, ..., vn],请你编程求解,是不是就有点困难了?自古鱼和熊掌不可兼得的问题大都符合这种模型。
2、为纪念M国成立60周年,政府发行了大量6元和60元纪念钞票,并可以在市面上用于流通。具有“最少张钞票”强迫症的你头疼了,因为就算你备足了各种整钱和零钱,原来的付款策略也不再奏效。例如,如果付款12元,从大到小,付一张10元和两张1元就是3张;而其实你付2张6元就可以了。
你可能会说这个问题是瞎编的。实际上,正是为了避免这种情况,所以我们的钞票才被设计成了现在这几种面值。而现实中如果不是特别设计过,那么不能用贪婪法的问题其实占大多数。
现实中的问题,要解决的问题往往只有两、三个限制条件,而往往只要两、三个限制条件,就让它变得既不简单,又不是特别复杂。可喜的是,这样正好进入了动态规划的射程。
——————————————————————————————————————————————————————————————————
跟我看几个实例:
一片原始森林要开发为野生旅游区,一批考察人员去实地考察,规划旅游路线。森林里路线非常繁多,根据计划,从A点出发,中间必须到达中途休息区(BCD三者之一都可以,未来会选择其中一处建设成休息站)供游客休息购物,然后再出发到达E点结束。
♦ | B | ♦ |
起点A | C | 终点E |
♦ | D | ♦ |
原始森林里,考察人员必须自己行走,然后记录自己到达每个点用的时间,用以最终确定路线。
这个问题很简单,最终目标是A-E,它们的距离记作Lae,划分成几个子问题:
Lab, Lac, Lad, Lbe, Lce, Lde
Lae = Min( Lab+Lbe, Lac+Lce, Lad+Lde )
全局一看,无非是三条路线,选一条最短的即可。
就说A-B这一段,也有很多走法,对考察人员来说,他们得把这些小分支都走一遍,才能找到最短的一条作为Lab。
推广开去:
A | 1 | 4 | 7 | 9 | 12 | B=12+3 | E | ||||||||||||||||||||||
1 | 26 | ||||||||||||||||||||||||||||
5 | 25 | ||||||||||||||||||||||||||||
8 | 16 | 20 | 21 | 22 | 23 | 24 |
不管路线多么复杂,总要把所有的路走一遍才能知道最短路线。因为每一个点都有可能是最终路线的一环,要所有点全部算出来才知道。
搜索策略有很多,可以任意选择。只需要记住一个关键:当一路走来达到某一个点B时,B这一点只需要记录从起点过来最少需要x步,如果从别的途径搜索过来用了x+1步,那么这条更慢的搜索路径也没用,直接终止。如图蓝色的路线到B时,B已经记录了15,蓝色路线就不需要往上走了,可以往其他没走过的格子继续走。
可以看出,越到后来,很多搜索途径会刚开始走就停止并不很浪费,搜索策略得当的话,这个问题的时间复杂度大致就和搜索空间一致,是O(格子数N)。实际上宽度优先搜索放在这里就是最好的方法了(反正我看起来是的)。
再看小偷装东西的问题。
如果背包大小是10,我们完全可以看成0、1、2、3、……、9、10 一共11个状态,每往包里放一个货物,状态就改变一次。最终达到状态10。不同的放置策略像不像一条路径?
1、2、3公斤的三种货物价值分别是60、200、310。 每个状态的价值记作m,初始状态的价值 m0 = 0。
状态1只能通过状态0跳过来。m1 = 60
状态2可以通过状态1 或者 状态0 转移过来。 m2 = max( 60+60, 200 ) = 200
状态3有三种跳转方法,从状态2来,从状态1来,从状态0来。
状态4有三种跳转方法,从状态3来,从状态2来,从状态1来。
状态5有三种跳转方法,从状态4来,从状态3来,从状态2来。
依次类推……就可以得到状态10的值了,无非是从状态7、8、9跳转而来,只需要取max即可。
——————————————————————————————————————————————————————————————————
我曾经遇到的最有情趣的一道题:一个N*N的棋盘,蚂蚁在左上角第一个格子( 0, 0 )里的位置,棋盘的每个格子里会放0~1粒蔗糖粉末。蚂蚁只能往右走或者往下走,问蚂蚁怎么走,才能使吃到的糖粉最多?
提示:设( 0,0 )格能吃到的最大数量为m(0,0),蚂蚁每次都要从向下和向右中做出选择:
m(x,y) = 目前这一格上的糖粉 + max( m(x+1,y), m(x,y+1) )
这是个递推式子,可以递归来解。当然想明白以后从右下角开始往上算就可以避免递归了。
——————————————————————————————————————————————————————————————————
大胆的抽象描述一下动态规划问题特性:
- 动态规划求解的问题,不易一眼看到明确的特征。
- 问题必须能够划分为若干子问题,或者叫做阶段。必须给定已知的初始阶段。
- 每一个阶段都必然从之前的某一个阶段跳转而来,每一个阶段都要知道自己的最优值的判定方法,以便只保留最优的那个值。
- 初始阶段经过一系列的跳转,每一步跳转都是最优解,那么达到最终阶段的时候也是最优解。(核心条件,只有满足这一条件的问题才能用动态规划法。)
反复的讨论一下:一个状态可能是最终最优跳转路径的一部分,所以大多数状态都需要被求解,虽然有可能不是。(后面会说怎么尽可能的减少求解的次数。)
有一类简单问题:只要每一步是最优解,那么结果肯定是最优解;不需要动态规划。有一类复杂问题:就算每一个子问题找到了最优解,跳转之后也不一定能得到最优解;这种问题不适用动态规划,而且不穷举的话很难求解。
符合动态规划的问题和上面二者不同:全局最优解一定是通过一系列局部最优解得来的,但是从某个局部最优解出发不一定能达到全局最优解。(举例,上面小偷装东西的问题,最终是通过2+2+2+2+2得来的,从1或者3状态出发永远也达不到最优解。)
最后,思考上最难的一点是:状态空间不仅可以是1维的,还可以是2维、3维、4维……寻找最短路径是典型的2维问题,装包是典型的1维问题。只需要修改一下小偷装东西问题,就可以变成2维的。
问题:小偷的背包容量为V,仓库里有n种货物,其价值分别为(v1,v2,...,vn),重量分别为(w1,w2,...,wn),剩余数量分别为(u1,u2,...,un),求小偷获得最大利益的方法
如果仍然使用1维的状态空间,很容易造成求得的解超过了货物的剩余数量,得到错误的解。必须使用二维空间,两个维度分别是 当前容量、当前货物总种类。最终跳转到容量为V、种类为n的节点。
——————————————————————————————————————————————————————————————
更深入的探讨,慢慢补充:
每多一个限制条件,状态空间增加一维。有时候状态空间的意义模糊不易理解,是主要的难点所在。
动态规划问题往往有两种解法:正向和反向,正向求解往往需要递归,有时候用反向可以避免递归。比如小偷装东西问题,上面说的是反向解法,没有递归。
你还可以找到一个递推式
m10 = max(m9+60,m8+200,m7+310)
其中m9、m8、m7都是未知的,直接递归来做就可以了。
而且,你发现了吗,随着递归深入,递归方式可以跳过一部分完全不需要计算的节点。举个例子,如果输入参数里,货物重量都是偶数,那么在递归的时候,奇数状态就不会被算到啦 :) (我在做diff算法时感觉到能跳过的节点不多,但是反向计算肯定是全都要算出来的。)
如果采用递归方式,务必记得用数组或者n维数组保存中间结果,不保存中间结果会反复的计算已经计算过的节点,在实际应用中觉得非常可怕。
比较恶搞的是,最终状态不一定能达到,比如小偷装东西的问题吧,如果货物重量是4、6、8、10等等,而你的背包容量是21,那么不可能达到21,只能达到20,而且进一步的,某种组合方式可能最大值不是出现在后面的状态,所以,你得max(状态1, 状态2 ..., 状态n)才能得到真正的极值。网上无数代码存在此漏洞,包括《编程之美——微软技术面试心得》这本书。
这又牵扯到神奇的数学,不同的数字竟然会对算法造成影响,恐怕这也是数学家的乐趣所在了 = =
转自:http://blog.163.com/hadyk1111@126/blog/static/2959940620075234275875/
动态规划方法采用最优原则( principle of optimality)来建立用于计算最优解的递归式。所谓最优原则即不管前面的策略如何,此后的决策必须是基于当前状态(由上一次决策产生)的最优决策。由于对于有些问题的某些递归式来说并不一定能保证最优原则,因此在求解问题时有必要对它进行验证。若不能保持最优原则,则不可应用动态规划方法。在得到最优解的递归式之后,需要执行回溯(t r a c e b a c k)以构造最优解。
编写一个简单的递归程序来求解动态规划递归方程是一件很诱人的事。然而,正如我们将在下文看到的,如果不努力地去避免重复计算,递归程序的复杂性将非常可观。如果在递归程序设计中解决了重复计算问题时,复杂性将急剧下降。动态规划递归方程也可用迭代方式来求解,这时很自然地避免了重复计算。尽管迭代程序与避免重复计算的递归程序有相同的复杂性,但迭代程序不需要附加的递归栈空间,因此将比避免重复计算的递归程序更快。
http://www.ccidedu.com/art/1925/20040927/159647_1.html
动态规划的实质是分治思想和解决冗余,因此,动态规划是一种将问题实例分解为更小的、相似的子问题,并存储子问题的解而避免计算重复的子问题,以解决最优化问题的算法策略。在求解过程中,该方法也是通过求解局部子问题的解达到全局最优解,但与分治法和贪心法不同的是, 动态规划允许这些子问题不独立,也允许其通过自身子问题的解作出选择,该方法对每一个子问题只解一次,并将结果保存 起来,避免每次碰到时都要重复计算。子问题的重叠性 动态规划算法的关键在于解决冗余,这是动态规划算法的根本目的。
http://tag.csdn.net/Article/0c26f380-46bd-4119-a86f-f1628fdaf93a.html
动态规划,一般可用于 求最优解。
全局最优解可通过一系列局部最优解得到。某个局部最优解可通过一系列其他局部最优解得到。有初始的局部最优解。
一般可以找到递推公式。但根据情况,很多时候直接使用递归会有大量重复计算的子问题,所以为了去除重复计算,一般将子问题的解存储下来,此时要计算所有子问题的解。但有时,递推可以避免计算所有的子问题,而效率更高些。
问题增加一个限制,状态空间增加一维。