【图解递归】从九九乘法表到汉诺塔,一文吃透递归精髓!

106 阅读2分钟

🧠 引言

你对递归的理解是什么?

是“自己调用自己”?还是“每次都快把我绕晕”?
很多前端工程师在遇到树形结构渲染深拷贝问题时,都会感受到递归的“玄学”。

这篇文章,我们从两个常见例子——九九乘法表汉诺塔问题——图解 + 动手代码,彻底弄懂递归的本质、思维模型与易错点。


🧱 一、递归的本质

递归的两个核心条件:

  1. 递归终止条件(Base Case)
    👉 否则会无限循环!
  2. 递归规则(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');

📌 输出示例

移动盘子1A ➡️ C
移动盘子2A ➡️ B
移动盘子1:C ➡️ B
移动盘子3A ➡️ C
移动盘子1B ➡️ A
移动盘子2B ➡️ C
移动盘子1A ➡️ 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)
死循环或堆栈溢出参数未递减或递推方向错误
不理解“先拆后合”画出递归调用树(建议手绘/调试)

🔄 六、拓展任务

  1. 实现一个“文件夹结构遍历”(如 DFS 目录树)
  2. 实现 JSON 深拷贝(带循环引用处理)
  3. 用递归画一个分形图(如树、雪花)

🧩 总结一句话

递归不是玄学,是可以像“积木”一样组合思维的编程武器。掌握它,你的算法能力会飞跃一个层次!


下一篇预告:

📘 第3篇:【数组与指针的诡异关系】JS 和 C 双视角讲清楚底层结构!