第一遞歸總和值[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