动态规划
- DSA的优化
Make it work, make it right, make it fast.
— Kent Beck
- 前两步可以通过递归实现,优化可采取迭代,
- 从某种意义上来讲,动态规划就是通过递归,找出算法的本质,给出问题的初步解,再将其等效得转化为迭代的形式。
斐波那契数列的计算
- 法一:递归
- 效率过低,原因在于计算每一项时要重复计算已经计算过的数字,复杂度为O(2^n)
int fib(n) { return (2>n) ? n : fib(n-1) + fib(n-2); }
-
法二:记忆法(memoization)
- 将已计算过实例的结果制表查询
-
法三:动态规划(dynamic programming)
- 颠倒计算方向:由自顶向下递归,改为自底向上迭代
int f = 0, g = 1;//fib(0), fib(1) while(n--){ g = g + f;//不断迭代,使他们始终指向斐波那契数列中当前相邻的两项 f = g - f; } return g;
最长公共子序列
- 子序列(Subsequence):由序列中若干字符,按原相对次序构成
- 最长公共子序列(Longest Common Subsequence):两个序列公共子序列中的最长者
- 可能有多个,可能有歧义(同一个字符由一个序列中的不同位置的字符均可提供)
- 可能有多个,可能有歧义(同一个字符由一个序列中的不同位置的字符均可提供)
递归法
- 对于序列A[0, n]和B[0, m],LCS(A, B)有三种情况
- 递归基:若n = -1或m = -1,则取作空序列("")
- 减而治之:若A[n] = ‘X’ = B[m], 则取作LCS(A[0, n], B[0, m)) + ‘X’
- 分而治之:若A[n] != B[m], 则在 LCS(A[0,n], B[0,m))与LCS(A[0,n),B[0,m])中取更长者
- 复杂度分析
- 单调性:无论如何,每经过一次比对,原问题的规模必可减小,具体的,作为输入的两个序列,至少其一的长度缩短一个单位
- 最好情况(不出现分而治之情况)下,只需要O(n+m)时间
- 问题在于,(在分而治之情况下)原问题将分解为两个子问题,两个子问题的规模之和,大致是原问题的两倍,且它们在随后进一步导出的子问题,可能雷同
- 复杂度O(2^n)
迭代法
- 采用动态规划的策略,只需要O(n*m)时间即可计算出所有的子问题
- 将所有的子问题(假想地)列成一张表
- 颠倒计算方向,从LCS(A[0], B[0])出发,依次计算出所有项
总结
- 递归:设计出可行且正确的解
- 动态规划:消除重复的计算,提高效率