【问题标题】:Haskell Fibonacci seems slowHaskell Fibonacci 似乎很慢
【发布时间】:2015-02-16 05:41:42
【问题描述】:

我正在学习Haskell,我写了一个简单的斐波那契函数:

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

【问题讨论】:

  • 这个问题似乎对 CodeReview 来说是个好问题。
  • 查看您的代码时,想象一下例如fib(5) 的计算频率。每次迭代,您都会再次计算所有“内部”斐波那契数。
  • 您应该使用经典的惰性无限列表版本:fibs = 0 : 1 : zipWith (+) fibs (tail fibs)fib n = fibs!!n。见the haskell wiki about the Fibonacci sequence。有趣的是,斐波那契因这个序列而闻名,这只是他应该成名的书中的一个小练习,将位值引入了西欧。

标签: haskell fibonacci


【解决方案1】:

评估花费的时间比您预期的要长,因为您的函数没有使用 memoization。参见例如this questionthat question 获取有关如何使用 memoization 在 Haskell 中定义斐波那契函数的答案。

【讨论】:

  • 记忆链接很好地解释了这个问题。
  • 这两个问题及其答案都相当深奥。 教科书方法呢?
  • 我不知道一个人怎么会错过fib=0:1:zipWith (+) fib (tail fib) 在通往高阶教育的路上。这是一切教科书示例。我可以在不看的情况下在手机上输入它(我刚刚做到了)。
  • 这个答案怎么能在这么短的时间内获得如此多的支持,这让我无法理解。它非常接近于仅提供链接的答案...
  • 不是那种类型的。使用like this。不过,我不确定它是否适合作为答案。
【解决方案2】:

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

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

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

【讨论】:

  • 从技术上讲,它的复杂度为O(fib n),因此大致为O(1.68^n),略好于O(2^n)。不过,这不会影响您的观点:它的复杂性仍然是指数级的,因此递归调用的数量很快就会变得不切实际。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

      猜你喜欢
      • 2021-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-08
      • 2018-12-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多