以计算斐波那契数列为例说说动态规划算法(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项斐波那契数 ...
随机推荐
- 内网IP外网IP的关联及访问互联网原理
首先解释一下“内网”与“外网”的概念: 内网:即所说的局域网,比如学校的局域网,局域网内每台计算机的IP地址在本局域网内具有互异性,是不可重复的.但两个局域网内的内网IP可以有相同的. 外网:即互联网 ...
- vue开发中regeneratorRuntime is not defined
我的项目是用vue提供的vue-cil脚手架生成的项目,但是当我在项目中使用async/await,编译代码的的时候报了regeneratorRuntime is not defined的错,我查过资 ...
- ACM/ICPC 2018亚洲区预选赛北京赛站网络赛D-80 Days--------树状数组
题意就是说1-N个城市为一个环,最开始你手里有C块钱,问从1->N这些城市中,选择任意一个,然后按照顺序绕环一圈,进入每个城市会有a[i]元钱,出来每个城市会有b[i]个城市,问是否能保证经过每 ...
- [Offer收割]编程练习赛97
链接 [https://hihocoder.com/contest/offers97/problems] 题意 题目1 : 放置矩形 时间限制:10000ms 单点时限:1000ms 内存限制:256 ...
- 软件工程(FZU2015) 学生博客列表(最终版)
FZU:福州大学软件工程 张老师的博客:http://www.cnblogs.com/easteast/ 经过前两周选课,最后正式选上课程的所有学生博客如下: 序号 学号后3位 博客 1 629 li ...
- Linux下php安装redis扩展(redis已经安装)
1. 下载需要的php操作redis的扩展包 (1).切换到 cd /usr/local/src (2). wget https://github.com/nicolasff/phpredis ...
- html问题汇总
1.textarea换行 textarea中无法使用<br/>换行,需要使用\n 2.textarea无法提交 我们知道表单中的元素需要设置name属性才能够提交,但是如果设置了disab ...
- [转帖]Linux分页机制之分页机制的演变--Linux内存管理(七)
Linux分页机制之分页机制的演变--Linux内存管理(七) 2016年09月01日 20:01:31 JeanCheng 阅读数:4543 https://blog.csdn.net/gatiem ...
- 将选中项的value值赋给select的title
$('select').change(function () { $(this).attr("title",$(this).find("option:selected&q ...
- @PathVariable
@PathVariable是用来对指定请求的URL路径里面的变量 eg: Java代码 @RequestMapping(value = "form/{id}/apply", met ...