从分治到动态规划,中间隔了重叠子问题(overlapping subproblems)
记忆化体(memo)存放的是子问题(subproblems)的 solution
stored subproblem solutions
动态规划仍然是一种暴力算法 brute force(也即遍历所有的可能性),只是 memo 的存在避免了重复计算;
0. 动态规划其名
“动态规划”(dynamic programming)一词源于研究优化问题的数学理论,这一名称其实与计算机领域中常用的“动态”(dynamic)和“编程(programming)”没有任何关系。动态规划的发明者贝尔曼称,
- dynamic:是因为单词本身的魅力,而不关于其内在含义;
- programming 在研究优化的领域中表示“搜索最优程序”的意思;
所以,不要从字面上去理解动态规划,而是从其具体的操作上。
1. 动态规划与分治
从斐波那契数列开始讲起,斐波那契数列的递归公式主要有两个子问题(subproblems),这使得其看起来似乎是一个分治问题。其与分治问题的主要不同在于子问题之间存在一些复杂依赖(tangled dependencies,计算 Fn 时需要计算 Fn−1 和 Fn−2 ,而当开始计算 Fn−1 时,也会首先计算 Fn−2 )。换句话说,我们面对的是一组相互重叠的子问题。
大体上看,动态规划法语分治法具有类似的处理方式(但仍有很大差别)。使用动态规划的算法通常先把问题分割成若干子问题(subproblem,对于分治法而言,比如二分,一刀两断,井水不犯河水),然后再利用这些答案得出整个问题的最终答案。不过,动态规划法和分治法在分割子问题的方式上存在不同(或者说是问题自身的特性使然)。
在动态规划中,有些子问题的计算结果会用于多个子问题的解题过程,比如斐波那契的 F3 ,可用于求解 F4 ,以及 F5 。因此,在对子问题进行一次计算的情况下,重复利用其结果就能提高运算速度。为了实现这一思路,需将子问题的答案预先保存到内存。这种保存答案的内存区域就是“缓存”(cache),能够重复利用两次以上的子问题称为“重复子问题”(overlapping subproblems)。
2. 动态规划与贪心算法
动态规划的核心其实是一个决策顺序的问题,我们所做的每一个选择都会导致一个新的局面,所以我们需要根据自己所期望的局面找出最好的选择顺序。这看起来似乎有点像是贪心算法所做的事情,但贪心算法只是基于当下做出最好的选择,并没有考虑全局。动态规划,即是避免短视行为,对未来的影响也需予以考虑。
3. 缓存与制表法
函数的引用透明性(referential transparency)
也即缓存对应的 map(或者一个一维或二维表),实现的是同一个输入(key),同一个输出(value),而不可能出现同一个输入,可以得到不同的输出,也即输出结果的不确定性。