Python 递归函数终极指南:从入门到精通
对话实录
小白:(抓狂)我的阶乘函数怎么报错RecursionError了?
专家:(掏出镜子)递归要有终止条件,否则就像两面镜子对照无限循环!
递归基础三连击
1. 递归三要素
递归函数包含三个关键要素:递归调用、递归终止条件(基准条件),以及问题规模的递减。以计算阶乘的函数为例:
阶乘是基斯顿·卡曼(Christian Kramp,1760~1826)于 1808 年发明的运算符号,是数学术语。一个正整数的阶乘n!=1×2×3×...×n。阶乘可以递归方式定义:n!=(n-1)!×n。
def factorial(n):
#基准条件:0! = 1
if n == 0:
return 1
# 递归条件:n! = n * (n-1)!
print(f'{n} * factorial({n - 1})')
return n * factorial(n-1)
print(factorial(5)) # → 120
#打印内容如下
5 * factorial(4) #num=5时打印
4 * factorial(3) #num=4时打印
3 * factorial(2) #num=3时打印
2 * factorial(1) #num=2时打印
递归结果:120
专家提醒:递归函数必须设置基准条件(base case),否则会陷入无限递归!
2. 递归调用栈
递归调用过程中,系统会使用调用栈来管理函数调用。每次函数调用,系统都会将当前函数的状态压入栈中,当函数返回时,再从栈中弹出状态。下面是一个无限递归的例子:
def infinite_recursion():
infinite_recursion() # ❌ 无限递归
# 实际运行会抛出RecursionError: maximum recursion depth exceeded
实战案例
案例 1:买卖股票计算赚了多少钱
在不同的行业,在使用递归函数时有不同的应用场景,下面以买股票的例子介绍使用递归计算卖出股票时赚了多少钱。
买卖股票的原则:先买进的股票优先卖出。比如周一到周五我们明天都以不同的价格买进了同一只股票,买进数量和价格放入一个列表中:
[[100,98.5],[200,99.2],[100,97.5],[300,98.1],[200,99.4]]
当下一个交易日我卖出n股时,我们用递归函数计算赚了多少钱。(浮点数计算我们采用decimal 模块解决计算精度问题)
from decimal import Decimal
stock_list= [[100,98.5],[200,99.2],[100,97.5],[300,98.1],[200,99.4]]
def stock_bs(stock_list,count,price,num=0):
"""
:param stock_list: 股票信息列表
:param count: 卖出股票的数量
:param price: 卖出股票的价格
:param num: 从列表的该切片开始计算
:return:
"""
#定义股票信息列表的长度
length = len(stock_list)
if num == length:
return 0
if stock_list[num][0] >= count:
#当卖出的股票数量小于等于某切片的买进股票数量时,不需要递归 直接返回计算结果,否则执行else开始递归
return (Decimal(str(price)) - Decimal(str(stock_list[num][1]))) * count
else:
#递归后 卖出的量要减少
count -= stock_list[num][0]
#打印每一次递归计算公式 num起始为0
print(f'第{num+1}次递归 : 计算公式:({price} - {stock_list[num][1]}) * {stock_list[num][0]}')
#递归后num每次加1
num += 1
#return 重新调用stock_bs函数
return (Decimal(str(price)) - Decimal(str(stock_list[num-1][1]))) * stock_list[num-1][0] \
+ stock_bs(stock_list,count,price,num=num)
-
当我们卖出股票800股(共900股),价格为99.2时,执行如下
print(f'赚的钱: {stock_bs(stock_list,800,99.2)}')
打印结果:
第1次递归 : 计算公式:(99.2 - 98.5) * 100
第2次递归 : 计算公式:(99.2 - 99.2) * 200
第3次递归 : 计算公式:(99.2 - 97.5) * 100
第4次递归 : 计算公式:(99.2 - 98.1) * 300
赚的钱: 550.0
-
当我们卖出股票100股(共900股),价格为99.2时,执行如下
print(f'赚的钱: {stock_bs(stock_list,100,99.2)}')
打印结果: 本次执行并未走入递归过程
赚的钱: 70.0
案例2: 文件树遍历
在文件系统操作中,递归可用于遍历整个目录树:
import os
def list_files(path):
for item in os.listdir(path):
full_path = os.path.join(path, item)
if os.path.isdir(full_path):
list_files(full_path) # 递归遍历子目录
else:
print(full_path)
list_files('/Users/desktop')
案例 3:组合生成器
计算组合是递归的经典应用场景,下面的代码可以生成指定长度的组合:
def combinations(arr, k):
if k == 0:
return [[]]
if not arr:
return []
head = arr[0]
return [
[head] + rest
for rest in combinations(arr[1:], k-1)
] + combinations(arr[1:], k) # 递归拆分
print(combinations([1,2,3], 2)) # → [[1,2], [1,3], [2,3]]
案例 4:JSON 解析
在处理复杂的 JSON 数据时,递归可帮助我们轻松地进行深度搜索:
def deep_search(data, target_key):
if isinstance(data, dict):
for key, value in data.items():
if key == target_key:
yield value
yield from deep_search(value, target_key)
elif isinstance(data, list):
for item in data:
yield from deep_search(item, target_key)
血泪陷阱
栈溢出灾难
以斐波那契数列计算为例,常规的递归实现会导致指数级的时间复杂度,极易引发栈溢出:
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2) # ❌ O(2^n)复杂度
# 正确做法:使用记忆化装饰器
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
return n if n <=1 else fib(n-1)+fib(n-2)
副作用陷阱
在递归函数中修改全局变量会导致难以调试的问题:
count = 0
def bad_recursion():
global count # ❌ 在递归中修改全局变量
count +=1
if count < 10:
bad_recursion()
专家工具箱
1. 记忆化优化
使用lru_cache装饰器可以显著提升递归函数的性能:
from functools import lru_cache
@lru_cache(maxsize=1000)
def factorial(n):
try:
if not isinstance(n, int) or n < 0:
raise ValueError("输入必须为非负整数")
return 1 if n == 0 else n * factorial(n - 1)
except ValueError as ve:
print(f"值错误: {ve}")
return None
2. 尾递归改写
将尾递归转换为迭代形式,可以避免栈溢出问题:
def factorial_iter(n, result=1):
try:
if not isinstance(n, int) or n < 0:
raise ValueError("输入必须为非负整数")
if n == 0:
return result
return factorial_iter(n - 1, result * n) # 改为迭代形式
except ValueError as ve:
print(f"值错误: {ve}")
return None
3. 递归可视化
通过递归可视化,我们可以更好地理解递归的执行过程:
def recursive_tree(depth, branch=1):
try:
if depth < 0:
raise ValueError("深度必须为非负整数")
if depth > 0:
print("--" * (4 - depth) + f"分支{branch}")
recursive_tree(depth - 1, 1)
recursive_tree(depth - 1, 2)
except ValueError as ve:
print(f"值错误: {ve}")
except RecursionError as re:
print(f"递归错误: {re}")
recursive_tree(3)
# 输出:
# --分支1
# ----分支1
# ------分支1
# ------分支2
# ----分支2
# ------分支1
# ------分支2
# --分支2
# ...
小白:(献上键盘)原来递归有这么多门道!
专家:(扶起小白)记住:递归是把双刃剑,用对场景是关键!
✅ 递归使用黄金法则:
- 先写基准条件
- 问题规模必须递减
- 警惕重复计算
- Python 默认递归深度不超过 1000