今天你 AI 赋能了吗?打开浏览器,在地址栏输入 xuefulu.ai ,按下 Enter 即可访问学府路1号!
2026年10月14日 星期三 10:40:23
常用网址: | | 学府路1号 https://xuefulu.ai | 下载 Python https://xuefulu.ai/Python.html | 下载 TRAE https://xuefulu.ai/Trae.html

一、什么是递归

递归听着吓人,其实就是一句话:一个函数在做的过程中,又调用了它自己。

先别想数学,看一个生活例子:数一串珠子,你数到一半让同桌「帮我把剩下的数完」,同桌又让下一个人数——这就是递归的味道。

代码里长这样:

Python
def f():
    print('我在干活')
    f()          # 在函数里又调用了自己

f()

它会不停地打印「我在干活」。因为 f() 里面又调 f(),永远没完——所以递归必须有个「停下来的条件」,不然就成死循环了(后面第九节会看到它怎么报错)。

递归 = 自己调自己 + 一个能停的出口。缺了出口,程序会一直往里钻,最后撞上 Python 的「递归层数上限」报错。

用循环写「倒计时」,也能得到一样的结果:

Python
i = 3
while i > 0:
    print(i)
    i -= 1

输出:

输出
3
2
1

什么时候用递归

适合递归不适合递归
问题能拆成「和自己长得一样、但更小」的子问题简单的一次性计算
比如:阶乘、斐波那契、文件夹遍历、汉诺塔比如:加减乘除、单趟循环

起步阶段,先把「递归长什么样、怎么停」搞懂,例子都是故意挑简单的。真正复杂的递归,大二大三才用得上。

二、递归的两个必备条件

写递归函数,脑子里必须同时装两样东西,缺一个都会出错:

条件作用忘了会怎样
① 出口(基线条件)告诉函数「到这一步就别再调自己了,直接返回」永远调自己 → 层数爆掉报错
② 每步变小每次调用,问题规模都要比上一次小一点规模不变 → 永远到不了出口

用「搬砖」打比方:你搬一块,剩下的让后面的人搬(自己调自己);但每次剩下的砖要比之前少(每步变小);直到只剩 0 块时直接说「搬完了」(出口)。

一个合格递归的骨架

Python
# 示意
def 函数(问题):
    if 问题已经小到最简单(出口):
        return 最简单答案
    把问题拆小一点
    return 函数(更小的问题)   # 自己调自己,规模更小

这是长相,不是能直接跑的代码。第三、四节用倒计时和阶乘把它变成真代码。

判断一个递归对不对,就问两句:「最最简单的那一步返回什么?」(出口)「每次是不是都比上次小?」(缩小)两句都答得上,递归就站得住。

课程思政|工匠精神与严谨求实 · 每退一步,都要离出口更近

写递归要同时想清两件事:什么时候停(出口),以及每次是不是真的更接近那个出口。少一样,函数就会一直往里钻,直到撞上 RecursionError。做事也一样,光有目标不够,还得确认每一步都在靠近它。给自己定计划时不妨问一句:这一周我做的,是让离目标更近了一点,还是原地打转?

三、第一个例子:倒计时

倒计时是最好懂的递归:从 n 数到 1。

Python
def countdown(n):
    if n <= 0:           # 出口:数到 0 或更小就停
        return
    print(n)
    countdown(n - 1)     # 自己调自己,而且 n 比上次小 1

countdown(3)

输出:

输出
3
2
1

走一遍:countdown(3) 打印 3,再调 countdown(2);打印 2,调 countdown(1);打印 1,调 countdown(0);到 0 触发出口 return,结束。

n <= 0 是出口:告诉函数「别再往里钻了」。countdown(n - 1) 保证每次都离出口更近。两个零件齐了,递归才安全。

如果忘了出口会怎样

Python
def countdown(n):
    print(n)
    countdown(n - 1)     # 没有出口!

countdown(3)

它会一直打印下去,直到 Python 受不了报错(详见第九节)。所以出口是递归的命门。

四、递归怎么走(调用栈)

递归最反直觉的是:它「一层层往里钻,再一层层往外退」。用一个调用栈来理解——每调一次,就压一层;返回时,再弹出来。

以 fact(3) 为例(阶乘函数下一节写,这里先借它看过程):

动作栈里有什么在做的事
调 fact(3)fact(3)3 不是 1,算 3 × fact(2),先去算 fact(2)
调 fact(2)fact(3) → fact(2)2 不是 1,算 2 × fact(1),先去算 fact(1)
调 fact(1)fact(3) → fact(2) → fact(1)1 触发出口,返回 1
fact(1) 返回fact(3) → fact(2)fact(2) 拿到 1,算 2 × 1 = 2,返回 2
fact(2) 返回fact(3)fact(3) 拿到 2,算 3 × 2 = 6,返回 6

看出来了吗:「进去」和「出来」是反着的。先进去的最后出来(像叠盘子)。每一层都「等」着里面那层先给结果。

Python
def fact(n):
    if n <= 1:
        return 1
    return n * fact(n - 1)

print(fact(3))

输出 6。这正是上面表格最后的结果。

每层递归都会占用一点内存(压栈)。递归层数太深,内存会被压满,程序就崩了——所以递归层数要有节制,别拿它去数到一百万。
记不住过程不要紧。写递归时你只管假设「里面的那次调用已经算好了」,比如写 fact(n) 时,直接相信 fact(n-1) 会返回正确答案,你只负责把它和 n 乘起来。

五、阶乘与累加

阶乘:n! = n × (n-1) × … × 1

阶乘的定义天生就是递归的:n! = n × (n-1)!,而 1! = 1 就是出口。

Python
def fact(n):
    if n <= 1:            # 出口
        return 1
    return n * fact(n - 1)   # 自己调自己,规模更小

print(fact(5))
print(fact(1))

输出:

输出
120
1

累加:1 + 2 + … + n

和阶乘一个套路,只是把「乘」换成「加」:

Python
def sum_to(n):
    if n == 1:           # 出口
        return 1
    return n + sum_to(n - 1)

print(sum_to(5))

输出 15(也就是 5+4+3+2+1)。

阶乘和累加是「递归双件套」。它们共同说明了递归的写法:出口返回最简单情况,否则把问题拆成「当前这一步 + 更小规模的同类问题」。

递归和循环的对照

Python
# 用循环写累加,更省内存
total = 0
for i in range(1, 6):
    total += i
print(total)

输出也是 15。同样的结果,循环写得也不难——这正是下一节要聊的:递归能做的,循环基本都能做。

Python
def my_max(lst):
    if len(lst) == 1:
        return lst[0]
    m = my_max(lst[1:])
    return lst[0] if lst[0] > m else m

print(my_max([3, 9, 2, 7]))

输出 9。

课程思政|科技自立自强 · 大难题,拆着拆着就小了

阶乘 n! = n × (n-1)! 天生就是递归的:把一个大问题,拆成一个「当前这一步」加上一个「更小、但长得一模一样的同类问题」。关键技术被人卡脖子,也是这么一点点攻下来的——没有谁一口气拿下整条产业链,靠的都是把大目标拆成一个个能落地的子问题,逐个解决。学会这手拆解,比记住一行代码值钱得多。

六、兔子生小兔:斐波那契的由来

斐波那契数列不是凭空想出来的,它最早来自一道数兔子的题。1202 年,意大利数学家斐波那契在《算盘全书》里问了这样一个问题:

题目:一对小兔子,出生后长一个月就能长大,从第 3 个月起每个月都会生出一对小兔子。假设兔子永远不死,问:n 个月后一共有多少对兔子?

光看题面容易绕晕,我们每个月记一笔账(第 1 个月只有刚买回来的那对小兔子):

月份幼兔(下个月才生)成年兔(本月会生)总对数
第 1 个月101
第 2 个月011
第 3 个月112
第 4 个月123
第 5 个月235
第 6 个月358
第 7 个月5813
第 8 个月81321

把每月的「总对数」单独抽出来看:1, 1, 2, 3, 5, 8, 13, 21 …——这正是斐波那契数列。规律就藏在记账的两条规则里:

  1. 上个月的兔子这个月一只都没少,全部保留:这部分就是第 n−1 个月的总数;
  2. 本月新生的小兔子,来自上上个月就已经存在的兔子——它们到这个月全都成年了,每对生一对:这部分就是第 n−2 个月的总数。

设 f(n) 表示第 n 个月的总对数,于是:

f(n) = f(n-1) + f(n-2)(n >= 3 时成立),出口是 f(1) = f(2) = 1——大问题由两个更小的同类问题拼出来,这正是递归的形状。照着翻译成 Python:

Python
def rabbit(n):
    if n == 1 or n == 2:    # 出口:前两个月都是 1 对
        return 1
    return rabbit(n - 1) + rabbit(n - 2)   # 上月保留的 + 本月新生的

for m in range(1, 9):
    print('第', m, '个月:', rabbit(m), '对')
输出
第 1 个月: 1 对
第 2 个月: 1 对
第 3 个月: 2 对
第 4 个月: 3 对
第 5 个月: 5 对
第 6 个月: 8 对
第 7 个月: 13 对
第 8 个月: 21 对

输出和上面的账本逐月对得上。「先手算小规模、再用程序验证」,这道题给了我们一个完整的示范。注意出口有两个(n == 1 和 n == 2):如果只写 n == 1,rabbit(2) 会继续拆成 rabbit(1) 和 rabbit(0),而 n 每次减 1 或减 2,rabbit(0)、rabbit(-1) 就一路变成负数、永远绕开出口,最后报 RecursionError。另外,这个朴素的递归写法很慢,慢在哪里、怎么补救,见第十一节的坑 4。

课程思政|工匠精神与严谨求实 · 先算小账,再信程序

这道题流传了八百多年,靠的不是拍脑袋,而是一个月一个月老老实实记出来的账。写递归也一样:先手算前几项、列一张小表,再拿程序输出和账本逐项核对。用数据说话、拿事实验证是工程师最朴素的严谨——程序跑通了不等于对了,和真实记录对上了才算数。

七、几个经典案例

1. 斐波那契数列

数列:1, 1, 2, 3, 5, 8, 13 … 规则是「每一项等于前两项之和」,开头两项都是 1。写成递归:

Python
def fib(n):
    if n <= 1:           # 前两项都是出口
        return n
    return fib(n - 1) + fib(n - 2)

print(fib(10))

输出 55(第 10 项是 55)。注意它同时调了自己两次,这正是它慢的原因(见第十一节)。

2. 最大公约数(辗转相除法)

Python
def gcd(a, b):
    if b == 0:           # 出口:余数为 0
        return a
    return gcd(b, a % b) # 用「除数、余数」缩规模

print(gcd(12, 18))
print(gcd(7, 5))

输出 6、1。12 和 18 的最大公约数是 6;7 和 5 互质,最大公约数是 1。

3. 汉诺塔

把 n 个盘子从 A 柱移到 C 柱,每次只能挪一个,且大盘不能压小盘。递归解法是「先把上面 n-1 个挪到 B,再把最大的挪到 C,最后把 n-1 个从 B 挪到 C」:

Python
def hanoi(n, a, b, c):
    if n == 1:                  # 出口:只剩一个盘,直接挪
        print(a, '->', c)
        return
    hanoi(n - 1, a, c, b)       # 上面 n-1 个:A -> B
    print(a, '->', c)           # 最大的那个:A -> C
    hanoi(n - 1, b, a, c)       # 上面 n-1 个:B -> C

hanoi(3, 'A', 'B', 'C')

输出(每一步是「从哪根柱挪到哪根柱」):

输出
A -> C
A -> B
C -> B
A -> C
B -> A
B -> C
A -> C

4. 字符串反转

Python
def rev(s):
    if len(s) <= 1:        # 出口:空串或单字符直接返回
        return s
    return rev(s[1:]) + s[0]   # 把第一个字符丢到最后

print(rev('奶茶真好喝'))
print(rev('今天不减肥'))

输出:

输出
喝好真茶奶
肥减不天今

5. 列表求和

Python
def lsum(nums):
    if not nums:          # 出口:空列表,和为 0
        return 0
    return nums[0] + lsum(nums[1:])

print(lsum([15, 8, 22]))

输出 45——奶茶 15 元 + 柠檬水 8 元 + 蛋糕 22 元的购物车总价。

顺手再看一个同样套路的:求 a 的 b 次方,出口是 b == 0 时返回 1:

Python
def power(a, b):
    if b == 0:
        return 1
    return a * power(a, b - 1)

print(power(2, 5))

输出 32。

6. 猴子吃桃

一道流传很广的趣味题:一只猴子摘了一堆桃子,每天都吃掉剩下的一半再多啃一个,到第 10 天想吃时发现只剩 1 个。问:第一天一共摘了多少个?

正着算很费劲,倒着推却非常简单:只要知道「今天的数量」,就能算回「昨天的数量」——昨天 = (今天 + 1) × 2。这正是递归的形状:第 10 天是出口(1 个),每一天都由「下一天」推出来。

Python
def peach(day):
    if day == 10:                    # 出口:第 10 天只剩 1 个
        return 1
    return (peach(day + 1) + 1) * 2  # 昨天 = (今天 + 1) × 2

print(peach(1))

输出 1534——第一天竟然摘了 1534 个桃子。

天数12345678910
桃子数15347663821909446221041
这道题的诀窍是「倒着想」:找不出「从前往后怎么算」时,先问问「这一天的结果由哪一天推出来」。把这句关系写成一行 return,递归就出来了。

7. 青蛙跳台阶

一只青蛙要跳上 n 级台阶,每次只能跳 1 级或 2 级,问一共有多少种跳法?

站在终点倒着看:最后一次要么从第 n-1 级跳 1 级上来,要么从第 n-2 级跳 2 级上来,两种情况互不重叠,所以 jump(n) = jump(n-1) + jump(n-2)。出口是最简单的两级:1 阶只有 1 种跳法,2 阶有 2 种。

Python
def jump(n):
    if n <= 2:                        # 出口:1 阶 1 种,2 阶 2 种
        return n
    return jump(n - 1) + jump(n - 2)  # 最后一步:跳 1 阶或跳 2 阶

print(jump(5))
print(jump(10))

输出 8 和 89。眼熟吗?1, 2, 3, 5, 8, 13…这就是斐波那契数列换了一身故事——很多看似不同的问题,递归式其实是同一个。

8. 剥洋葱:拍平嵌套列表

列表里还能套列表,像俄罗斯套娃:[1, [2, 3], [4, [5, 6]]]。想把所有数字一层层剥出来拍成一维,用循环写会非常别扭,而递归天然擅长这种嵌套结构:碰到盒子就打开(调自己),碰到单个数字就收下。

Python
def flat(xs):
    result = []
    for x in xs:
        if isinstance(x, list):   # 还是盒子:打开它,递归处理
            result += flat(x)
        else:                     # 是单个数字:直接收下
            result.append(x)
    return result

data = ['奶茶', ['加珍珠', '三分糖'], ['加椰果', ['换燕麦奶']]]
print(flat(data))

输出 ['奶茶', '加珍珠', '三分糖', '加椰果', '换燕麦奶']。这个案例的出口不再是「数量变小」,而是「嵌套深度变浅」:每递归一层,剥掉一层盒子,最深处必然是最普通的那个元素。文件目录、网页标签这类「套娃结构」,用的都是同一招。

这八个案例都有一个共同点:先写出口(最简单情况怎么返回),再写「当前这步 + 调自己处理更小的部分」。套路吃透,换什么问题都能套。

AI 助手小贴士|经典案例值得让 AI 把「看不见的过程」画出来。

打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:用文字画出 3 层汉诺塔的移动步骤和递归调用树,并解释为什么最少要 2 的 n 次方减 1 步

八、递归 vs 循环

老实说:前面这些递归,用循环都能写,而且循环通常更快、更省内存。

递归循环
写法短、像数学定义,好懂稍长,但要自己管计数器
速度慢(每次调用都有开销)快
内存每层都压栈,深了会爆基本不额外占内存
适合问题天然递归(树、目录、分治)绝大多数日常计算

同一个「列表求和」,循环版:

Python
nums = [15, 8, 22]
total = 0
for x in nums:
    total += x
print(total)

输出 45,和递归版一模一样,但循环不用压栈、不会爆层数。

Python
def show(s):
    if not s:
        return
    print(s[0])
    show(s[1:])

show('hi')

输出:

输出
h
i
老师给的口诀:能用循环就别用递归。递归的价值不在「炫技」,而在那些「结构本身就是一层套一层」的问题(比如遍历文件夹、解析树)。起步阶段,递归主要用来训练拆解问题的思维。
考试/作业里让你「用递归写」时再用递归;平时自己算,优先循环。别为了递归而递归。

课程思政|诚信与职业操守 · 别为了递归而递归

前面那些递归,用循环都能写,而且通常更快、更省内存。递归真正的用武之地,是那些「结构本身就一层套一层」的问题,比如遍历文件夹。选工具要看问题合不合适,不看它显得高不高级。硬把简单的活写成花哨的样子,看着厉害,其实既不诚实也不专业——对自己实在一点,什么合用就用什么。

九、忘记出口会怎样(RecursionError)

递归如果忘了写出口(或出口永远触发不了),函数会一直调自己,层数越压越深,直到撞上 Python 的上限,抛出 RecursionError。

Python
def f(n):
    return f(n - 1)     # 只有调用,没有出口

f(5)
报错
RecursionError: maximum recursion depth exceeded

这就是真实的报错原文:递归层数超过了上限。下面这种「规模没变小」也是同样的错:

Python
def f(n):
    f(n)               # 传进去的还是 n,永远到不了出口

f(3)
报错
RecursionError: maximum recursion depth exceeded

两种不同的「忘出口」写法,报错都是同一句 RecursionError: maximum recursion depth exceeded。

看到 RecursionError,第一反应就是查:出口写了吗?每次调用规模真的变小了吗?十有八九是这两处漏了。循环里的死循环靠 Ctrl+C 停,递归的死循环只能改代码。
Python 默认的递归上限大约是 1000 层。你可以用 sys.setrecursionlimit() 调大,但那只是推迟崩溃,真正该做的是让递归能正常结束,而不是硬撑层数。

十、十个基础案例

下面十个例子都很短,建议每个都亲手敲一遍、跑一遍。它们覆盖了递归最常见的写法。

1. 倒计时(从 n 数到 1)

Python
def countdown(n):
    if n <= 0:
        return
    print(n)
    countdown(n - 1)

countdown(3)

输出:

输出
3
2
1

2. 阶乘

Python
def fact(n):
    if n <= 1:
        return 1
    return n * fact(n - 1)

print(fact(0))
print(fact(6))

输出:

输出
1
720

3. 求 1 加到 n 的和

Python
def sum_to(n):
    if n == 1:
        return 1
    return n + sum_to(n - 1)

print(sum_to(5))

输出 15。

4. 求购物清单总价

Python
def lsum(nums):
    if not nums:
        return 0
    return nums[0] + lsum(nums[1:])

print(lsum([15, 8, 22]))

输出 45:奶茶 15 + 柠檬水 8 + 蛋糕 22。

5. 求字符串长度

Python
def my_len(s):
    if s == '':
        return 0
    return 1 + my_len(s[1:])

print(my_len('珍珠奶茶'))
print(my_len('hello'))

输出:

输出
4
5

6. 反转字符串

Python
def rev(s):
    if len(s) <= 1:
        return s
    return rev(s[1:]) + s[0]

print(rev('奶茶真好喝'))

输出 喝好真茶奶。

7. 判断回文串(正着读反着读一样)

Python
def is_pal(s):
    if len(s) <= 1:
        return True
    if s[0] != s[-1]:
        return False
    return is_pal(s[1:-1])

print(is_pal('上海自来水来自海上'))
print(is_pal('abc'))

输出:

输出
True
False

8. 求 2 的 n 次方

Python
def pow2(n):
    if n == 0:
        return 1
    return 2 * pow2(n - 1)

print(pow2(3))
print(pow2(0))

输出:

输出
8
1

9. 求最大公约数

Python
def gcd(a, b):
    if b == 0:
        return a
    return gcd(b, a % b)

print(gcd(12, 18))

输出 6。

10. 十进制转二进制(字符串)

Python
def to_bin(n):
    if n < 2:
        return str(n)
    return to_bin(n // 2) + str(n % 2)

print(to_bin(10))
print(to_bin(13))

输出:

输出
1010
1101

十一、常见坑

坑 1:忘写出口 → RecursionError

Python
def f(n):
    return f(n - 1)   # 没出口
f(5)
报错
RecursionError: maximum recursion depth exceeded

每次调用都压一层栈,超过上限就崩。先写出口再写递归体。

坑 2:规模没变小,永远到不了出口

Python
def f(n):
    f(n)            # 传的还是 n,没缩小
f(3)
报错
RecursionError: maximum recursion depth exceeded

必须让参数朝出口方向靠近,比如 n - 1、lst[1:]、n // 2。

坑 3:递归里忘写 return

递归体里调了自己,却忘了 return 把结果交出来,外面拿到的是 None:

Python
def f(n):
    if n == 0:
        return 0
    else:
        f(n - 1) + 1     # 算了却没 return

print(f(3))
报错
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

f(n-1) + 1 算出来的结果被丢掉了,函数默认返回 None,None + 1 直接报类型错。把这一行改成 return f(n - 1) + 1 即可。

坑 4:朴素斐波那契慢得离谱

前面那个 fib(n) 每次都「双调用」,会大量重复计算。算 fib(20) 就要调自己两万多次:

Python
count = 0
def fib(n):
    global count
    count += 1
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

print(fib(20), count)

输出 6765 21891——为了算第 20 项,函数被调用了 21891 次!同样的活,循环只要 20 次。所以真要算斐波那契,用循环或记住结果(记忆化),别用这个朴素递归。

递归不是「写得短就快」。像斐波那契这种「一个值被反复算」的,朴素递归是指数级变慢。大数值请用循环;如果一定要递归,也得加「缓存」避免重复算。
四个坑一句话记:出口要写、规模要小、结果要 return、别拿朴素递归算会重复的东西。

十二、报错手册

递归相关最常见的报错就这两类,照抄本机解释器原文。

Python
def f(n):
    return f(n - 1)
f(5)
报错
RecursionError: maximum recursion depth exceeded

原因:没有出口,或出口触发不了,层数超过上限。检查「出口条件」和「每次有没有缩小规模」。

Python
def f(n):
    f(n)
f(3)
报错
RecursionError: maximum recursion depth exceeded

原因:传给自己的还是 n,规模一点没变小,永远到不了出口。改成 f(n - 1) 之类。

Python
def f(n):
    if n == 0:
        return 0
    else:
        f(n - 1) + 1
print(f(3))
报错
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

原因:递归体忘了 return,函数返回 None,后面拿 None 做加法。补上 return。

Python
def fact(n):
    return n * fact(n - 1)   # 漏了出口
print(fact(3))
报错
RecursionError: maximum recursion depth exceeded

原因:fact 没有处理 n <= 1 的出口,会一路乘到负数还不停。补上 if n <= 1: return 1。

AI 助手小贴士|递归写错往往不报错、只是算不对,练改错眼力。

打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:出 3 道递归改错题:分别缺出口、没缩小问题规模、返回值没传回去,让我找出问题并修复

十三、递归思维小结

学了这么多例子,其实写递归就三步,记住就不慌:

步骤要想什么例子(数 1~n 的和)
① 找出口最简单到不能再小的情况返回什么n == 1 时返回 1
② 信里面假设「更小的那次调用」已经算对了相信 sum_to(n-1) 会给正确结果
③ 拼当前用这一步 + 里面的结果,凑出答案返回 n + sum_to(n-1)

用三步法写一个:数一下列表里偶数的个数

Python
def count_even(lst):
    if not lst:                 # ① 出口:空列表,偶数 0 个
        return 0
    cur = 1 if lst[0] % 2 == 0 else 0   # 当前这个数算 1 或 0
    return cur + count_even(lst[1:])     # ②+③ 当前 + 更小的列表

print(count_even([1, 2, 3, 4, 5, 6]))

输出 3(2、4、6 共三个偶数)。

新手最容易卡在哪

卡住的地方怎么破
不知道出口写什么先想「最小的输入」:空列表?n=1?长度为 1 的字符串?
不敢写「自己调自己」先假设它已经能用了,你只管把它和当前这步拼起来
写出来死循环检查每次调用的参数有没有朝出口方向变小
把「递归」当成一种思维方式而不是语法糖:先想最小情况怎么返回,再想「大问题 = 当前一小步 + 更小的问题」。能这么想,阶乘、斐波那契、汉诺塔都是同一个套路。

AI 助手小贴士|递归最难在「看不见调用过程」。让 AI 把调用树一层层画出来,画两棵树就通了。

打开任意 AI 对话工具(网页版或 App 都可以),试试这样问:用文字画出 factorial(4) 的递归调用树:每一层传了什么参数、什么时候到底、返回值怎么一层层带回去

十四、随堂自测(10 题)

点击选项即可判分,选错的题会立刻展开解析。做完可以看到正确率和错题清单,也可以重新答题。

已答 0 / 10 · 正确 0 —

1下面哪句话最准确地描述了「递归」?

  • A. 一个函数自己调用自己
  • B. 函数里再调用别的函数
  • C. 把代码写在一个循环里
  • D. 函数只能被调用一次
答案:A。递归就是函数在执行过程中又调用了它自己。关键不只是调用,而是「调用的是自己」,并且要有出口让它停下来。

2一个正确的递归函数,必须同时具备哪两个条件?

  • A. 循环和列表
  • B. 出口(基线条件)和每步缩小规模
  • C. 两个参数
  • D. return 和 print
答案:B。递归两大命门:① 出口,告诉函数「到这步别再调自己了」;② 每次调用规模都比上次小,才能最终到达出口。缺一个都会无限递归。

3fact(n) 里写成 return n * fact(n - 1),这一步主要体现了递归的哪一点?

  • A. 函数在逃避计算
  • B. 把问题拆成更小的同类问题(n 比 n-1 小)
  • C. 在给变量改名
  • D. 两个函数互相调用
答案:B。fact(n) 借助 fact(n-1) 算结果,正是「当前这一步 + 规模更小的同类问题」。配合 n<=1 的出口,递归才成立。

4递归函数忘了写出口,运行时会怎样?

  • A. 输出 0
  • B. 正常结束
  • C. 打印 None
  • D. 抛出 RecursionError
答案:D。没有出口就一直调自己,层数越压越深,超过 Python 上限就抛 RecursionError: maximum recursion depth exceeded。

5关于「递归」和「循环」,下面哪种说法更稳妥?

  • A. 递归比循环快
  • B. 循环通常更快、更省内存,能用循环就别用递归
  • C. 递归永远不会出错
  • D. 循环不能做累加
答案:B。递归每次调用都有压栈开销、层数深了会爆栈;循环基本不额外占内存。起步阶段口诀:能用循环就别用递归。

6程序运行时提示 RecursionError: maximum recursion depth exceeded,首先该查什么?

  • A. 是不是少写了 print
  • B. 是不是把变量名拼错了
  • C. 出口写没写、规模有没有缩小
  • D. 是不是没导入库
答案:C。这句报错 100% 是递归没正常结束:要么没出口,要么每次调用的规模没变小。先查这两处。

7下面哪个是递归的经典应用场景?

  • A. 汉诺塔(盘子一层套一层地挪)
  • B. 简单的 1+2+3 累加
  • C. 单次 if 判断
  • D. 打印一行文字
答案:A。汉诺塔、文件夹遍历、树结构这类「天然一层套一层」的问题最适合递归。普通累加用循环更合适。

8递归函数里调了自己却忘了写 return,外部拿到的结果通常是?

  • A. 正确的值
  • B. 0
  • C. 报错 SyntaxError
  • D. None
答案:D。没 return 的函数默认返回 None。若后面还拿它做运算,会进一步报 TypeError: ...NoneType...。记得把递归结果 return 出来。

9朴素递归版斐波那契(fib(n)=fib(n-1)+fib(n-2))为什么慢?

  • A. 因为 Python 不支持加法
  • B. 因为函数名太长
  • C. 因为大量子问题被重复计算
  • D. 因为没用列表
答案:C。每个 fib 都要双调用,导致同样的子问题被反复算(算 fib(20) 要调用自己两万多次)。真要算请用循环或加缓存,别用朴素递归。

10老师给的递归使用建议,最贴近下面哪句?

  • A. 所有循环都该改成递归
  • B. 能用循环就别用递归,递归留给天然递归的问题
  • C. 递归层数越多越好
  • D. 递归不需要出口
答案:B。递归主要用来训练拆解思维,和处理「结构本身一层套一层」的问题。日常计算优先循环,别为了递归而递归。