动态规划原理(算法导论)
适合动态规划求解的最优化问题应该具备两个要素: 最优子结构和子问题重叠。
1、 最优子结构
要使用动态规划算法,首先要具有最优子结构的性质,即原问题的最优解包含子问题的最优解。通过逐步求解子问题,最终得到原问题的最优解。在寻找最优子结构的过程中,需要做出一些假设,比如在第一次做出选择时,需要假设已经知道了哪种选择会得到最优解,于是,在获得最优解的选择后,分析通过这次选择后会产生哪些子问题,同时保持子问题空间尽可能简单。在原问题涉及的子问题中,确定最优解使用哪些子问题时,需要考虑多少中选择。比如,在矩阵链相乘问题中,Ai..j需要考虑两个子问题,一共有j-i中选择。
对于动态规划的运行时间,可以使用子问题的总数以及每个子问题需要考察多少中选择这两个因素来估算。比如,矩阵链相乘问题,共有O(n2)个子问题,每个子问题最多需要n-1种选择,因此,运行的时间复杂度为:O(n3).
下面讨论两个问题:无权最短路径和无权最长路径问题,分析这两个问题是否具有最优子结构性质。
1)无权最短路径。假如节点s到节点e的最短路径为p(s!=e)。设m为p上的一点,这时将p分割成两段,设s-->m的路径为p1,m-->e的路径为p2。现假设s->m的最短路径不是p1,而是p1’。这时,p1’+p2<p,这与p已经是最短路径矛盾。因而,若p是s-->e的最短路径,则p1,p2也是在s-->m和m-->e的最短路径。故无权最短路径问题具有最优子结构性质。
2)无权最长路径。假如现在有如下有向图。节点A到节点D的最长简单路径为:A-->B--D。将此问题分解成两个子问题,A-->B和B-->D。而A-->B的最长简单路径为A-->C-->D-->B。而B--D的最长简单路径为B-->A-->C-->D。若结合A-->B和B-->D的最长简单路径则得到A-->C-->D-->B-->A-->C-->D。而这个路径已经不是简单路径了。因而无权最短路径不具有最优子结构的性质,实际上此问题是NP完全的。
上面两个问题最大的区别在于,虽然最短路径问题和最长路径问题都用到了两个子问题,但是最长路径的两个子问题是相关的,而最短路径问题的子问题之间是无关的。子问题无关是指:同一原问题的一个子问题的解不影响另一个子问题的解。在最长路径问题中,A-->B的最长路径用到了B-->D的节点D。这导致B-->D的最长路径中不能使用D,从而发生了冲突。因而这两个子问题是相关的。更通俗的说,就是在求解一个子问题时用到了某些资源,导致这些资源在求解其他子问题时不可用。
2、子问题重叠
要使用动态规划算法,需要满足子问题的空间足够小,也就是在递归的过程中会多次求解相同的子问题,而不是总是生成新问题,子问题的总数控制在输入规模的多项式函数级别比较合适。在递归的过程中,总是需要求解相同的子问题称为子问题重叠。动态规划算法通过对相同子问题只求解一次,然后再次出现相同子问题时通过查表获得子问题的解。
在动态规划中的递归方案中,采用备忘机制主要是为了解决子问题重叠。因为每次在前面的操作中,已经对某个子问题进行了求解,因而,在后面需要求解同样子问题时,这时就可以使用前面已经求得的结果。这样更很大程度上降低计算复杂度。但是,增加了内存空间来保存子问题的结果。归根结底就是:用空间换时间。
由于每个子问题都必须求解一次,自底向上的动态规划算法要比自顶向下的递归备忘算法快,因为自底向上的算法没有递归调用的开销,不需要保存现场然后进行跳转。但是,在某些情况下,如子问题空间中的某些子问题不必完全求解时,递归算法的速度可能会更快,因为递归算法只求解必须要用到的子问题,而自底向上的算法会将所有的子问题全部求解。