尾递归
2023 javascript尾递归,即在函数尾位置调用自身,尾递归是递归的一种特殊情况,也是一种特殊的尾调用。
特点
尾递归在普通的尾调用的基础上多了 2 个特征:
- 在尾部调用的函数自身(self-called)
- 可通过优化,使得计算仅占常量栈空间(stack space)
在递归调用的过程中,系统为每一层的返回点、局部量等开辟了栈来存储,递归次数多了容易造成栈溢出。
我们可以使用尾递归,即在一个函数中所有递归形式的调用都出现在函数末尾,对应尾递归来说,由于只存在一个调用记录,所以永远不会发生“栈溢出”。尾递归优化则使原本 O(n) 的调用栈空间只需要 O(1)。
例子
我们使用常用的阶乘举例
普通的递归函数如下:
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
factorial(5); // 120
如果 n 等于 5,这个方法要执行 5 次,才返回最终的计算表达式,这样每次都要保存这个方法,就容易造成栈溢出,复杂度为 O(n)。
使用尾递归:
function factorial(n, total = 1) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5); // 120
每一次返回的就是一个新的函数,不带上一个函数的参数,也就不需要储存上一个函数了。尾递归只需要保存一个调用栈,复杂度 O(1)
更多例子
斐波那契数列
function fibonacci(n) {
if (n === 1 || n === 2) return 1;
return fibonacci(n - 1) + fibonacci(n - 2);
}
使用尾递归优化:
function fibonacci(n, pre = 1, cur = 1) {
if (n <= 1) return cur;
return fibonacci(n - 1, cur, pre + cur);
}