Haskell 如何使斐波那契函数更快?

Haskell 如何使斐波那契函数更快?,haskell,functional-programming,fibonacci,Haskell,Functional Programming,Fibonacci,此函数用于查找第n个斐波那契函数 a = 1 b = 2 fibonacci :: Int -> Int fibonacci 1 = a fibonacci 2 = b fibonacci n = (fibonacci (n-1)) + (fibonacci (n-2)) 但是速度很慢。如果我把斐波那契[1..]映射到地图上,它会随着数字的到来而变慢。我猜这是由于使用了多少堆栈和计算的绝对数量造成的开销-将每一个堆栈向下进行a和b,而不是将最后两个堆栈相加 如何改进它,使其更快,但仍然

此函数用于查找第n个斐波那契函数

a = 1
b = 2

fibonacci :: Int -> Int
fibonacci 1 = a
fibonacci 2 = b
fibonacci n = (fibonacci (n-1)) + (fibonacci (n-2))
但是速度很慢。如果我把斐波那契[1..]映射到地图上,它会随着数字的到来而变慢。我猜这是由于使用了多少堆栈和计算的绝对数量造成的开销-将每一个堆栈向下进行
a
b
,而不是将最后两个堆栈相加

如何改进它,使其更快,但仍然使用函数式编程风格?(我绝对是哈斯克尔和FP新手!) 相比之下,我在Python中尝试了一些闪电般的东西


提示即使不比工作代码更受欢迎,也同样受欢迎

问题是,这将导致显著的分支。假设您调用
fibonacci 5
,则将生成以下调用树:

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

您可以使用累加器:递归传递并相应更新的变量。对于斐波那契,有两个这样的变量,f(n-2)和f(n-1)。然后,每次计算这两个值的和,然后移动:

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

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

因此,这将导致五个调用(原始代码为九个调用)。当然,调用的数量并不能保证性能,因为有些方法会做更多的工作,但是我们的方法会随着n线性扩展,而原始方法会随着n指数扩展。因此,即使原始方法的速度快上千倍,最终调用数量的差异也会如此巨大,其性能将优于原始方法。

您可以通过记忆来消除冗余计算

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

对于给定的
a
b
初始值

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

感谢v清楚地解释了为什么这么慢-我知道是这样的,但是分支非常清楚我知道你的解决方案是如何工作的-为什么
fib'
存储值而我的没有?我是不是对GHC期望太高了?@JamesWilson,很多Haskell的新手都希望GHC是魔术,我把这归咎于粉丝媒体。GHC很好,因为您可以进行大量抽象,而不必支付抽象成本,但它不会改变您使用的算法的渐近性。@JamesWilson,您问题的直接答案是
fib'
将其中间值存储在两个累加器参数中。您的实现没有存储它们的机制。不过,您可以显式地记忆@luqui有时完全惰性转换实际上改善了渐近性。当然,有时候这也会让他们变得更糟……来吧。每个Haskell程序员都应该知道这个页面:@EugeneSh。来吧,“来吧”?这句话似乎是居高临下的。@luqui这不是取决于面部表情吗?我同意这是居高临下的。特别是加上“每一个”和“应该”,它说,任何人谁不知道这个网页一定不是一个真正的哈斯克尔程序员。但是你不可能生来就知道所有有趣的事情:OP是其中之一,暗示他应该已经知道这件事是不好的。哦,天啊。。考虑到我链接到的那个页面很幽默,也没有真正的教育意义(好吧,有点),我的说法与“要理解递归,就应该理解递归”的说法大致相同——这也不是一个很严肃的说法。这被称为“笑话”。
fibonacci 5
    fib' 3 1 2
        fib' 2 2 3
            fib' 1 3 5
                fib' 0 5 8
fibonacci = (map fibm [0..] !!)
    where fibm 1 = a
          fibm 2 = b
          fibm n = fibonacci (n-1) + fibonacci (n-2)