递归三要素:以阶乘为例
聊递归,我习惯先问一个问题:递归到底长什么样?
说白了,递归就是函数自己调用自己。但光会调用还不够,你得知道什么时候停、怎么往下推、最终返回什么。这三个问题,就是递归的三要素。
我个人觉得,理解递归最好的方式就是拿阶乘开刀。阶乘简单、直观,而且三要素特别清晰。咱们今天就拿它说事。
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 画出了递归三要素的关系。你看一眼就明白了。
总结一下
递归三要素,说白了就是三个问题:
- 什么时候停? → 终止条件
- 怎么往下走? → 递推关系
- 最终返回什么? → 返回值
阶乘的例子虽然简单,但三要素的套路是通用的。你以后写任何递归函数,都可以拿这三个问题来检查。缺一个,递归就可能是定时炸弹。
嗯,今天就聊到这儿。记住这三要素,递归就不再神秘了。