我已经编写了以下斐波那契游戏程序作为学习Haskell的一部分:
fibonacci 0 = [0]
fibonacci 1 = [0,1]
fibonacci n = let
foo'1 = last (fibonacci (n-1))
foo'2 = last (fibonacci (n-2))
in reverse((foo'1 + foo'2):reverse (fibonacci (n-1)))
该计划有效:
ghci>fibonacci 6
[0,1,1,2,3,5,8]
但是,性能随着n呈指数下降。如果我给它一个参数30,它需要大约一分钟才能运行,而不是在6瞬间运行。似乎懒惰的执行正在烧毁我,而斐波纳契正在为最终列表中的每个元素运行一次。
我做的事情愚蠢还是缺少某些东西?
(我已经摆脱了可能正在做的++思考)
答案 0 :(得分:5)
正如评论中指出的那样,你的方法有点过于复杂。特别是,您不需要使用递归调用,甚至是reverse
函数,以生成Fibonacci序列。
除了your own answer之外,这里还有一本教科书单行,它使用了memoization:
fibs :: [Integer]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
获得fibs
后,编写fib
功能非常简单:
fib :: Int -> [Integer]
fib n
| n < 0 = error "fib: negative argument"
| otherwise = take (n+1) fibs
fib
的这种实现具有复杂度Θ(n),这显然比Θ(exp(n))好得多。
λ> :set +s
λ> fib 6
[0,1,1,2,3,5,8]
(0.02 secs, 7282592 bytes)
λ> fib 30
[0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711,28657,46368,75025,121393,196418,317811,514229,832040]
(0.01 secs, 1035344 bytes)
如您所见,fib 30
在我的机器上在一分钟之内得到了评估。
有关如何在Haskell中生成Fibonacci序列的更全面的处理,我建议你参考haskell.org wiki
答案 1 :(得分:1)
以下是使用@ icktoofay指向memoization的问题的答案。答案包括一个快速返回给定斐波那契数的函数,所以我用他们的例子创建了一个原始问题的解决方案 - 创建一个斐波那契数字列表,直到所要求的数字。
此解决方案几乎可以即时运行(该页面还有将“我的方法”称为“天真”的额外好处)
memoized_fib :: Int -> Integer
memoized_fib = (map fib [0 ..] !!)
where fib 0 = 0
fib 1 = 1
fib n = memoized_fib (n-2) + memoized_fib (n-1)
fib 0 = [0]
fib 1 = [0,1]
fib n = reverse ((memoized_fib (n-2) + memoized_fib(n-1)) : reverse (fib (n-1)))
答案 2 :(得分:1)
您不需要添加备忘录到您的功能 - 它已经拥有所有以前的结果,并生成一个列表。您只需要停止忽略这些结果,就像现在使用last
一样。
首先,如果以相反的顺序构建列表更自然,那么没有理由不这样做:
revFib 0 = [0]
revFib 1 = [1,0]
revFib n | n > 0 = let f1 = head (revFib (n-1))
f2 = head (revFib (n-2))
in f1 + f2 : revFib (n-1)
这仍然很慢,因为我们仍然忽略了所有以前的结果,除了最后一个结果,位于列表的头部。我们可以停止这样做,
revFib 0 = [0]
revFib 1 = [1,0]
revFib n | n > 0 = let f1 = head (revFib (n-1))
f2 = head (tail (revFib (n-1)))
in f1 + f2 : revFib (n-1)
然后我们将命名公共子表达式,以便它在其用途之间共享,并且只计算一次:
revFib 0 = [0]
revFib 1 = [1,0]
revFib n | n > 0 = let prevs = revFib (n-1)
[f1,f2] = take 2 prevs
in f1 + f2 : prevs
突然之间它是线性的,而不是指数。