Python基础:递归
文章目录
一、 知识点详解
1.1 递归是什么?
- 定义:递归是一种通过递归函数调用自身,实现将复杂问题分解为更小的同类子问题来解决的编程技巧。
递归函数必须包含两个核心要素:
1. 基线条件:
递归终止的条件,避免无限循环(如数值为0、空字符串、到达最底层节点等)。
2. 递归条件:
函数调用自身的逻辑,每次调用必须缩小问题规模,向基线条件靠近。
1.2 为什么使用递归?
- 简化代码:
递归能用简洁的代码解决复杂问题(如树遍历、分治算法)。 - 自然映射问题:
适合处理具有自相似性的问题(如数学递推公式、文件系统层级结构)。 - 分治思想:
将大问题分解为规模更小的同类子问题(如快速排序、斐波那契数列)。
1.3 怎么使用递归?
通过定义递归函数来实现复杂问题简单化
函数实现步骤:
1. 定义基线条件:
明确递归何时终止(如n=0
、n=1
、空集合等)。
2. 缩小问题规模:
每次递归调用必须修改参数,使其更接近基线条件(如n-1
、子字符串、子列表等)。
1.4 案例说明
以上说明可能难于理解, 咱们用一个“拆盒子”小案例辅助理解 :
假如,你的朋友送给你一个礼物盒子,但是这个盒子比较有趣:
盒子里面装着一个更小的盒子,再里面又有一个更小的盒子……
直到最后一个盒子里装着一颗宝石。
递归就是“自己拆自己里面的盒子”:
- 基线条件(停止条件):
当拆到最后一个盒子(比如第5层)时,不再拆了,直接拿出宝石(这是递归的终点)。 - 递归条件(重复逻辑):
每次拆开一个盒子,发现里面还有盒子,就继续拆里面的盒子(调用自己处理更小的问题)。
示例代码 :
def open_boxes(current_layer=1, max_layers=5):
'''
--- 用于解释递归函数的拆盒子小案例 ---
参数:
current_layer: 当前拆到的层数(默认从第1层开始)
max_layers: 最大层数(基线条件)
返回:
当拆到第5层时返回宝石信息,否则继续拆下一层盒子
'''
print(f"🔍正在拆第{current_layer}层盒子...".center(20, '='))
# 基线条件:拆到最大层数(第5层)时找到宝石
if current_layer == max_layers:
print('终于不是盒子了!')
return '✨ 里面有一颗宝石💎 ✨'
# 递归条件:打开后发现内层盒子,继续拆
print('打开后发现...还是一个盒子📦 !')
result = open_boxes(current_layer + 1) # 递归调用,层数+1
return result # 回归阶段:将结果逐层返回
# 调用函数并打印结果
print(open_boxes())
输出结果 :
====🔍正在拆第1层盒子...====
打开后发现...还是一个盒子📦 !
====🔍正在拆第2层盒子...====
打开后发现...还是一个盒子📦 !
====🔍正在拆第3层盒子...====
打开后发现...还是一个盒子📦 !
====🔍正在拆第4层盒子...====
打开后发现...还是一个盒子📦 !
====🔍正在拆第5层盒子...====
终于不是盒子了!
✨ 里面有一颗宝石💎 ✨
执行流程 :
调用函数->
递推-> 拆开第1层盒子...
递推-> 拆开第2层盒子...
递推-> 拆开第3层盒子...
递推-> 拆开第4层盒子...
递推-> 拆开第5层盒子...
基线-> 找到宝石!
(递推结束-> 开始回归)
回归-> 第4层收到结果
回归-> 第3层收到结果
回归-> 第2层收到结果
回归-> 第1层收到结果
返回结果-> 函数执行完毕
说明 :
- 递推阶段:从第1层逐层调用到第5层(每一层等待内层返回结果)。
- 回归阶段:第5层返回宝石,第4层接收后返回给第3层,直到第1层将结果输出。
1.5 递归的常见应用场景
- 适合递归的核心条件:问题能分解为更小的同类子问题。
数学问题:
阶乘、斐波那契数列(第n项等于前两项之和)。
简单重复任务:
比如计算1到n的和
(1+2+...+n = n + (1+2+...+n-1)
)。
层级结构:
比如遍历文件夹(文件夹里有子文件夹,子文件夹里有文件,递归处理每个子文件夹)。
1.6 使用注意事项
- 基线条件必须存在:
缺少基线条件会导致无限递归,最终引发RecursionError
(Python默认递归深度限制约1000层)。 - 问题规模必须缩小:
每次调用自己,参数必须更接近基线(停止)条件(比如基线条件是n=0
, 那么n
应该每次减1,而不是加1)。 - 递归深度限制:
深度过深会触发递归深度限制错误,简单问题建议优先用循环(如阶乘、求和)。 - 性能问题:
递归代码简洁但存在函数调用开销,可能比循环效率低(如斐波那契数列递归实现存在大量重复计算)。
二、说明示例
场景1. 计算阶乘
def factorial(n):
if n == 0: # 基线条件:0的阶乘为1
return 1
else:
# 递归条件:n的阶乘 = n × (n-1)的阶乘
return n * factorial(n - 1)
print(factorial(5)) # 输出:120(5×4×3×2×1)
场景2. 斐波那契数列
def fibonacci(n):
if n <= 1: # 基线条件:F(0)=0,F(1)=1
return n
else:
# 递归条件:F(n) = F(n-1) + F(n-2)(n > 1时)
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(5)) # 输出:5(F(5) = F(4)+F(3)=3+2=5)
场景3. 遍历目录结构
import os
def list_files(path):
"""
递归遍历指定路径下的所有文件(不包含目录本身),并打印文件路径
参数:
path: 待遍历的目录路径(字符串)
递归逻辑:
1. 基线条件:遇到文件时直接打印路径
2. 递归条件:遇到子目录时,递归调用自身处理该子目录
"""
# 遍历当前路径下的所有条目(文件和子目录)
for entry in os.listdir(path):
# 拼接完整路径(避免路径解析错误)
full_path = os.path.join(path, entry)
# 检查是否为目录(递归条件:需要进一步处理的子问题)
if os.path.isdir(full_path):
print(f"📁 进入子目录: {full_path}") # 打印完整路径
list_files(full_path) # 递归处理子目录(问题规模缩小为子目录)
# 基线条件:遇到文件时直接处理(不再继续分解问题)
else:
print(f"📄 文件路径: {full_path}") # 打印文件路径(递归终止点)
三、知识点总结
- 递归定义:
函数自己调用自己,需包含基线条件(终止递归)和递归条件(缩小问题规模)。 - 使用原因:
用简洁代码解决自相似或分治问题(如树结构、数学递推)。 - 使用方法:
先定基线条件(如n=0
),再通过参数变化(如n-1
)逐步靠近基线。 - 应用场景:
问题能分解为更小同类子问题(如阶乘、斐波那契、目录遍历)。 - 注意事项:
必须有基线条件,参数需逐次靠近基线,避免深度超限(Python默认≈1000层),循环可能更高效。
四、扩展知识
4.1 递归 VS 递归函数
在 Python 中,递归和递归函数是紧密相连的概念,不过侧重点有所不同。下面咱们来看一下它们的具体差异:
-
递归
递归是一种编程思想,其核心是在解决问题时,让问题的规模逐步缩小,最终归结到一个最基本的情况。
它主要包含以下关键点:
核心思路:把一个复杂的大问题分解成与原问题结构相同,但规模更小的子问题。
持续进行这样的分解,直到子问题简单到无需再分解,也就是达到基线条件。
应用场景:递归思想适用于具有递归结构的问题,像阶乘计算、斐波那契数列问题等。
实现方式:在 Python 中,递归这种思想通常借助递归函数来实现。 -
递归函数
递归函数是实现递归思想的具体手段,它是一类特殊的函数,其特点如下:
函数定义:在函数的定义中,函数会直接或间接地调用自身。
关键组成:
1. 基线条件:这是递归终止的条件,若不设定,函数会陷入无限递归。
2. 递归条件:在满足一定条件时,函数会调用自身来解决规模更小的子问题。 -
两个概念总结
递归 递归函数 它是一种解决问题的思想和策略 它是实现递归思想的具体编程工具 强调的是问题分解和基线条件 强调函数调用自身的实现方式 是一种抽象的概念 是具体的代码实现 -
递归函数执行流程图
五、知识点考察题
def func(n, r=0):
if n == 0:
return r
else:
r = n + func(n - 1)
print(func(2))
运行以上代码会出现以下哪种情况 ( )
- A.
输出 : 0
- B.
输出 : 3
- C.
输出 : None
- D.
报错TypeError
答案:D
解析:
在递归条件(else
分支)中,代码执行 r = n + func(n - 1)
后缺少 return r
语句,导致递归调用的返回值无法向上传递,函数隐式返回 None
。
- 当
n=2
时,递归调用func(1)
,其内部同样因缺少返回语句返回None
。 - 最终执行
r = 2 + None
,由于int
和None
无法相加,触发TypeError
(类型错误)。
关键点:递归函数必须确保每个分支都有显式的返回值,避免因隐式返回 None
导致逻辑错误。