女孩和机器人一起思考

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

3 个不同玩具排成一行有多少种顺序?第一个位置有 3 种选择,后面还有 2 和 1 种。

把过程摊开来看

沿着编号看一遍,再用自己的话讲一遍
  1. 3!3 乘 2!
  2. 2!2 乘 1!
  3. 1!1 乘 0!
  4. 0! = 1返回后得 1、2、6

阶乘 n! 表示从 1 乘到 n,约定 0! = 1。

递归写成 n! = n × (n−1)!,每次把任务缩小一层。3! 的返回结果是 6。

看见调用,也看见返回

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

第 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什么是阶乘?

正整数阶乘写作 n!

4! = 4 × 3 × 2 × 1 = 24

数学上规定:

0! = 1

02阶乘为什么天然有递归结构?

因为:

n! = n × (n - 1)!

比如:

4!=4 × 3!=4 × 3 × 2!

03递归代码怎样写?

long long factorial(int n) {
    if (n == 0) {
        return 1;
    }

    return n * factorial(n - 1);
}

这里假设输入 n >= 0

04factorial(4) 怎样向下调用?

factorial(1)factorial(2)factorial(3)factorial(4)

继续到 factorial(0) 后,不再递归,直接返回 1。

05答案怎样一层层算回来?

0! = 11! = 1 × 1 = 12! = 23! = 64! = 24

06为什么不能忽略输入范围?

如果传入负数,上面的函数会继续变成 -1、-2、-3……永远到不了 0。

所以真实程序要先规定函数的有效输入,或者主动检查。

07long long 能算很大的阶乘吗?

也不能无限大。

常见 64 位 long long 最多只能安全表示到 20!21! 已经超过有符号 64 位整数最大范围。

08阶乘一定要用递归吗?

不一定,也可以用循环:

long long result = 1;

for (int i = 2; i <= n; i++) {
    result *= i;
}

阶乘主要用来帮助学习递归结构,并不表示递归一定是最合适的实现。

你已经知道了什么

  • 阶乘满足 n! = n × (n-1)!
  • 0! = 1 可以作为基本情况。
  • 递归调用逐层减小 n,返回时逐层相乘。
  • 函数必须考虑有效输入范围。
  • 阶乘增长非常快,即使 long long 也很快溢出。
  • 阶乘也可以用循环实现。

下一篇:用递归解决“小问题里的小问题”

轮到你来试一试

为什么把 0! 写成 0 会把后面的答案都算错?

想好了吗?点开看解释

因为每层都乘上下一层,乘到 0 后结果都成为 0。正确的乘法起点是 1。