尾递归
一. 尾调用 --指某个函数的最后一步是调用另一个函数
function f(x){
return g(x);
}
这个是尾调用,但下面的两个不是:
// 情况一
function f(x){
let y = g(x);
return y;
}
// 情况二
function f(x){
return g(x) + 1;
}
如果在函数A的内部调用函数B,那么在A的调用记录上方,还会形成一个B的调用记录。等到B运行结束,将结果返回到A,B的调用记录才会消失。如果函数B内部还调用函数C,那就还有一个C的调用记录栈,以此类推。所有的调用记录,就形成一个"调用栈"(call stack)
"尾调用优化"(Tail call optimization),即只保留内层函数的调用记录。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用记录只有一项,这将大大节省内存。这就是"尾调用优化"的意义
二. 尾递归--函数调用自身,称为递归。如果尾调用自身,就称为尾递归
-
递归非常耗费内存,因为需要同时保存成千上百个调用记录,很容易发生"栈溢出"错误(stack overflow)。但对于尾递归来说,由于只存在一个调用记录,所以永远不会发生"栈溢出"错误.
-
尾递归实例
function factorial(n, total) { if (n === 1) return total; return factorial(n - 1, n * total); } factorial(5, 1) // 120
由此可见,"尾调用优化"对递归操作意义重大,所以一些函数式编程语言将其写入了语言规格。ES6也是如此,第一次明确规定,所有 ECMAScript 的实现,都必须部署"尾调用优化"。这就是说,在 ES6 中,只要使用尾递归,就不会发生栈溢出,相对节省内存
-
尾递归优化
function factorial(n, total = 1) { if (n === 1) return total; return factorial(n - 1, n * total); } factorial(5) // 120
def factorial(n: Int): Int = { @tailrec def iter(x: Int, result: Int): Int = if (x == 1) result else iter(x - 1, result * x) iter(n, 1 ) }
def factorial(n: Int, total: Int = 1): Int = {
if (n == 1) total
else factorial(n - 1, n * total)
}
public long tailFactorial(long n,long total) {
if (n == 1) return total;
return tailFactorial(n - 1, n * total);
}
private long factorial(long n) {
return tailFactorial(n, 1);
}
public static void main(String[] args) {
System.out.println(new TailRecursive().factorial(5));
}