对动态规划DP的深入理解

来源:互联网 发布:js笔试题及答案 编辑:程序博客网 时间:2024/05/17 17:58

dp其实是一种思想,而不是一种算法。它的核心就是一空间换时间。通过对所有状态的最优解的记录,再通过某种递推关系,得到最终解。用查询的方式代替重新计算,从而降低时间复杂度。说到时间复杂度,dp的时间复杂度有一个统一的表示:状态数*得到每个状态最优解的时间。由于这些核心的思想,决定了dp的题目应该具备一些特征

1、最优子结构:最优子结构就是局部最优能够决定整体最优。即:我们要解决的一个困难的大问题,能够划分成一个个容易解决小问题,通过解决这些小问题,和这些小问题与大问题的某种递推关系,得到大问题的解。

2、重叠子问题:这个是dp能过降低时间复杂度的原因。意思就是,在解决一个大问题的过程中,需要多次解决某个小问题。由于我们已经把小问题的解计算出来,并存在内存中,所以在需要用到的时候直接取出来就行。不需要再去计算,这样就能降低时间复杂度,空间换时间。

3、无后效性:我刚开始看到这个名次的时间是在《算法艺术与信息学竞赛》这本书上。我看了好久都没有明白,其实它的意思也很好理解。就是说当前要解决的子问题,只与前面比这个问题小的问题有关,对后来要解决的大问题没有影响。并且大问题只关心的是小问题的最优解,至于最优解是怎么来的,对大问题都没有影响。


对于dp解题的一般思考方向,也主要是从dp的思想和问题的结构开始的。《算法导论》上对此总结了dp的一般步骤

1、描述最优解结构;

2、递归定义最优解;

3、按照自底向上的方式计算最优解的值;

4、按计算的结果构造一个最优解。


感觉着个总结还是挺好懂的,我想谈谈我自己对解决问题中的一些想法和一些注意值得注意的地方

1、状态的确定,对于一个dp题,首要的任务就是描述问题状态,也就是最优子结构。状态的确定其实并不简单,自己感觉,正确的定义状态(最有子结构),这个dp问题就解决了一半。但是状态的确定还是有一定的思考方向的:模仿一些经典的dp问题中状态的定义;仔细分析问题,观察大问题能够怎样的分解,这种分解是不是满足dp的一些性质,初始状态是什么;当发现定义的状态没法转移时,可以考虑将状态细化,即:增加数组维度;通过确定的状态推出状态转移方程。

2、递推的方向,dp解决的问题是将大问题分解为小问题,状态和状态转移方程一旦确定,那么接下来就是要从小问题推出大问题。显然,是从小问题开始递推,小问题是什么,其实也是一个值得去思考的东西。特别是用递推的方法写代码时,如何去表示一个小问题,递推过程中,如何满足当要解决某个问题时与之相关的小问题都已经被解决了

3、递推的形式,递推的形式可以总结为三种:(1)记忆化搜索;(2)我为人人;(3)人人为我;基本上dp的题目这三种形式都可以用,记忆化搜索是递归的,递归的自底向上的思想符合dp将大问题分解为小问题,先解决小问题,再用小问题推出大问题的解的过程。所以这种形式的代码比较简单好写,而且易懂。但缺点是比递推要耗时一点。后面两种是递推的,具体意思,一看名词就能够理解到,这里就不一一述说啦。(作为转载者的博主,怎能容许如此敷衍的话?以下为博主加)(2)指的是以我的值去更新其它人的值,也就是在轮到我的时候,要做的是去更新别人。(3)是最常见的形式,在轮到我做事的时候,我要通过以前的状态来求到我的最佳状态,也就是利用别人跟新我自己。

4、空间的优化,由于dp是用时间换空间,在降低时间复杂度的同时,也增加了空间复杂度。这样,在解决问题中就有可能出现,超内存的情况。所以dp中要注意空间的优化。


一些经典的例子是01背包和LCS的优化,利用滚动数组。dp的空间优化,主要看状态转移方程。看下当前状态的最优解与前面的哪些状态有关,那些无关的部分可以不要,直接用滚动数组的方式将其覆盖就行。


总之,dp的问题还是比较活的,真正能把握的东西不是很多,需要做大量的题目来增加对dp的敏感性。更重要的是需要自己 去思考,去问为什么。在做题中去积累一些经验。


以上经验之谈转载自http://blog.csdn.net/zyjhtutu/article/details/38677191