【问题标题】:How to make this fibonacci function faster?如何使这个斐波那契函数更快?
【发布时间】:2017-10-15 13:51:56
【问题描述】:

这个函数可以找到第 n 个斐波那契。

a = 1
b = 2

fibonacci :: Int -> Int
fibonacci 1 = a
fibonacci 2 = b
fibonacci n = (fibonacci (n-1)) + (fibonacci (n-2))

但是速度很慢。如果我这样做map fibonacci [1..],随着数字的到来,它真的会变慢。我猜这是由于使用了多少堆栈和计算的绝对数量而导致的开销 - 将每一个都降低到 ab 而不是仅仅将最后两个加在一起。

如何改进它以使其更快,但仍使用函数式编程风格? (我是一个明确的 haskell 和 FP 新手!) 我在 Python 中尝试了一些相比之下闪电般的东西。

提示与工作代码一样受欢迎!

【问题讨论】:

  • 来吧。每个 Haskell 程序员都应该知道这个页面:willamette.edu/~fruehr/haskell/evolution.html
  • @EugeneSh。来吧来吧”?这条评论似乎居高临下。
  • @luqui 不是看表情吗?
  • 我同意这是居高临下的。特别是加上“every”和“should”,它表示任何不知道这个页面的人一定不是真正的Haskell程序员。但是你不可能生来就知道所有有趣的事情:OP 是today's lucky 10,000 之一,暗示他应该已经知道这一点并不好。
  • 哦,伙计.. 鉴于我链接到的那个页面是幽默,而且没有真正的教育意义(嗯,有点),我的说法与说“到理解递归一个人应该理解递归”——这也不是一个很严肃的说法。它被称为“笑话”。

标签: haskell functional-programming fibonacci


【解决方案1】:

问题是这会导致戏剧性的分支。假设您调用fibonacci 5,那么这将导致以下调用树

fibonacci 5
    fibonacci 4
        fibonacci 3
            fibonacci 2
            fibonacci 1
        fibonacci 2
    fibonacci 3
        fibonacci 2
        fibonacci 1

如您所见,fibonacci 3 被调用了两次,fibonacci 2 被调用了 3 次,fibonacci 1 被调用了两次(在这个非常小的示例中)。有大量的重叠:您多次使用相同的参数调用相同的函数。这当然是低效的:一旦你知道fibonacci 33,就不需要第二次计算了。当然,在这个非常小的示例中,这并不是一个真正的问题:计算fibonacci 3 只需几纳秒。但是如果你必须多次计算fibonacci 100,这将产生巨大的影响。 冗余调用的数量也呈指数级增长(所以这不是一个小问题,只会对边际产生一些影响)。

您可以做的是使用 accumulator(s):您以递归方式传递并相应更新的变量。对于斐波那契,有两个这样的变量,f(n-2)f(n-1)。然后你每次计算这两者的总和并 shift 所以:

fibonacci :: Int -> Int
fibonacci 1 = a
fibonacci 2 = b
fibonacci n = fib' (n-2) a b
    fib' 0 x y = x+y
    fib' i x y = fib' (i-1) y (x+y)

在这种情况下,调用树将如下所示:

fibonacci 5
    fib' 3 1 2
        fib' 2 2 3
            fib' 1 3 5
                fib' 0 5 8

因此,这会导致五次调用(针对原始代码的九次调用)。当然,调用次数并不能保证性能,因为有些方法做的工作更多,但是我们的方法线性n 而原始方法指数地扩展n。所以即使原来的方法快上千倍,最终调用次数的差异也会如此巨大,以至于它会被超越。

【讨论】:

  • 感谢 v 清楚地解释为什么这么慢 - 知道它是这样的,但分支真的很清楚
  • 我知道您的解决方案是如何工作的 - 为什么fib' 存储值而我的却没有?我是否对 GHC 期望过高?
  • @JamesWilson,很多 Haskell 新手都希望 GHC 具有魔力,我将此归咎于狂热的媒体。 GHC 的好处在于你可以抽象出一个 lot 而不必支付抽象成本,但它不会改变你使用的算法的渐近性。
  • @JamesWilson,您的问题的直接答案是fib' 将其中间值存储在其两个累加器参数中。您的实现没有存储它们的机制。不过,您可以显式地记忆。 wiki.haskell.org/Memoization
  • @luqui 有时完整的惰性转换实际上确实改善了渐近性。当然,有时它也会让他们变得更糟......
【解决方案2】:

你可以用记忆化来结束它以消除冗余计算

fibonacci = (map fibm [0..] !!)
    where fibm 1 = a
          fibm 2 = b
          fibm n = fibonacci (n-1) + fibonacci (n-2)

对于您给定的 ab 初始值。

【讨论】:

    【解决方案3】:

    您可以使用矩阵乘法在 O(logN) 时间内计算斐波那契数。类似的问题是here

    【讨论】:

      猜你喜欢
      • 2014-05-03
      • 2011-03-21
      • 2023-03-11
      • 2013-06-30
      • 1970-01-01
      • 2011-09-30
      • 2017-07-08
      • 2021-02-14
      • 1970-01-01
      相关资源
      最近更新 更多