斐波那契数列是经过"递归"定义的,经过这个递归关系式,咱们能够知道斐波那契数列中任意一个位置的数值。python
\[ \begin{equation}\begin{split} F(0) & = 0,\\ F(1) & = 1,\\ F(n) & = F(n-1) + F(n-2),\\ \end{split}\end{equation} \]数组
很容易地,咱们能写出下面的代码:缓存
def fib(n): if n == 0: return 0 if n == 1: return 1 return fib(n - 1) + fib(n - 2)
说明:ide
代码自己用于计算是没有问题的,可是仔细研究,咱们就会发现,咱们虽然使用递归实现了斐波那契数列在任意位置的值的计算,可是,若是要咱们本身计算的话,确定不会这样计算,由于太耗时了。优化
耗时的缘由在于,在上述的递归实现中,存在大量的重复计算,例如:
要计算 fib(4),就得计算 fib(3) 和 fib(2),
要计算 fib(3),就得计算 fib(2) 和 fib(1),
此时 fib(2) 就被重复计算了,下面是一张图,展现了部分重复计算的过程。idea
memo = None def _fib(n): if memo[n] != -1: return memo[n] if n == 0: return 0 if n == 1: return 1 memo[n] = _fib(n - 1) + _fib(n - 2) return memo[n] def fib(n): global memo memo = [-1] * (n + 1) return _fib(n)
这个版本是最接近咱们本身去计算斐波那契数列的第 \(n\) 项。想想的确是这样,聪明的你必定不会递归去计算波那契数列的,由于咱们的脑容量是有限,不太适合作太深的递归思考,虽然计算机在行递归,可是咱们也没有必要让计算机作重复的递归工做。spa
def fib(n): memo = [-1] * (n + 1) memo[0] = 0 memo[1] = 1 for i in range(2, n + 1): memo[i] = memo[i - 1] + memo[i - 2] return memo[n]
针对一个递归问题,若是它呈树形结构,而且出现了不少”重叠子问题”,会致使计算效率低下,“记忆化搜索”就是针对”重叠子问题”的一个解决方案,实际上就是”加缓存避免重复计算”。code
由上面的介绍咱们就能够引出动态规划的概念:递归
下面咱们给出“动态规划”的官方定义:it
dynamic programming (also known as dynamic optimization) is a method for solving a complex problem by breaking it down into a collection of simpler subproblems, solving each of those subproblems just once, and storing their solutions – ideally, using a memory-based data structure.
将原问题拆解成若干子问题,同时保存子问题的答案,使得每一个子问题只求解一次,最终得到原问题的答案。
咱们一般的作法是:使用记忆化搜索的思路思考原问题,可是使用动态规划的方法来实现。即“从上到下”思考,可是“从下到上”实现。
对于一个递归结构的问题,若是咱们在分析它的过程当中,发现了它有不少“重叠子问题”,虽然并不影响结果的正确性,可是咱们认为大量的重复计算是不环保,不简洁,不优雅,不高效的,所以,咱们必须将“重叠子问题”进行优化,优化的方法就是“加入缓存”,“加入缓存”的一个学术上的叫法就是“记忆化搜索”。
另外,咱们还发现,直接分析递归结构,是假设更小的子问题已经解决给出的实现,思考的路径是“自顶向下”。但有的时候,“自底向上”的思考路径每每更直接,这就是“动态规划”,咱们是真正地解决了更小规模的问题,在处理更大规模的问题的时候,直接使用了更小规模问题的结果。