惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
D
Docker
S
SegmentFault 最新的问题
博客园 - 聂微东
美团技术团队
Apple Machine Learning Research
Apple Machine Learning Research
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
F
Fortinet All Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The GitHub Blog
The GitHub Blog
GbyAI
GbyAI
L
LangChain Blog
Vercel News
Vercel News
博客园 - 叶小钗
MongoDB | Blog
MongoDB | Blog
Stack Overflow Blog
Stack Overflow Blog
H
Help Net Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
Engineering at Meta
Engineering at Meta
T
Threat Research - Cisco Blogs
T
Threatpost
Scott Helme
Scott Helme
T
Tailwind CSS Blog
Latest news
Latest news
Stack Overflow Blog
Stack Overflow Blog
Blog — PlanetScale
Blog — PlanetScale
The Register - Security
The Register - Security
罗磊的独立博客
P
Proofpoint News Feed
腾讯CDC
S
Schneier on Security
雷峰网
雷峰网
A
About on SuperTechFans
T
Tenable Blog
F
Full Disclosure
Cyberwarzone
Cyberwarzone
博客园_首页
有赞技术团队
有赞技术团队
K
Kaspersky official blog

文章列表

游戏玩后感:ReLief:献给亲爱的你 我的周边(谷子)分享 游戏玩后感:Kanon 简谱:致真实的你 《Rust中常见的有关生命周期的误解》学习笔记 简谱:StarMap 简谱:かく咲きたらばいと恋ひめやも 简谱:东风 简谱:无法诉说的思念 简谱:Girlish 游戏玩后感:时钟机关的Layline 简谱:风之琶音 简谱:星空的记忆 简谱:因为遇见了你 简谱:月童 番茄简谱脚本转调器 游戏玩后感:青空下的约定:Refine 游戏玩后感:在这苍穹展翅 书籍读后感:控制论与科学方法论 游戏玩后感:恋爱表达式 游戏玩后感:樱之诗 MLIR-tutorial学习笔记 游戏玩后感:潜伏之赤途 游戏玩后感:纯爱咖啡厅:帕露菲重制版 游戏玩后感:智以泪聚 游戏玩后感:初雪樱 游戏玩后感:告别回忆:从今以后 游戏玩后感:梦灯花 游戏玩后感:金辉恋曲四重奏 游戏玩后感:五彩斑斓的世界 昇腾310P使用记录 游戏玩后感:AIR 游戏玩后感:弹丸论破 游戏玩后感:流景之海的艾佩莉亚 Xilinx_HLS上板过程记录 游戏玩后感:告别回忆2 游戏玩后感:恋爱绮谭 Faiss和Rapidsai_Raft使用记录 游戏玩后感:近月少女的礼仪 游戏玩后感:樱色之云,绯色之恋 游戏玩后感:幸运草的约定 游戏玩后感:星之梦、候鸟和丸子与银河龙 游戏玩后感:白色相簿2 Windows上使用VTune分析PyTorchExtension调用的Cpp程序 SpinalHDL上板过程记录 游戏玩后感:仰望夜空的星辰 最简单的算卦方法之一:梅花易数法 游戏玩后感:苍之彼方的四重奏 krkr引擎解包工具介绍 自定义CUDA实现PyTorch算子的四种简单方法 游戏玩后感:星空的记忆 游戏玩后感:9nine 游戏玩后感:AtriMyDearMoments 游戏玩后感:极限脱出 游戏玩后感:魔女的夜宴 SSH实现多跳代理 动漫观后感:向山进发 flv重封装H264、AAC流 动漫观后感:夏日重现 CSP模板 游戏玩后感:海沙风云 动漫观后感:灵能百分百 游戏玩后感:交响乐之雨 游戏玩后感:爱上火车LastRun 游戏玩后感:LittleBustersEX 游戏玩后感:SummerPockets 游戏玩后感:逆转裁判 Ultra96V2开发板简单使用 SpinalWorkshop实验笔记(三) SpinalWorkshop实验笔记(二) SpinalWorkshop实验笔记(一) PYNQ开发板上使用USB声卡+OSS兼容层播放音频 TestOS移植K210开发板 rCore-Tutorial-Book-v3学习笔记(七) 动漫观后感:凉宫春日的忧郁 rCore-Tutorial-Book-v3学习笔记(♭七) rCore-Tutorial-Book-v3学习笔记(六) rCore-Tutorial-Book-v3学习笔记(五) rCore-Tutorial-Book-v3学习笔记(四) rCore-Tutorial-Book-v3学习笔记(三) rCore-Tutorial-Book-v3学习笔记(二) rCore-Tutorial-Book-v3学习笔记(一) 游戏玩后感:RewritePlus MIT-6.S081-2020实验(xv6-riscv64)十一:net MIT-6.S081-2020实验(xv6-riscv64)十:mmap MIT-6.S081-2020实验(xv6-riscv64)九:fs MIT-6.S081-2020实验(xv6-riscv64)八:lock MIT-6.S081-2020实验(xv6-riscv64)七:thread MIT-6.S081-2020实验(xv6-riscv64)六:cow MIT-6.S081-2020实验(xv6-riscv64)五:lazy MIT-6.S081-2020实验(xv6-riscv64)四:traps MIT-6.S081-2020实验(xv6-riscv64)三:pgtbl MIT-6.S081-2020实验(xv6-riscv64)二:syscall 动漫观后感:吹响吧上低音号 MIT-6.S081-2020实验(xv6-riscv64)一:util 快速生成网络mp4视频缩略图技术 用plantuml画图示例 QQ缩略图和大图不同实现 Python制作字符图片 动漫观后感:命运石之门
关于Haskell计算斐波那契数列的思考
VnYzm · 2020-01-24 · via

背景

众所周知,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)索引且支持惰性求值的数据结构作为记忆表进行记忆化搜索的方法。显然,使用自底向上更优一些,这在其他范式的语言也是一样,没有递归负担,容易优化(滚动数组省空间、特定问题使用单调队列、斜率优化等),而且部分函数式编程语言不支持惰性求值,则直接关上了记忆化搜索的大门。总的说来,斐波那契数列还是一个极为简单的例子,函数式语言实现动态规划,仍然是值得深入研究的一个问题。