Haskell Fibonacci似乎很慢

Haskell Fibonacci seems slow

我在学习haskell,我写了一个简单的fibonacci函数:

1
2
3
4
5
fib :: Int -> Int

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

它似乎编译得很好,将这个脚本加载到ghci repl中,我可能会用一些数字来处理这些问题。我试过了

1
fib 33

令人惊讶的是,用了4秒钟才得出结果。(抱歉,我还不知道如何给Haskell中的函数计时,所以算我自己)。

FIB 33不是特别的征税。答案不到400万。所以我假设我的代码写得不是很好,或者我做递归的方式有问题(好吧,它写得不好,因为它不考虑负整数)。问题是,为什么速度慢?感谢您的帮助。


由于您的函数不使用memoization,因此评估时间比预期长。关于如何使用memoization在haskell中定义fibonacci函数的答案,请参见例如这个问题或那个问题。


你把这段时间和其他语言相比了吗?

这是一种具有O(2^n)复杂性的递归算法。在n=33时,会发出惊人的呼叫量。如果你计算每一个这样的呼叫有多少毫秒或纳秒,你就可以得到一个非常显著的关于实际性能的答案。

记住,有些编译器/执行环境可能会缓存函数返回值(Frerich对函数的命名方法memoization有更好的内存),这在使用此算法的情况下大大提高了性能。在这种情况下,不会发生这种情况,因此所有2^n递归调用都会发生。


你的算法不是很好。你可以用记忆法稍微改进一下,最多是O(N)。使用分而治之,您最多可以获得o(登录n):

1
2
3
4
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已经在其(^)的默认库中实现了这一点。

这确实有效:

1
2
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]


这里是一个带有助手函数的优化版本。仍然比上面给出的懒惰的无限列表慢,但对像我这样的新手来说更直接!

1
2
3
4
5
6
7
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

P.S:仅适用于正数