第一递归总和值[0..n]
从其端部(n
)开始,这样的:
1+(2+(3+(... + ((n-1) + n)) ...)))
通过这种方法,程序首先必须枚举所有的号码,产生全序列,只有经过这些添加可以实际执行。
这需要O(n)内存和O(n)时间。
在第二递归,我们指望从0
到n
,因为我们没有较早,但现在我们在
(((1+2)+3)+4)+ ...
总结数字,因为我们去,就像我们可以总结1+2
我们数到3
之前。之后,我们只能保留前面的金额1+2
的结果,并从内存中丢弃号码1
和2
。因此,在整个过程中,我们只保留记忆1)迄今为止所达到的数字总和的结果,以及2)序列中的下一个数字。因此,我们现在只需要O(1)个存储器和O(n)时间。
注意:由于Haskell是惰性的,只有在每次递归时实际强制部分和才能使用上述参数。编译器优化器可以静静地添加这个强制,但是明确地说明它是个好主意,例如,在
sumdown n = sumd n 0
sumd 0 !a = a
sumd n !a = sumd (n-1) (n+a)
-- here I am using the BangPatterns extension,
-- otherwise, seq can be used instead
第二次递归通常被称为“累加器式”,这是“尾递归”的一个特例。
(注2:尾递归并不总是在一个慵懒的语言象Haskell是个好主意,但如果各地传递的数据是简单的,如相同的数字,而不是像列表,尾递归通常是有益的。)
来源
2017-05-26 11:07:25
chi
'a'实际上是一个累加器,并有助于使递归调用尾部优化。这与第一个例子不同,它不会增加调用堆栈。 – Redu
@Redu Haskell没有调用堆栈,它有一个thunk堆栈。盲目地将事情变成尾递归形式实际上可能会损害Haskell程序的性能,因为使用累加器会阻止函数返回,直到检查完整个输入为止,这本质上会干扰懒惰。 – Ben