女孩和机器人一起思考

一起动脑筋 · 先看一个小故事

女孩想数一叠卡片:拿走一张后,剩下的仍是一叠卡片。能不能把同样的问题交给下一次自己?

把过程摊开来看

沿着编号看一遍,再用自己的话讲一遍
  1. 数 3 张1 加上数 2 张
  2. 数 2 张1 加上数 1 张
  3. 数 0 张返回 0
  4. 逐层返回1、2、3

递归用同一种方法解决更小的同类问题,再把结果带回来。

每次调用有自己的任务,不是所有层共用一份计数。必须知道什么时候不用再往下分。

看见调用,也看见返回

任务卡从左到右表示从栈底到栈顶。最右边是当前正在执行的一层。

第 1 步 · 进入 factorial(3)
f(3)

这一层要算 3 × f(2),先等待 f(2)。

第 2 步 · 进入 factorial(2)
f(3) 等待f(2)

这一层要算 2 × f(1),再新增一层。

第 3 步 · 继续到最小情况
f(3) 等待f(2) 等待f(1) 等待f(0) = 1

f(0) 直接返回 1,不再新增调用。

第 4 步 · 回到 factorial(1)
f(3) 等待f(2) 等待f(1) = 1

计算 1 × 1,得到 1 并返回。

第 5 步 · 回到 factorial(2)
f(3) 等待f(2) = 2

计算 2 × 1,得到 2 并返回。

第 6 步 · 回到 factorial(3)
f(3) = 6

计算 3 × 2,得到 6。本次调用完成。

01递归到底是什么?

递归(Recursion)是一种解决问题的方法:

把一个问题变成一个更小但结构相同的问题,不断缩小,直到遇到可以直接解决的情况。

较大的问题更小的同类问题可以直接解决

02递归代码通常有什么两部分?

基本情况

什么时候可以直接得到答案,不再继续调用。

递归情况

怎样把当前问题缩小,再交给下一层。

03一个最小的递归例子

void printDown(int n) {
    if (n == 0) {
        return;
    }

    std::cout << n << '\n';
    printDown(n - 1);
}

这里:

  • n == 0 是基本情况
  • printDown(n - 1) 是递归调用

04为什么 n - 1 很重要?

因为它让问题逐步变小:

n=3n=2n=1n=0

最终一定能到达基本情况。

05递归只是“自己调用自己”吗?

不够准确。

如果只有“自己调用自己”,但没有缩小问题 + 到达基本情况,那只是无限调用,并不是一个正确的递归算法。

06递归一定比循环好吗?

不一定。

有些问题用循环更简单,有些问题的结构天然适合递归,例如树、分治、回溯。

你已经知道了什么

  • 递归会把问题转化成规模更小的同类问题。
  • 递归通常包含基本情况和递归情况。
  • 问题规模必须不断接近基本情况。
  • 只有“函数调用自己”还不够构成正确递归。
  • 递归不一定比循环更好,要看问题结构。

下一篇:递归为什么一定要有停止条件?

轮到你来试一试

数卡片时每次递归仍传入 3,而不是 2,会接近结束吗?

想好了吗?点开看解释

不会。参数没有朝终止条件变化,会不断新增调用,最终可能耗尽调用栈。