以计算斐波那契数列为例说说动态规划算法(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项斐波那契数 ...
随机推荐
- 【记一次pull request的惨痛教训】不可见的分隔符之Zero-with-space
问题描述: 我在修改 ctf-wiki 目录后进行 mkdocs build 去生成索引目录的时候报错: 然后我尝试定位到第 2 行和第 288 行,这些行我似乎并没有修改过啊. 未果,开始去找师傅解 ...
- set和multiset的用法
set #include<iostream> #include<algorithm> #include<cstdio> #include<string.h&g ...
- Shell脚本1
1Shell编程 Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁.Shell 既是一种命令语言,又是一种程序设计语言. Shell脚本 Shell 脚本(shell scr ...
- 多线程系列之八:Thread-Per-Message模式
一,Thread-Per-Message模式 翻译过来就是 每个消息一个线程.message可以理解为命令,请求.为每一个请求新分配一个线程,由这个线程来执行处理.Thread-Per-Message ...
- Java 常见编码格式——URL、Base64
数据编码 我们对数据进行编码是因为在某些情况下,不能直接传输中文字符或者其他字符,比如在设置http协议的头部信息或者cookie时,如果value有中文字符,那么就需要将中文字符使用某种编码方式进行 ...
- 解决ERR Client sent AUTH, but no password is set
在搭建cookies池时,需要将账号密码保存到redis,保存时报错:ERR Client sent AUTH, but no password is set 报错原因:Redis服务器没有设置密码, ...
- Linux基础命令和NAT技术
yum yellowdog updater,modified是一种用python写的基于rpm的管理工具 用于解决rpm包的依赖性 要安装编译工具 yum install gcc 库函数:静态库 ...
- MYSQL 表大小限制
MySQL 3.22限制的表大小为4GB.由于在MySQL 3.23中使用了MyISAM存储引擎,最大表尺寸增加到了65536TB(2567 – 1字节).由于允许的表尺寸更大,MySQL数据库的最大 ...
- checkbox保存和赋值
//货物信息中的表格内容 $.each(trG.find('td input,td select'),function(i,inp){ if($(inp).attr('type')=='checkbo ...
- MHA高可用及读写分离
一.MHA简介 二.工作流程 三.MHA架构图 四.MHA工具介绍 五.基于GTID的主从复制 六.部署MHA 七.配置VIP漂移 八.配置binlog-server 九.MySQL中间件Atlas