递归三要素:以阶乘为例

聊递归,我习惯先问一个问题:递归到底长什么样?

说白了,递归就是函数自己调用自己。但光会调用还不够,你得知道什么时候停、怎么往下推、最终返回什么。这三个问题,就是递归的三要素。

我个人觉得,理解递归最好的方式就是拿阶乘开刀。阶乘简单、直观,而且三要素特别清晰。咱们今天就拿它说事。

1. 终止条件:递归的刹车片

递归最怕什么?无限循环。你想想看,函数一直调用自己,栈帧越堆越高,最后栈溢出,程序崩了。我在项目中就见过这种惨案——一个同事写递归没写终止条件,线上服务直接挂了。

所以,终止条件是递归的第一道防线。

对于阶乘 n!,它的数学定义是:

  • 0! = 1
  • n! = n × (n-1)!

你看,0! 就是那个终止点。当 n 等于 0 时,递归就该停了。

核心原则:终止条件必须是一个可以直接计算的简单情况,不需要再递归。

2. 递推关系:递归的发动机

有了刹车,还得有油门。递推关系就是那个油门——它告诉你如何从当前问题走向更小的问题。

阶乘的递推关系很简单:

n! = n × (n-1)!

嗯,这里要注意:递推关系必须让问题规模越来越小。每次递归调用,n 都减 1,最终会撞到 0 这个终止条件。

我曾经在代码评审时看到有人写:

def factorial(n):
    return n * factorial(n+1)  # 错误!n 越来越大,永远停不下来

这种错误很隐蔽,但后果很严重。递推方向搞反了,递归就变成了死循环。

避坑指南:递推关系必须保证每次递归都向终止条件靠近一步。如果越递归越远,那就是灾难。

3. 返回值:递归的最终答案

递归不能光顾着调用,最后得返回一个结果。这个结果怎么来的?

对于阶乘:

  • 终止条件返回 1(0! = 1)
  • 递推关系返回 n × (n-1)! 的结果

你看,每一层递归都在等下一层的结果,然后乘上自己的 n,再返回给上一层。最终,最外层拿到的是完整的阶乘值。

我的习惯:写递归函数时,先想清楚返回值是什么类型。阶乘返回整数,那就别返回字符串或 None。保持返回值类型一致,代码才不容易出错。

完整代码示例

def factorial(n):
    # 终止条件
    if n == 0:
        return 1
    
    # 递推关系 + 返回值
    return n * factorial(n - 1)

# 测试
print(factorial(5))  # 输出 120

这个函数虽然只有几行,但三要素全齐了。你仔细品品:

  • 终止条件:n == 0 时返回 1
  • 递推关系:n * factorial(n-1)
  • 返回值:每一层都返回一个整数

三要素关系图

下面这张图,我用 SVG 画出了递归三要素的关系。你看一眼就明白了。

递归三要素关系图 递归函数 终止条件 n == 0 返回 1 递推关系 n × (n-1)! 返回值 整数结果 必须定义 缩小问题规模 逐层返回 三要素缺一不可,否则递归就会出问题

总结一下

递归三要素,说白了就是三个问题:

  1. 什么时候停? → 终止条件
  2. 怎么往下走? → 递推关系
  3. 最终返回什么? → 返回值

阶乘的例子虽然简单,但三要素的套路是通用的。你以后写任何递归函数,都可以拿这三个问题来检查。缺一个,递归就可能是定时炸弹。

嗯,今天就聊到这儿。记住这三要素,递归就不再神秘了。

可可精品资料库