动态规划入门----斐波那契数列(搬

视频地址:https://www.youtube.com/watch?reload=9&v=vYquumk4nWw

可以通过三个方式写出斐波那契数列:递归,备忘录,自底向上

动态规划入门----斐波那契数列(搬

1.递归

def fib(n):
    if n == 1 or n == 2:
        result = 1
    else:
        result = fib(n - 1) + fib(n - 2)
    
    return result

使用递归的会产生大量的重复计算,比如要计算fib(5),就要多次计算fib(3),fib(2)等之前计算过的值,浪费很多的空间和时间,当n值变得很大的时候,计算时间成指数增长,时间复杂度为O(2^n)。

动态规划入门----斐波那契数列(搬

那么怎么去优化这个程序呢?我们可以将其之前计算过的fib值使用一个列表或者其他容器存储起来,当之后计算需要使用的时候就直接使用,不再去重复计算。这里就引入了备忘录的解决方法。

# 函数传入两个参数,一个n和一个memo用来存储计算过的值
def fib(n, memo):
    # 若第n个fib的值已经在memo中,就直接返回
    if memo[n] != None:
        return memo[n]
    if n == 1 or n == 2:
        result = 1
    else:
        result = fib(n - 1) + fib(n - 2)
    # 每次计算的结果都存入memo
    memo[n] = result
    
    return result
# 伪代码,源码在下面

动态规划入门----斐波那契数列(搬

此时时间复杂度为O(2n+1) ---> O(n)远小于O(2^n)。还可以在备忘录的基础上继续优化,不使用递归来做,函数依然指传入第n个fib求解,函数中定义一个n+1长度的数组(列表),从1开始存储值,第一位是1,第二位也是1,这两项就是动态规划中的初始状态的值(边界状态),从第三项开始一直到n进行fib的计算,bottom_up[i] = bottom_up[i-1] + bottom_up[i - 2]就是动态规划要使用到的状态转移方程,最终返回列表顶部的数,自底向上方法也就是动态规划的方法求解的fib,时间复杂度也是O(n)。

动态规划入门----斐波那契数列(搬

   动规解题的一般思路

    1. 将原问题分解为子问题

  •     把原问题分解为若干个子问题,子问题和原问题形式相同或类似,只不过规模变小了。子问题都解决,原问题即解决(数字三角形例)。
  •     子问题的解一旦求出就会被保存,所以每个子问题只需求 解一次。

    2.确定状态

  •     在用动态规划解题时,我们往往将和子问题相关的各个变量的一组取值,称之为一个“状 态”。一个“状态”对应于一个或多个子问题, 所谓某个“状态”下的“值”,就是这个“状 态”所对应的子问题的解。
  •     所有“状态”的集合,构成问题的“状态空间”。“状态空间”的大小,与用动态规划解决问题的时间复杂度直接相关。 在数字三角形的例子里,一共有N×(N+1)/2个数字,所以这个问题的状态空间里一共就有N×(N+1)/2个状态。

    整个问题的时间复杂度是状态数目乘以计算每个状态所需时间。在数字三角形里每个“状态”只需要经过一次,且在每个状态上作计算所花的时间都是和N无关的常数。

    3.确定一些初始状态(边界状态)的值

    以“数字三角形”为例,初始状态就是底边数字,值就是底边数字值。

    4. 确定状态转移方程

     定义出什么是“状态”,以及在该“状态”下的“值”后,就要找出不同的状态之间如何迁移――即如何从一个或多个“值”已知的 “状态”,求出另一个“状态”的“值”(递推型)。状态的迁移可以用递推公式表示,此递推公式也可被称作“状态转移方程”。

--------------------- 本文来自 ChrisYoung1314 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/baidu_28312631/article/details/47418773?utm_source=copy

图片上的都是伪代码,源码放在下面了。

def fib_2(n, memo):
    if memo[n] is not None:
        return meno[n]
    if n == 1 or n == 2:
        result = 1
    else:
        result = fib_2(n - 1, memo) + fib_2(n - 2, memo)
    memo[n] = result
    return result

def fib_memo(n):
    memo = [None] * (n + 1)
    return fib_2(n, memo)
def fib_bottom_up(n):
    if n == 1 or n == 2:
        return 1
    bottom_up = [None] * (n + 1)
    bottom_up[1] = 1
    bottom_up[2] = 1

    for i in range(3, n + 1):
        bottom_up[i] = bottom_up[i - 1] + bottom_up[i - 2]
    
    return bottom_up[n]