动态规划与排列组合

动态规划与排列组合

By 刘未鹏(pongba)

C++的罗浮宫(http://blog.csdn.net/pongba)

TopLanguage(http://groups.google.com/group/pongba)

像所有的新手一样,对一种算法思想的理解需要经历从肤浅(流于表面形式)到逐渐触摸到本质的过程。为什么说"逐渐"触摸到本质,是因为很多时候你并不确定一个解释是不是最本质的,有时候会有好几个等价的解释,各自在不同的场景下具有启发。

比如对动态规划(DP)的理解,一开始我理解为"递推",但实际上这是最肤浅的理解,对于如何在特定的问题中找到递推关系毫无帮助和启发。换言之,这只是一个描述性的总结,而不是一个建设性的总结,不含方法论。

做(看)了一些题目之后我开始总结关于"How"的方法,怎样寻找到递推关系(递推关系蕴含着子问题)。得到一个简单的观察,如果一个问题里面含有n这个变量,考虑把n变成n-1的情况。

当然,这个方法是非常特殊(狭窄)的。实际上更具一般性的方法是不仅可以从n-1后面切一刀,还可以在任意地方切(譬如切成两个n/2规模的),以任意标准切(比如像快排那样的)。所有考虑各种切法中哪种能够最有利于建立子问题是有帮助的。

这个我姑且把它叫做通过直接切分问题来寻找子问题。

可是。这还是特殊了。因为显然这个方法不能解决所有的DP问题,连"多数"DP问题都未必能解决。譬如大家熟悉的"最大(小)和连续子序列"问题,就难以通过这种方法求解(可行,但思维难度较大。);再譬如上次Lee给的敲石头问题,以及DD出的不听话的机器人问题。因此,在知道了这几个问题的解法之后我继续思考这些解法里面是不是蕴含着更具一般性的解题方法

看起来我找到了一个,就是分类讨论法,具体做法是:首先,写出可行解的一般形式,譬如a1, a2, ..., an。然后对其中的某个不确定的ai进行讨论。譬如旅行商问题里面对"下一个城市"进行讨论。当不确定时,讨论。讨论的每个分支都带来了进一步的确定性,从而将问题转化成一个子问题。

然后Lee提到,这个方法是自顶向下的,有时候不适于思考,譬如对敲石头问题。并提到一种自底向上,着重于构造"状态"的外推法

于是,到目前为止,DP的一般性启发式思考方法就已经有了三种:

1. 通过直接对问题切分来探索子问题。
2. 通过对可行解的分类讨论来探索子问题。
3. 通过建立"状态"来自底向上地推导最终解。

可是,我心里还是不踏实,因为"离散"的知识是极其不利于记忆的,需要更多的练习才能在运用的时候"不由自主"的联想起来,而且也容易遗忘。每次做DP题的时候,我都得把好几种指导性的思路一一费劲从脑袋里翻出来,然后尝试。实在很不爽。虽然DP题也许并没有万用的解题手法,但我还是希望能够尽量提取出不同手法之间的本质联系,如果能够将一组看上去离散的知识点统一在一个更具一般性,更本质的知识点下面,我们的知识树就多出了一个根节点,于是下次提取的时候只要提取出那个根节点,下面的几个子节点就会一一乖乖闪现,极大的降低记忆的复杂性。

那么,上面三种手法的本质联系到底是什么呢?有一次在路上走的时候我想出了一种解释。它也许不是最本质的,也许大牛们早就想到过,但是我觉得至少有两个好处:

1. 它涵盖以上三种手法,通过增加一个根节点,减少知识的记忆复杂性和易提取性。
2. 归纳抽象的过程本身就是一种锻炼,锻炼的是归纳抽象的能力。

这个解释就是:大多DP问题的可行解的形式是一个排列组合(典型的——旅行商问题、最短路径问题)。大家都知道,穷举一个规模为N的排列组合复杂性是a^n的,也就是"组合复杂性"。而求解DP问题的核心步骤:发现“子问题”,这个“子问题”实际上就是对应最终解的那个排列组合的某个"子排列组合"(某种子集);而这里的“子排列组合”的数目则往往是多项式的(silwile,XuYoug9指出并非总是如此),这就是为什么一个组合复杂性的穷举问题可以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题似乎都可以借助这种手法来探索,当然,刚才说过了,未必是最快的,所以也许可以考虑用来做后备方案,当其它方案没有头绪的时候试试。

我的解题经验还很有限,所以不清楚这个手法的覆盖范围有多广。实际上一个更广的领域是"组合优化"。更上面提到的很像。但针对的问题就不仅止于DP了。

参考

1. 《Introduction To Algorithms》的DP章节。
2. 《Algorithms》的DP章节。
3. 《Algorithm Design Manual》的DP章节。
4. DD的《背包问题九讲
5. wikipedia
6. 网络搜索出来的一堆题目和讲解。(如《动态规划经典题集》)
7. TopLanguage上的帖子:矩阵也疯狂DPDPDPDP(二)

阅读更多

没有更多推荐了,返回首页