递归:“自己调用自己”,每次调用时都会把问题变得更简单一点,直到问题简单到可以直接解决为止。
就像剥洋葱:你先剥开一层,发现里面结构跟整个洋葱一样,于是继续剥这一层,直到剥到最中心最小的一片,然后从内向外,把每一层的结果组合起来,得到完整的答案。
写递归时,必须明确两件事:
1. 递归结束条件(Base Case)
- 作用:告诉递归“什么时候该停了”,避免无限循环。
- 比喻:像你拆套娃时,发现“这个套娃不能再拆了”(比如最小的那个),就停止。
- 例子:
- 计算阶乘
n!时,结束条件是0! = 1或1! = 1。 - 遍历文件夹时,结束条件是“当前文件夹没有子文件夹”。
- 计算阶乘
n!称为阶乘,表示从1乘到n,就像一步步爬阶梯一样,一步步乘上去。
n! = n ×\times× n−1{n-1}n−1 ×\times× n−2{n-2}n−2 ×\times× n−3{n-3}n−3 ×\times× ……… ×\times× 3 ×\times× 222 ×\times× 111
def factorial(n):
if n == 0: # 结束条件
return 1
# 否则继续递归...
如果没有结束条件:程序会一直递归下去,直到崩溃。
类似:
“从前有座山,山上有座道观,道观里有一个老道长和小道长在讲故事,他们在讲什么呢?他们讲的是
‘从前有座山,山上有座道观,道观里有一个老道长和小道长在讲故事,他们在讲什么呢?他们讲的是
“从前……”’”
2. 递归规律(Recursive Case)
- 作用:定义“如何把问题拆小”,让每次递归都向结束条件靠近。
- 比喻:像你每次拆开一个盒子后,对里面的更小的盒子重复同样的拆解操作。
- 关键点:
- 问题规模必须减小:比如计算
n!时,递归调用factorial(n-1)。 - 相信递归能工作:不要深入跟踪每次调用,假设子问题能正确解决(类似“相信下一层套娃能被正确拆开”)。
- 问题规模必须减小:比如计算
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n - 1) # 问题规模减小(n-1)
完整例子:计算阶乘
def factorial(n):
# 1. 结束条件
if n == 0:
return 1
# 2. 递归规律:n! = n * (n-1)!
else:
return n * factorial(n - 1)
执行流程(以 factorial(3) 为例):
3! = 3 * factorial(2)2! = 2 * factorial(1)1! = 1 * factorial(0)0! = 1(触发结束条件,开始返回结果)- 逐层返回:
1 * 1 = 1→2 * 1 = 2→3 * 2 = 6
常见问题
- 忘记结束条件:无限递归,最终崩溃(Stack Overflow)。
- 问题没拆小:比如递归调用时参数没变化,导致无限循环。
- 效率问题:递归可能有重复计算(如斐波那契数列),可用记忆化优化。
斐波那契数列:F0=0,F1=1F_0 = 0, F_1 = 1F0=0,F1=1
n≥2,Fn=Fn−1+Fn−2n \geq 2, F_n = F_{n - 1} + F_{n - 2}n≥2,Fn=Fn−1+Fn−2
记忆化优化:
在递归函数中,一个函数会不断调用自己,把大问题拆成小问题。但有些小问题会被反复计算很多次,数据越大,重复计算就越多,浪费大量时间。
记忆化优化,就是给递归函数“做笔记”。
每次算出一个子问题的结果,就把它记下来(比如存到一个数组或字典里)。
下次再遇到同样的问题,直接查“笔记”,不用重新算。
总结
- 结束条件:递归的“底线”,避免无限循环。
- 递归规律:把大问题拆成小问题,并假设子问题能解决。
- 信任递归:像相信“下一层盒子会被正确拆解”一样,不要过度纠结细节。
44

被折叠的 条评论
为什么被折叠?



