码迷,mamicode.com
首页 > 其他好文 > 详细

动态规划(1)使用斐波那契数列引入了动态规划的概念

时间:2018-03-21 13:46:26      阅读:168      评论:0      收藏:0      [点我收藏+]

标签:针对   lob   lex   ble   body   one   研究   .com   using   

9-1 使用斐波那契数列引入了动态规划的概念

一、计算斐波那契数列的第 $n$ 项数值

1、斐波那契数列的定义

斐波那契数列是通过"递归"定义的,通过这个递归关系式,我们可以知道斐波那契数列中任意一个位置的数值。

$$
\begin{equation}\begin{split}
F(0) & = 0,\
F(1) & = 1,\
F(n) & = F(n-1) + F(n-2),\
\end{split}\end{equation}
$$

2、第 1 版 Python 代码实现:使用斐波那契数列的定义式子递归实现

很容易地,我们能写出下面的代码:

def fib(n):
    if n == 0:
        return 0
    if n == 1:
        return 1
    return fib(n - 1) + fib(n - 2)

说明:

  1. 代码本身用于计算是没有问题的,但是仔细研究,我们就会发现,我们虽然使用递归实现了斐波那契数列在任意位置的值的计算,但是,如果要我们自己计算的话,肯定不会这样计算,因为太耗时了。

  2. 耗时的原因在于,在上述的递归实现中,存在大量的重复计算,例如:
    要计算 fib(4),就得计算 fib(3) 和 fib(2),
    要计算 fib(3),就得计算 fib(2) 和 fib(1),
    此时 fib(2) 就被重复计算了,下面是一张图,展示了部分重复计算的过程。

技术分享图片

  1. 要解决上一步的问题,就要避免重复计算,我们可以引入一个 memo 数组,用于存入已经计算过一次的 fib 的值,下一次需要这个值的时候,再从中取,下面是代码实现。

3、第 2 版 Python 代码实现:加入了记忆化搜索,即使用了缓存数组,以避免重复计算

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)

4、第 3 版 Python 代码实现:虽然很简单,但是我们就可以称之为“动态规划”的解法

这个版本是最接近我们自己去计算斐波那契数列的第 $n$ 项。想一想的确是这样,聪明的你一定不会递归去计算波那契数列的,因为我们的脑容量是有限,不太适合做太深的递归思考,虽然计算机在行递归,但是我们也没有必要让计算机做重复的递归工作。

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]

二、什么是“记忆化搜索”

针对一个递归问题,如果它呈树形结构,并且出现了很多”重叠子问题”,会导致计算效率低下,“记忆化搜索”就是针对”重叠子问题”的一个解决方案,实际上就是”加缓存避免重复计算”。

三、什么是“动态规划”

(1)比较“记忆化搜索”与“动态规划”

由上面的介绍我们就可以引出动态规划的概念:

  • "记忆化搜索"或者我们称"重叠子问题"的加缓存优化的实现,我们的思考路径是"自顶向下"。即为了解决数据规模大的问题,我们“假设”已经解决了数据规模较小的子问题。
  • “动态规划”就是上述"循环版本"的实现,我们思考问题路径是"自下而上"。实际上,我们是先“真正地”解决了数据规模较小的问题,然后一步一步地解决了数据规模较大的问题。

(2)“动态规划”的官方定义

下面我们给出“动态规划”的官方定义:

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.

将原问题拆解成若干子问题,同时保存子问题的答案,使得每个子问题只求解一次,最终获得原问题的答案。

(3)针对“动态规划”问题的一般思考路径

我们通常的做法是:使用记忆化搜索的思路思考原问题,但是使用动态规划的方法来实现。即“从上到下”思考,但是“从下到上”实现。

技术分享图片

四、总结

对于一个递归结构的问题,如果我们在分析它的过程中,发现了它有很多“重叠子问题”,虽然并不影响结果的正确性,但是我们认为大量的重复计算是不环保,不简洁,不优雅,不高效的,因此,我们必须将“重叠子问题”进行优化,优化的方法就是“加入缓存”,“加入缓存”的一个学术上的叫法就是“记忆化搜索”。

另外,我们还发现,直接分析递归结构,是假设更小的子问题已经解决给出的实现,思考的路径是“自顶向下”。但有的时候,“自底向上”的思考路径往往更直接,这就是“动态规划”,我们是真正地解决了更小规模的问题,在处理更大规模的问题的时候,直接使用了更小规模问题的结果。

动态规划(1)使用斐波那契数列引入了动态规划的概念

标签:针对   lob   lex   ble   body   one   研究   .com   using   

原文地址:https://www.cnblogs.com/liweiwei1419/p/8616113.html

(0)
(1)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!