这是my previous question的后续行动。
当我understand时,以下计算斐波那契数的方法效率很低,因为为每个斐波纳契数调用方法fib
,每次调用它时都会创建一个新流。
def fib:Stream[Int] = Stream.cons(1, Stream.cons(1, (fib zip fib.tail) map {case (x, y) => x + y}))
另一方面,尾递归方法(如here中)看起来效率很高,并计算O(1)
def fib(a:Int, b:Int):Stream[Int] = Stream.cons(a, fib(b, a+b));
现在我得出结论,创建Streams的递归方法是有效的当且仅当它们是尾递归时。这是对的吗?
答案 0 :(得分:7)
不,tail递归是为了帮助编译器循环而不是堆栈(全局),这是编译时优化。
问题来自第一个实现,其中对fib
的多次调用导致了几个Stream构造,因此反复进行相同的微积分。
fib zip fib.tail
//if we are at the 1000, it will compute million Streams
如果您想看到它,请尝试以下
var i = 0
def fib:Stream[Int] = {
i = i + 1
println("new Stream : " + i)
Stream.cons(1, Stream.cons(1, (fib zip fib.tail) map {case (x, y) => x + y}))
}
答案 1 :(得分:4)
我试图改进Andy的answer,但他几乎把它钉了起来。第一个解决方案是创建一个流金字塔 - 每个调用fib
创建另一个斐波纳契流,每个新流将自己创建新流,依此类推。
要明确的是,通过调用fib
会产生三个流:
fib
fib zip fib.tail
创建的一个
fib.tail
fib zip fib.tail
创建的一个
map
创建的一个(请记住,map
创建一个新的集合)由于前两个是对fib
的调用,因此它们将分别创建三个流,依此类推。
这是一张粗略的“图片”:
1
1
1 2 1
1 3 1 2 1
1 2 1 5 1 3 1 2 1
1 3 1 2 1 8 1 2 1 5 1 3 1 2 1
这一直在继续。使用其左侧和右侧的最高流(fib和fib.tail)计算中间流。它们中的每一个都使用其左下方的较低流来计算。使用最后一行显示的流计算这些较低流中的每一个。
我们可以继续这样做,但你可以看到,当我们计算8时,我们已经有14个其他的斐波纳契流正在进行。
如果您将其从def
更改为val
,则所有这些新流都会消失,因为fib
和fib.tail
会引用现有流而不是创建新流。由于不会创建新流,因此不会再调用fib
和fib.tail
。
现在,如果你看第二个答案,你会注意到只有一个fib
调用,没有map
或类似的方法,所以没有乘法效应。