转【算法之动态规划(五)】DP规划思想学习:从《算法导论》到《算法设计》
像所有的新手一样,对一种算法思想的理解需要经历从肤浅(流于表面形式)到逐渐触摸到本质的过程。为什么说逐渐触摸到本质,是因为很多时候你并不确定一个解释是不是最本质的,有时候会有好几个等价的解释,各自在不同的场景下具有。
动态规划经典题集转]动态规划与排列组合,比如对动态规划(DP)的理解,一开始我理解为递推,但实际上这是最肤浅的理解,对于如何在特定的问题中找到递推关系毫无帮助和。换言之,这只是一个描述性的总结,而不是一个建设性的总结,不含方。
做(看)了一些题目之后我开始总结关于How的方法,怎样寻找到递推关系(递推关系蕴含着子问题)。得到一个简单的观察,如果一个问题里面含有n这个变量,考虑把n变成n-1的情况。
当然,这个方法常特殊(狭窄)的。实际上更具一般性的方法是不仅可以从n-1后面切一刀,还可以在任意地方切(譬如切成两个n/2规模的),以任意标准切(比如像快排那样的)。所有考虑各种切法中哪种能够最有利于建立子问题是有帮助的。
这个我姑且把它叫做通过直接切分问题来寻找子问题。
可是。这还是特殊了。因为显然这个方法不能解决所有的DP问题,连多数DP问题都未必能解决。譬如大家熟悉的最大(小)和连续子序列问题,就难以通过这种方法求解(可行,但思维难度较大。);再譬如上次Lee给的敲石头问题,以及DD出的不听话的机器人问题。因此,在知道了这几个问题的解法之后我继续思考这些解法里面是不是蕴含着更具一般性的解题方法。
看起来我找到了一个,就是分类讨论法,具体做法是:首先,写出可行解的一般形式,譬如a1,a2,...,an。然后对其中的某个不确定的ai进行讨论。譬如旅行商问题里面对下一个城市进行讨论。当不确定时,讨论。讨论的每个分支都带来了进一步的确定性,从而将问题成一个子问题。
然后Lee提到,这个方法是自顶向下的,有时候不适于思考,譬如对敲石头问题。并提到一种自底向上,着重于构造状态的外推法。
于是,到目前为止,DP的一般性式思考方法就已经有了三种:
1.通过直接对问题切分来探索子问题。
3.通过建立状态来自底向上地推导最终解。
可是,我心里还是不踏实,因为离散的知识是极其不利于记忆的,需要更多的才能在运用的时候不由自主的联想起来,而且也容易遗忘。每次做DP题的时候,我都得把好几种指导性的思一一费劲从脑袋里翻出来,然后尝试。实在很不爽。虽然DP题也许并没有万用的解题手法,但我还是希望能够尽量提取出不同手法之间的本质联系,如果能够将一组看上去离散的知识点统一在一个更具一般性,更本质的知识点下面,我们的知识树就多出了一个根节点,于是下次提取的时候只要提取出那个根节点,下面的几个子节点就会一一乖乖闪现,极大的降低记忆的复杂性。
那么,三种手法的本质联系到底是什么呢?有一次在上走的时候我想出了一种解释。它也许不是最本质的,也许大牛们早就想到过,但是我觉得至少有两个好处:
1.它涵盖以上三种手法,通过增加一个根节点,减少知识的记忆复杂性和易提取性。
2.归纳抽象的过程本身就是一种锻炼,锻炼的是归纳抽象的能力。
动态规划经典题集这个解释就是:大多DP问题的可行解的形式是一个排列组合(典型的——旅行商问题、最短径问题)。大家都知道,穷举一个规模为N的排列组合复杂性是a^n的,也就是组合复杂性。而求解DP问题的核心步骤:发现“子问题”,这个“子问题”实际上就是对应最终解的那个排列组合的某个子排列组合(某种子集);而这里的“子排列组合”的数目则往往是多项式的(silwile,,指出并非总是如此),这就是为什么一个组合复杂性的穷举问题可以DP 优化为多项式复杂度的问题。将重复出现的子排列组合对应的子问题的解缓存起来,就是DP的缓存优化了。
此外,这一解释也提供了如何探索子问题的一个通用方案:寻找形式相同的子排列组合。还是拿最大和连续子序列说事,其解的形式是:A[i],A[i+1],..,A[j-1],A[j],其中i,j不确定。那么如何得到形式相同的子组合呢?首先讨论A[j],为什么要讨论,因为只要A[j]不确定,A[j-1]就不确定,就拿不出子组合来。对于一个确定的A[j0],解的可能性为A[i],A[i+1],..,A[j0-1],A[j0]。其最优解依赖于子组合 A[i],A[i+1],..,A[j0-1],到这里子问题就不请子现了:A[i],A[i+1],..,A[j0-1],A[j0]和 A[i],A[i+1],怎么开通腾讯图书如何在微时代打造出一本畅销书..,A[j0-1]的形式相同,意味着它们是同一个问题的不同阶表示。
当然,由于这个指导思想一般性大了点,所以实际问题中往往没有前面提到的三种手法寻找方案来得快——众所周知的是,越特定的手题面虽然越窄,但如果题目对口了解决起来也越快。但它至少有两个好处(前面说过了)。所以考虑一般性和特殊性的手法都是有帮助的。
类似的,敲石头问题也可以通过这种手法来探索子问题。至少目前我做过的DP题似乎都可以借助这种手法来探索,当然,刚才说过了哈7》惊险二连冠 《格列佛游记》,未必是最快的,所以也许可以考虑用来做后备方案,当其它方案没有头绪的时候试试。
我的解题经验还很有限,所以不清楚这个手法的覆盖范围有多广。实际上一个更广的领域是组合优化。更提到的很像。但针对的问题就不仅止于DP了。
你说得没错。用空间换时间是DP的一个重要性质,事实上,它还是许多算法的一个性质。所以说,“用空间换时间”并没有“充分”地描述DP的特点,更没有对“如何探索一个DP问题的解”提供建设性的帮助。对于后者,如何寻找递推关系或“子问题”才是重点,一旦找到了子问题之后通过缓存子问题的解来优化复杂度就相对比较trivial了。
DP的思想理解起来容易,一开始的时候我看了几本书的DP章节,觉得,哦,这很容易,递推嘛。但真正自己做题的时候,发现寻找递推关系是最困难的一步,non-trivial的DP题里面递推关系并不是显而易见的。
TopLang里面也有人推荐《算法艺术与信息学竞赛》,但提到里面的题目偏难,不适合初学者。作为题集应该还是不错的。据说几本众所周知的经典算法书里面最适合程序员学习的是《AlgorithmDesign》,以下引一段g9的介绍:
我也喜欢AlgorithmDesign这本书。个人觉得非常适合初学者和程序员。书一开始就给出稳定婚姻问题。从解决问题的角度入手,层层递进地怎么设计,引出算法设计的一般方法。然后用几道常见题目引入常用算法的设计思。奠定总纲后,后面的书就从不同的角度深化算法设计的方法。例题也有趣。个人相当喜欢从设计到分析的思。这种方法给读者(至少是程序员)诱人的动机。毕竟很多人读算法书是为了写出更强大的程序。尤其像我这种老人家,NADD症状严重,尤其需要令人信服的动机,不然就老走神。而且作者强调设计,反而提供了钻研算法分析的充分理由,很好地解释了要设计出正确高效的算法,形式化的分析是必要手段。为什要扩展?为什么要到处一堆引理?为什么要追究复杂度?为什么要泛化?为什么要特化?这些看来学究的行为自然地有了现实基础。加上这本书写得晓畅通透,就算你只看书不动手,也可以获得好舒服,原来我也可以设计出这些算法啊。一点都不难嘛这种类似后的波浪状快感和。:-D
DP的思想理解起来容易,一开始的时候我看了几本书的DP章节,觉得,哦,这很容易,递推嘛。但真正自己做题的时候,发现寻找递推关系是最困难的一步,non-trivial的DP题里面递推关系并不是显而易见的。[/quote]
又碰到一个有上思考习惯滴。我想作者想描述的本质可能在于,如何识别一个问题是DP可以用DP解决的,所以顺着这种想法,自然就像探究DP的本质,因为一旦探究了DP的本质就可以迅速的对需要解决的问题进行定位,然后解决。比如一个很难的题目,我们告诉应试者可以用DP解决,也许他很快能想到答案,如果我们不说呢,也许他搞不定。认清DP的本质就有助于我们判断某个问题是否用DP可以解决,我想这是楼主的本意吧。raymond
来源:http://blog.csdn.net/cangchen/article/details/45045633