背景

众所周知,Haskell语言是一门函数式编程语言。函数式编程语言的一大特点就是数值和对象都是不可变的,而这与经常需要对状态目前的值进行修改的动态规划算法似乎有些“格格不入”,本文对几乎可以说是动态规划的最简单特例:斐波那契数列的求解提出几种算法(不包括矩阵快速幂优化、Monad和通项公式计算),探讨一下函数式编程如何结合动态规划。

自底向上写法

算法1:

f' 1 _ b = b
f' n a b = f' (n - 1) b (a + b)
f n = f' n 0 1

尾递归,所以本质上和其他语言循环递推计算是一样的,但是如果编译器没看出来而真的用递归去算可能会爆栈。

算法2:

f' (a, b) _ = (b, a + b)
f n = snd (foldl f' (0, 1) (take (n - 1) (repeat 0)))

和上面算法一样,但是用fold来写的话可以保证编译器优化掉递归而不会爆栈。同时,在我看来,fold的过程体现了状态的变化,初状态通过一步步的计算得到末状态,正和动态规划的思想相契合,所以我认为这是动态规划的递推形式在函数式编程语言里最好的写法。

算法3:

fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
f n = fibs !! n

摘自https://wiki.haskell.org/The_Fibonacci_sequence,这个应该是最接近斐波那契数列的数学递推式的写法了,构造一个无限序列(fibs),然后描述序列是由0、1、fibs[0:n]和fibs[1:n+1]相加构成,最后当执行函数f n时,才开始对fibs[n]进行计算。这个算法充满了数学的味道,但如果从计算机的角度则非常难分析,当然也有可能是我太弱,至少我是写不出这样的算法。原网页还有很多类似的算法,但是我智商不给用很多看不懂。

自顶向下写法

算法1:

f 0 = 0
f 1 = 1
f x = (f (x - 1)) + (f (x - 2))

秉承了自顶向下写法一贯的好懂,但是作为教科书级的待优化代码,也是最慢的。

算法2:

a = map f [0..]
f 0 = 0
f 1 = 1
f x = a !! (x - 1) + a !! (x - 2)

这个可以说是比较标准的函数式语言里的记忆化搜索了,先声明一个无穷序列a存储的值是对0到无穷施加函数f的结果,但由于惰性求值的机制,一开始a的值是没有经过计算的,只有在递归的过程中遇到了要求a[x-1]和a[x-2]时才会去求,而求a[x-1]和a[x-2]就是求f(x-1)和f(x-2),实现了搜索,同时当a[x]求出来之后,如果之后还需要a[x]就直接取值就行了,因此也实现了记忆化。美中不足的是,haskell里的list是用链表实现的,因此取索引需要O(n)的复杂度,比较慢。

算法3:

import Data.Sequence
f n = let
a = fromFunction (n + 1) f'
f' 0 = 0
f' 1 = 1
f' x = a `index` (x - 1) + a `index` (x - 2)
in f' n

和上面那个算法差不多,但是使用Sequence代替list,Sequence用的是BST,索引复杂度是O(logn),虽然还是有点浪费,不过也差不多了。haskell里有一个array,虽然支持O(1)索引,但是没有什么map、fromFunction之流可以用。有一个库Vector据说可以符合这样的要求(惰性求值+map或fromFunction+O(1)索引),不过因为不是标准库所以我没尝试。注意Sequence不支持无限长度,同时fromFunction传给f'的是length-1,故传的值是n+1。

总结

目前看来,haskell计算斐波那契数列的方法中,自底向上法效率最高且容易懂的应该是使用fold的方法,自顶向下法效率最高的应该是利用一个O(1)索引且支持惰性求值的数据结构作为记忆表进行记忆化搜索的方法。显然,使用自底向上更优一些,这在其他范式的语言也是一样,没有递归负担,容易优化(滚动数组省空间、特定问题使用单调队列、斜率优化等),而且部分函数式编程语言不支持惰性求值,则直接关上了记忆化搜索的大门。总的说来,斐波那契数列还是一个极为简单的例子,函数式语言实现动态规划,仍然是值得深入研究的一个问题。

关于Haskell计算斐波那契数列的思考的更多相关文章

  1. 使用并行的方法计算斐波那契数列 (Fibonacci)

    更新:我的同事Terry告诉我有一种矩阵运算的方式计算斐波那契数列,更适于并行.他还提供了利用TBB的parallel_reduce模板计算斐波那契数列的代码(在TBB示例代码的基础上修改得来,比原始 ...

  2. Android NDK入门实例 计算斐波那契数列一生成jni头文件

    最近要用到Android NDK,调用本地代码.就学了下Android NDK,顺便与大家分享.下面以一个具体的实例计算斐波那契数列,说明如何利用Android NDK,调用本地代码.以及比较本地代码 ...

  3. 以计算斐波那契数列为例说说动态规划算法(Dynamic Programming Algorithm Overlapping subproblems Optimal substructure Memoization Tabulation)

    动态规划(Dynamic Programming)是求解决策过程(decision process)最优化的数学方法.它的名字和动态没有关系,是Richard Bellman为了唬人而取的. 动态规划 ...

  4. 用递归方法计算斐波那契数列(Recursion Fibonacci Sequence Python)

    先科普一下什么叫斐波那契数列,以下内容摘自百度百科: 斐波那契数列(Fibonacci sequence),又称黄金分割数列.因意大利数学家列昂纳多·斐波那契(Leonardoda Fibonacci ...

  5. shell脚本计算斐波那契数列

    计算斐波那契数列 [1,1,2,3,5,8,,,,,] #!/bin/bash n=$ num=( ) i= while [[ $i -lt $n ]] do let num[$i]=num[$i-] ...

  6. java 递归及其经典应用--求阶乘、打印文件信息、计算斐波那契数列

    什么是递归 我先看下百度百科的解释: 一种计算过程,如果其中每一步都要用到前一步或前几步的结果,称为递归的.用递归过程定义的函数,称为递归函数,例如连加.连乘及阶乘等.凡是递归的函数,都是可计算的,即 ...

  7. X86汇编——计算斐波那契数列程序(详细注释和流程图说明)

    X86汇编实现斐波那契数列 程序说明: 输入斐波那契数列的项数, 然后依次输出斐波那契数列, 输入的项数小于256且为数字, 计算的项数不能超过2^16次方, 输入失败是 不会回显数字 因为存结果是A ...

  8. python计算斐波那契数列

    斐波那契数列就是黄金分割数列 第一项加第二项等于第三项,以此类推 第二项加第三项等于第四项 代码如下 这一段代码实现fib(n)函数返回第n项,PrintFN(m,n,i)函数实现输出第i项斐波那契数 ...

  9. Android NDK入门实例 计算斐波那契数列二生成.so库文件

    上一篇文章输生成了jni头文件,里面包含了本地C代码的信息,提供我们引用的C头文件.下面实现本地代码,再用ndk-build编译生成.so库文件.由于编译时要用到make和gcc,这里很多人是通过安装 ...

随机推荐

  1. 随机抽样一致性(RANSAC)算法详解

    随机抽样一致性(RANSAC)算法能够有效的剔除特征匹配中的错误匹配点. 实际上,RANSAC能够有效拟合存在噪声模型下的拟合函数.实际上,RANSAC算法的核心在于将点划分为“内点”和“外点”.在一 ...

  2. Zookeeper——基本使用以及应用场景(手写实现分布式锁和rpc框架)

    文章目录 Zookeeper的基本使用 Zookeeper单机部署 Zookeeper集群搭建 JavaAPI的使用 Zookeeper的应用场景 分布式锁的实现 独享锁 可重入锁 实现RPC框架 基 ...

  3. http 的8中请求方式:

    http 的8中请求方式: 1.OPTIONS 返回服务器针对特定资源所支持的HTTP请求方法,也可以利用向web服务器发送‘*’的请求来测试服务器的功能性 2.HEAD 向服务器索与GET请求相一致 ...

  4. redis编译报错总结

    redis编译报错总结: 1.不能编译没有GCC 编译工具安装报错:问题1:make时可能会报如下错误cc -c -std=c99 -pedantic -O2 -Wall -W   -g -rdyna ...

  5. 手把手教你使用Python生成图灵智能小伙伴,实现工作助手/闲聊功能

    /1 前言/ 在家闲着,做个小项目,基于Python,实现一个语聊小机器人,分享给大家.项目整体比较简单,官方文档介绍的非常详细,可快速上手. /2 目标/ 将图灵机器人放到桌面,实现工作助手/陪聊功 ...

  6. IDEA 使用jebel热部署插件启动失败

    在使用Jebel热部署插件开发springmvc时,启动会出现内存溢出错误.可在配置Tomcat时增加JVM参数解决. -Xms768m -Xmx768m -XX:PermSize=64M -XX:M ...

  7. linux 配置ssh免密登录

    一.SSH概念(百度) SSH 为 Secure Shell 的缩写,由 IETF 的网络小组(Network Working Group)所制定:SSH 为建立在应用层基础上的安全协议.SSH 是目 ...

  8. windows 下搭建 MQTT 服务

    1.首先搭建起MQTT服务 1.1安装mosquitto,mosquitto是开源的MQTT代理服务器,它的Windows安装包地址:https://mosquitto.org/download/ 1 ...

  9. 用阿里的 sketch 插件 FusionDesign 来快速设计中后台

    Fusion Design 是阿里推出的新的基于sketch的快速设计方案,很适合快速设计中后台. (1) 到Fusion官方站点 https://fusion.design 注册并创建一个项目. ( ...

  10. jQurey Select2 4.0

    https://jeesite.gitee.io/front/jquery-select2/4.0/index.htm