Haskell Fibonacci似乎很慢

时间:2014-12-17 15:55:06

标签: haskell fibonacci

我正在学习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万。 所以我假设我的代码编写得不是很好,或者我的递归方式也存在一些问题(好吧,它并没有写得好,因为它没有考虑到负面因素整数)。问题是,为什么它变慢? 任何帮助表示赞赏。

4 个答案:

答案 0 :(得分:10)

评估花费的时间比预期的要长,因为您的功能不使用memoization。参见例如this questionthat question,了解如何使用memoization在Haskell中定义fibonacci函数。

答案 1 :(得分:6)

你把时间与其他语言比较了吗?

这是具有O(2 ^ n)复杂度的递归算法。在n = 33时,会给出惊人的呼叫量。如果算上每次这样的呼叫有多少毫秒或几纳秒,你就可以得到一个非常显着的答案来确定实际性能。

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

答案 2 :(得分:4)

你的算法不是很好。您可以使用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]

答案 3 :(得分:0)

这是具有辅助功能的优化版本。仍然比上面给出的惰性无限列表要慢,但是对于像我这样的新手来说要简单得多!

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:仅适用于正数