以计算斐波那契数列为例说说动态规划算法(Dynamic Programming Algorithm Overlapping subproblems Optimal substructure Memoization Tabulation)
动态规划(Dynamic Programming)是求解决策过程(decision process)最优化的数学方法。它的名字和动态没有关系,是Richard Bellman为了唬人而取的。
动态规划主要用于解决包含重叠子问题的最优化问题,其基本策略是将原问题分解为相似的子问题,通过求解并保存重复子问题的解,然后逐步合并成为原问题的解。动态规划的关键是用记忆法储存重复问题的答案,避免重复求解,以空间换取时间。
用动态规划解决的经典问题有:最短路径(shortest path),0-1背包问题(Knapsack problem),旅行商人问题(traveling sales person)等等。
(注:背包问题分为两种:若物体不可分割,则称为0-1背包问题,比如拿一块金砖;若物体可以分开,则称为一般背包问题,比如拿多少克大米。一般背包问题可以用贪心算法解决。贪心算法在每个阶段即可找出当前最优解,每个阶段的最优状态都是由上一个阶段的最优状态得到的。)
可以采用动态规划来求解的问题需要具有以下两个主要特征:
1)重叠子问题(Overlapping Subproblems):有些子问题会被重复计算多次。
2)最优子结构(Optimal Substructure):问题的最优解可以从某个子问题的最优解中获得。
下面以计算斐波那契数列为例,看看动态规划算法的实现过程。
以下是1-5的斐波那契数列递归树:
fib(5)
/ \
fib(4) fib(3)
/ \ / \
fib(3) fib(2) fib(2) fib(1)
/ \ ¦ ¦ ¦
fib(2) fib(1) 1 1 1
¦ ¦
1 1
可以看出,fib(5)是由fib(4)和fib(3)相加而成,fib(4)则是由fib(3)和fib(2)相加而成,等等。其中,fib(3)要计算2次,fib(2)要计算3次。这里面进行了很多重复的计算。
按之前博客中提到的递归方法来计算这个斐波那契数列(用递归方法计算斐波那契数列),在此基础上加入print("fib called with",n)语句后,看看fib函数的调用情况:
def fib(n):
print("fib called with",n) #看调用了哪个fib函数,也就是说看计算了斐波那契数列的第几项
if n<2:
return n
else:
return (fib(n-1) + fib(n-2))
计算一下斐波那契数列的第5项试试:
print(fib(5))
运行结果如下:
fib called with 5
fib called with 4
fib called with 3
fib called with 2
fib called with 1
fib called with 0
fib called with 1
fib called with 2
fib called with 1
fib called with 0
fib called with 3
fib called with 2
fib called with 1
fib called with 0
fib called with 1
5
可以看出一共进行了15次调用,其中fib(3)被计算了2次,fib(2)被计算了3次。
而使用动态规划算法来计算这个斐波那契数列,运行则会快一些。代码如下:
def fastFib(n,memo): #memo是设置的一个字典
print("fib1 called with",n)
if not n in memo: #如果斐波那契数列的第n项数值不在字典里,那么用递归方式计算该值,并把该值放入字典中
memo[n]=fastFib(n-1,memo)+fastFib(n-2,memo)
return memo[n] #如果斐波那契数列的第n项数值在字典里,那么直接返回字典里的该项数值 def fib1(n):
memo={0:0,1:1} #初始化一个字典
return fastFib(n,memo)
同样也计算一下斐波那契数列的第5项试试,运行结果如下:
fib1 called with 5
fib1 called with 4
fib1 called with 3
fib1 called with 2
fib1 called with 1
fib1 called with 0
fib1 called with 1
fib1 called with 2
fib1 called with 3
5
可以看出一共进行了9次调用,在进行过一次计算之后,后面的调用都是直接到字典里去获取该值即可。
有两种不同的方式来存储数值:
1) 默记法(从上到下)/ Memoization (Top Down):设置一个数组,当需要子问题的解时,先去这个数组中查找。如果此问题之前已经求过解,那么就直接返回该值,如果此问题之前并未求过解,那么就计算该值并把结果放入数组中,以备后用。
2) 表格法(从下到上)/ Tabulation (Bottom Up):用迭代法建立一个表格,从该表格中返回所需的值。
那么到底应该用默记法还是表格法呢?
如果需要求解所有的子问题,那么表格法往往要比默记法好。这是因为表格法没有递归的额外消耗,并且使用预先分配好的数组(preallocated array),而不是哈希图(hash map)。
如果只是需要求解其中一些子问题,那么默记法则要好些。
参考:麻省理工学院公开课:计算机科学及编程导论(第13集)
以计算斐波那契数列为例说说动态规划算法(Dynamic Programming Algorithm Overlapping subproblems Optimal substructure Memoization Tabulation)的更多相关文章
- 使用并行的方法计算斐波那契数列 (Fibonacci)
更新:我的同事Terry告诉我有一种矩阵运算的方式计算斐波那契数列,更适于并行.他还提供了利用TBB的parallel_reduce模板计算斐波那契数列的代码(在TBB示例代码的基础上修改得来,比原始 ...
- Android NDK入门实例 计算斐波那契数列一生成jni头文件
最近要用到Android NDK,调用本地代码.就学了下Android NDK,顺便与大家分享.下面以一个具体的实例计算斐波那契数列,说明如何利用Android NDK,调用本地代码.以及比较本地代码 ...
- 用递归方法计算斐波那契数列(Recursion Fibonacci Sequence Python)
先科普一下什么叫斐波那契数列,以下内容摘自百度百科: 斐波那契数列(Fibonacci sequence),又称黄金分割数列.因意大利数学家列昂纳多·斐波那契(Leonardoda Fibonacci ...
- shell脚本计算斐波那契数列
计算斐波那契数列 [1,1,2,3,5,8,,,,,] #!/bin/bash n=$ num=( ) i= while [[ $i -lt $n ]] do let num[$i]=num[$i-] ...
- java 递归及其经典应用--求阶乘、打印文件信息、计算斐波那契数列
什么是递归 我先看下百度百科的解释: 一种计算过程,如果其中每一步都要用到前一步或前几步的结果,称为递归的.用递归过程定义的函数,称为递归函数,例如连加.连乘及阶乘等.凡是递归的函数,都是可计算的,即 ...
- 关于Haskell计算斐波那契数列的思考
背景 众所周知,Haskell语言是一门函数式编程语言.函数式编程语言的一大特点就是数值和对象都是不可变的,而这与经常需要对状态目前的值进行修改的动态规划算法似乎有些"格格不入", ...
- php实现记忆化递归--以斐波那契数列为例(还是以边学边做为主,注重练习)
php实现记忆化递归--以斐波那契数列为例(还是以边学边做为主,注重练习) 一.总结 1.递归不优化的话,30层开外就有点吃力了 2.php因为定义变量的时候不用定义变量类型,所以数组里面的类型也是p ...
- X86汇编——计算斐波那契数列程序(详细注释和流程图说明)
X86汇编实现斐波那契数列 程序说明: 输入斐波那契数列的项数, 然后依次输出斐波那契数列, 输入的项数小于256且为数字, 计算的项数不能超过2^16次方, 输入失败是 不会回显数字 因为存结果是A ...
- python计算斐波那契数列
斐波那契数列就是黄金分割数列 第一项加第二项等于第三项,以此类推 第二项加第三项等于第四项 代码如下 这一段代码实现fib(n)函数返回第n项,PrintFN(m,n,i)函数实现输出第i项斐波那契数 ...
随机推荐
- H5 文字属性的缩写
05-文字属性的缩写 abc我是段落 <!DOCTYPE html> <html lang="en"> <head> <meta char ...
- DAG路径覆盖模型
概述 路径覆盖模型的特点是DAG中每个点经过且只经过一次,且一条路径覆盖路径上的所有点. 将每个点拆为\(x\)和\(x'\),暂不考虑其实际意义.然后连边\(S\rightarrow x\),\(x ...
- myeclipse使用hibernate5框架load延迟装载对象报错_$$_javassist_0 cannot be cast to javassist.util.proxy.Proxy
jar包问题,将hibernate-core-5.0.12.Final.jar删除,换为hibernate-core-4.2.3.final.jar搞定.注意项目运行过后可能删不掉jar包,只需关闭m ...
- 关于php,python,javascript文件或者模块导入引入的区别和联系
前言: 我们经常看到编程语言之间,文件或者模块的引来引去的,但是他们在各个编程语言之间有什么区别和联系呢? 1.javascript (1).全局引入方式: <script src='xxxxx ...
- openstack-虚拟化模型
一. 虚拟化模型 1.虚拟化模型 图1 虚拟化模型 图2 KVM架构 2.KVM模块 处理器虚化 内存虚化 3.QEMU设备模型 其它虚化(网卡.声卡.显卡等)
- java总结:double取两位小数的多种方法
1.方法一 四舍五入: import java.math.BigDecimal; double f = 111231.5585; BigDecimal b = new BigDecimal(f); d ...
- mysql-SQL Error: 1205, SQLState: 41000
mysql-SQL Error: 1205, SQLState: 41000——CSDN问答频道https://ask.csdn.net/questions/176492 mysql-SQL Erro ...
- ascii、unicode、utf-8、gbk 区别
原文:https://blog.csdn.net/u010262331/article/details/46013905 ASCII:遇上0×10, 终端就换行: 遇上0×07, 终端就向人们嘟嘟叫: ...
- laravel 项目表单中有csrf_token,但一直报错419错误 解决redis连接错误:MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persi
laravel 项目表单中有csrf_token,但一直报错419错误,因为项目中使用到Redis缓存,在强制关闭Redis后出现的问题,查询laravel.log文件查找相关问题 安装redis后在 ...
- v-router几种定义方式
第一种 const router = new VueRouter({ routes: [{ path: '/newSongs', component: require('../views/NewSon ...