JavaScript 递归
什么是递归?
递归是编程中一种强大的技术,它指的是函数调用自身的过程。简单来说,递归函数就是一个会在内部调用自己的函数。这种技术允许我们用简洁的代码解决一些看似复杂的问题,尤其是那些可以分解为相似子问题的任务。
递归的两个基本要素
- 基本情况(Base Case) - 停止递归的条件
- 递归情况(Recursive Case) - 函数调用自身的部分
如果没有基本情况,递归将无限进行,导致栈溢出错误。
递归的工作原理
让我们通过一个简单的例子来理解递归是如何工作的:
function countdown(n) {
// 基本情况
if (n <= 0) {
console.log("发射!");
return;
}
// 显示当前数字
console.log(n);
// 递归情况
countdown(n - 1);
}
// 调用递归函数
countdown(5);
输出结果:
5
4
3
2
1
发射!
在这个例子中:
- 当
n > 0
时,函数打印当前值,然后以n-1
为参数调用自己 - 当
n <= 0
时(基本情况),函数打印"发射!"并停止递归
递归调用堆栈
当我们使用递归时,每次函数调用都会在调用堆栈上创建一个新的执行环境。让我们可视化上述 countdown(5)
的调用堆栈:
常见递归示例
1. 计算阶乘
阶乘是递归的经典应用:
function factorial(n) {
// 基本情况
if (n === 0 || n === 1) {
return 1;
}
// 递归情况
return n * factorial(n - 1);
}
console.log(factorial(5)); // 输出: 120
计算过程:
factorial(5)
=5 * factorial(4)
=5 * 24
=120
factorial(4)
=4 * factorial(3)
=4 * 6
=24
factorial(3)
=3 * factorial(2)
=3 * 2
=6
factorial(2)
=2 * factorial(1)
=2 * 1
=2
factorial(1)
=1
(基本情况)
2. 斐波那契数列
斐波那契数列是另一个递归的典型应用:
function fibonacci(n) {
// 基本情况
if (n <= 1) {
return n;
}
// 递归情况
return fibonacci(n - 1) + fibonacci(n - 2);
}
console.log(fibonacci(6)); // 输出: 8
斐波那契数列: 0, 1, 1, 2, 3, 5, 8, 13, 21, ...
性能警告
上面的斐波那契函数实现虽然直观,但效率较低,因为它重复计算了许多子问题。对于较大的输入值,应使用动态规划或记忆化技术优化。
3. 优化的斐波那契实现(使用记忆化)
function fibonacciMemoized(n, memo = {}) {
// 检查是否已计算过
if (memo[n] !== undefined) {
return memo[n];
}
// 基本情况
if (n <= 1) {
return n;
}
// 存储计算结果
memo[n] = fibonacciMemoized(n - 1, memo) + fibonacciMemoized(n - 2, memo);
return memo[n];
}
console.log(fibonacciMemoized(50)); // 快速计算大数值
递归的实际应用场景
1. 目录树遍历
递归非常适合处理树形结构,如文件系统:
function traverseFileSystem(directory, level = 0) {
// 获取目录内容
const files = getFilesInDirectory(directory); // 假设这个函数存在
files.forEach(file => {
// 打印当前文件/目录名,缩进表示层级
console.log(' '.repeat(level) + file.name);
// 如果是目录,递归遍历
if (file.isDirectory) {
traverseFileSystem(file.path, level + 1);
}
});
}