见下面几个例子:
function factorial(n) { if (n === 1) return 1; return n * factorial(n - 1);}factorial(5)
复杂度为o(n) 太大会造成堆栈溢出
function factorial(n, total) { if (n === 1) return total; return factorial(n - 1, n * total);}factorial(5, 1) //
复杂度为哦o(1)
尾递归函数的改写:
尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数
递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归实现,这就是为什么尾递归对这些语言极其重要。 对于其他支持“尾调用优化”的语言(比如 Lua,ES6),只需要知道循环可以用递归代替,而一旦使用递归,就最好使用尾递归。
解决方案: 1、蹦床函数的一个实现,它接受一个函数f作为参数。只要f执行后返回一个函数,就继续执行。 注意,这里是返回一个函数,然后执行该函数,而不是函数里面调用函数,这样就避免了递归执行,从而就消除了调用栈过大的问题。
例子:
function trampoline(f) { while (f && f instanceof Function) { f = f(); } return f;}function sum(x, y) { if (y > 0) { return sum.bind(null, x + 1, y - 1); } else { return x; }}trampoline(sum(1, 100000))// 100001
2、真的优化方案
function tco(f) { var value; var active = false; var accumulated = []; return function accumulator() { accumulated.push(arguments); if (!active) { active = true; while (accumulated.length) { value = f.apply(this, accumulated.shift()); } active = false; return value; } };}var sum = tco(function(x, y) { if (y > 0) { return sum(x + 1, y - 1) } else { return x }});sum(1, 100000)
上面代码中,tco函数是尾递归优化的实现,它的奥妙就在于状态变量active。默认情况下, 这个变量是不激活的。一旦进入尾递归优化的过程,这个变量就激活了。然后,每一轮递归sum返回的都是undefined,所以就避免了递归执行;而accumulated数组存放每一轮sum执行的参数,总是有值的,这就保证了accumulator函数内部的while循环总是会执行。这样就很巧妙地将“递归”改成了“循环”,而后一轮的参数会取代前一轮的参数,保证了调用栈只有一层。