普通递归:每次函数调用时,都会在调用栈上创建一个新的栈帧以保存函数的局部变量、参数和返回地址。随着递归深度的增加,栈会不断增长,可能导致栈溢出错误。
尾递归 :当它在递归调用后不执行任何操作,直接返回递归调用的结果
尾递归调用:是一种特殊的递归形式,其中递归调用是函数的最后一个操作。尾递归将递归调用转换为迭代,从而节省函数调用栈的空间,避免栈溢出。许多编程语言和编译器能够对尾递归进行优化,称为尾递归优化(Tail Call Optimization,TCO)。
举两个例子:阶阶乘函数和斐波那契数列来分别使用普通递归和尾递归方式实现。
1. 阶乘函数
1.普通递归:
function factorial(n: number): number {
if (n === 0) {
return 1;
}
return n * factorial(n - 1);
}
// factorial(5)的调用过程:
// 5 * factorial(4)
// 5* 4 * factorial(3)
// 5* 4* 3 * factorial(2)
// 5* 4* 3* 2 * factorial(1)
// 5* 4* 3* 2* 1 * factorial(0)
// 5* 4* 3* 2* 1 * 1 ---> 120
2.尾递归
function tailRecursiveFactorial(n: number, acc: number = 1): number {
if (n === 0) {
return acc;
}
return tailRecursiveFactorial(n - 1, n * acc);
}
// tailRecursiveFactorial(5)的调用过程:
// tailRecursiveFactorial(4, 5)
// tailRecursiveFactorial(3, 20)
// tailRecursiveFactorial(2, 60)
// tailRecursiveFactorial(1, 120)
// tailRecursiveFactorial(0, 120)
// 输出120
在这种实现中,递归调用 tailRecursiveFactorial(n - 1, n * acc) 是函数的最后一个操作,没有其他操作跟在后面,因此是尾递归。
2. 斐波那契数列
1.普通递归:
function fibonacci(n: number): number {
if (n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
2.尾递归:
function tailRecursiveFibonacci(n: number, a: number = 0, b: number = 1): number {
if (n === 0) {
return a;
}
return tailRecursiveFibonacci(n - 1, b, a + b);
}
console.log(tailRecursiveFibonacci(10)); // 输出: 55
3. 尾递归调用的优缺点
优点:
- 节省内存:通过优化递归调用,减少调用栈的深度,避免栈溢出。
- 提高效率:减少函数调用的开销,提升性能。
缺点:
- 语言和编译器支持:并非所有编程语言和编译器都支持尾递归优化。
- 问题复杂度:对于某些复杂问题,尾递归可能不适用,仍需使用其他优化技术。