繁体   English   中英

Haskell Fibonacci 看起来很慢

[英]Haskell Fibonacci seems slow

我正在学习 Haskell,我写了一个简单的 Fibonacci 函数:

fib :: Int -> Int

fib 1 = 1
fib 0 = 0
fib n = (fib (n-1)) + (fib (n-2))

它似乎可以编译,并将此脚本加载到 GHCI REPL 中,我可能会弄乱一些数字。 我试过

fib 33

并惊讶地发现结果花了大约 4 秒钟。 (抱歉,我还不知道如何在 Haskell 中为函数计时,所以算我自己)。

Fib 33 并不是特别费力。 答案是不到400万。 所以我假设我的代码写得不是很好,或者我做递归的方式可能存在一些问题(好吧,它写得不好,因为它没有考虑负整数)。 问题是,为什么慢? 任何帮助表示赞赏。

评估花费的时间比您预期的要长,因为您的函数不使用memoization 有关如何使用记忆化在 Haskell 中定义斐波那契函数的答案,请参见例如这个问题那个问题

您是否将那个时间与其他语言进行了比较?

这是具有 O(2^n) 复杂度的递归算法。 在 n=33 时,调用数量惊人。 如果您计算每个此类调用有多少毫秒或纳秒,您就会得到关于实际性能的非常出色的答案。

请记住,某些编译器/执行环境可能会缓存函数返回值(Frerich 对它的调用方式有更好的记忆:记忆化),这在此算法的情况下大大提高了性能。 在这种情况下它不会发生,所以所有那些 2^n 递归调用都会发生。

你的算法不是很好。 您可以使用 memoization 对其进行一点改进,最高为 O(n)。 使用分而治之,你可以得到 O(log n):

import Data.Matrix

fib :: Integer -> Integer
fib n = ((fromLists [[1,1],[1,0]]) ^ n) ! (1,2)

这个想法是,乘法是关联的,因此您可以将大括号放在重要位置:

5^10 = (5 * 5 * 5 * 5 * 5) * (5 * 5 * 5 * 5 * 5) = (5 * 5 * 5 * 5 * 5) ^ 2 = ( (5 * 5) * ( 5 * 5) * 5) ^ 2 = ( (5 * 5 ) ^ 2 * 5) ^ 2 = (((5 ^ 2) ^ 2) * 5) ^2

相同的模式可以应用于矩阵乘法。 并且 Haskell 已经在(^)默认库中实现了这一点。

这确实有效:

map fib [1..21]
--! [1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946]

这是带有辅助功能的优化版本。 仍然比上面给出的惰性无限列表慢,但对于像我这样的新手来说更直接!

fib :: Integer -> Integer
fib 0 = 0
fib 1 = 1
fib n = fib' 0 1 2 n

fib' :: Integer -> Integer -> Integer -> Integer -> Integer
fib' a b i n = if i > n then b else fib' b (a + b) (i + 1) n

PS:仅适用于正数

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM