普通遞歸與優化遞歸

function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}
 
factorial(5) // 120

 

上面代碼是一個階乘函數,計算n的階乘,最多須要保存n個調用記錄,複雜度 O(n) 。javascript

若是改寫成尾遞歸,只保留一個調用記錄,複雜度 O(1) 。java

尾遞歸

函數調用自身,稱爲遞歸。若是尾調用自身,就稱爲尾遞歸。編程

遞歸很是耗費內存,由於須要同時保存成千上百個調用幀,很容易發生「棧溢出」錯誤(stack overflow)。但對於尾遞歸來講,因爲只存在一個調用幀,因此永遠不會發生「棧溢出」錯誤編程語言

function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}
 
factorial(5, 1) // 120

還有一個比較著名的例子,就是計算 Fibonacci 數列,也能充分說明尾遞歸優化的重要性。函數式編程

非尾遞歸的 Fibonacci 數列實現以下。函數

function Fibonacci (n) {
  if ( n <= 1 ) {return 1};
 
  return Fibonacci(n - 1) + Fibonacci(n - 2);
}
 
Fibonacci(10) // 89
Fibonacci(100) // 堆棧溢出
Fibonacci(500) // 堆棧溢出

尾遞歸優化過的 Fibonacci 數列實現以下。優化

unction Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
  if( n <= 1 ) {return ac2};
 
  return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}
 
Fibonacci2(100) // 573147844013817200000
Fibonacci2(1000) // 7.0330367711422765e+208
Fibonacci2(10000) // Infinity

因而可知,「尾調用優化」對遞歸操做意義重大,因此一些函數式編程語言將其寫入了語言規格。ES6 是如此,第一次明確規定,全部 ECMAScript 的實現,都必須部署「尾調用優化」。這就是說,ES6 中只要使用尾遞歸,就不會發生棧溢出,相對節省內存。code

 遞歸函數的改寫blog

function factorial(n, total = 1) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}
 
factorial(5) // 120
相關文章
相關標籤/搜索