🧠 引言
你对递归的理解是什么?
是“自己调用自己”?还是“每次都快把我绕晕”?
很多前端工程师在遇到树形结构渲染或深拷贝问题时,都会感受到递归的“玄学”。
这篇文章,我们从两个常见例子——九九乘法表和汉诺塔问题——图解 + 动手代码,彻底弄懂递归的本质、思维模型与易错点。
🧱 一、递归的本质
递归的两个核心条件:
- 递归终止条件(Base Case)
👉 否则会无限循环! - 递归规则(Recursive Case)
👉 当前任务如何拆解为更小的子任务
如果你会用 for 循环,就一定能学会递归!
✨ 二、递归演练:打印九九乘法表(对比递归和循环)
✅ 1. 循环方式(for 实现)
function print99() {
for (let i = 1; i <= 9; i++) {
let row = '';
for (let j = 1; j <= i; j++) {
row += `${j}x${i}=${i * j} `;
}
console.log(row);
}
}
print99();
✅ 2. 递归方式实现
function printLine(i, j) {
if (j > i) {
console.log();
return;
}
process.stdout.write(`${j}x${i}=${i * j} `);
printLine(i, j + 1);
}
function printTable(i = 1) {
if (i > 9) return;
printLine(i, 1);
printTable(i + 1);
}
printTable();
📌 输出结果
1x1=1
1x2=2 2x2=4
1x3=3 2x3=6 3x3=9
...
🗼 三、递归经典:汉诺塔(Tower of Hanoi)
❓ 问题描述
有三根柱子 A、B、C,n 个大小不同的盘子初始都在 A 上。每次只能移动一个盘子,且大盘不能放在小盘上。
目标:将所有盘子从 A 移到 C
✅ JS 实现
function hanoi(n, from, helper, to) {
if (n === 1) {
console.log(`移动盘子1:${from} ➡️ ${to}`);
return;
}
hanoi(n - 1, from, to, helper); // 1. 将 n-1 个盘从 A 移到 B
console.log(`移动盘子${n}:${from} ➡️ ${to}`); // 2. 将第 n 个盘从 A 移到 C
hanoi(n - 1, helper, from, to); // 3. 将 n-1 个盘从 B 移到 C
}
hanoi(3, 'A', 'B', 'C');
📌 输出示例
移动盘子1:A ➡️ C
移动盘子2:A ➡️ B
移动盘子1:C ➡️ B
移动盘子3:A ➡️ C
移动盘子1:B ➡️ A
移动盘子2:B ➡️ C
移动盘子1:A ➡️ C
🎯 四、递归的图形思维模型
hanoi(3, A, B, C)
├── hanoi(2, A, C, B)
│ ├── hanoi(1, A, B, C)
│ └── move 2
│ └── hanoi(1, C, A, B)
├── move 3
└── hanoi(2, B, A, C)
用分治的思想拆解问题
→ 直到问题“足够小”,可以直接解决
→ 再逐步“组合”回去
⚠️ 五、递归易错点
| 错误原因 | 正确思路 |
|---|---|
| 忘写终止条件 | 必须先判断 if (n === 1) 等 |
| 死循环或堆栈溢出 | 参数未递减或递推方向错误 |
| 不理解“先拆后合” | 画出递归调用树(建议手绘/调试) |
🔄 六、拓展任务
- 实现一个“文件夹结构遍历”(如 DFS 目录树)
- 实现 JSON 深拷贝(带循环引用处理)
- 用递归画一个分形图(如树、雪花)
🧩 总结一句话
递归不是玄学,是可以像“积木”一样组合思维的编程武器。掌握它,你的算法能力会飞跃一个层次!
下一篇预告:
📘 第3篇:【数组与指针的诡异关系】JS 和 C 双视角讲清楚底层结构!