Python 递归函数终极指南:从入门到精通

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)
  1. 当我们卖出股票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
  1. 当我们卖出股票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
# ...

小白:(献上键盘)原来递归有这么多门道!

专家:(扶起小白)记住:递归是把双刃剑,用对场景是关键!

✅ 递归使用黄金法则:

  1. 先写基准条件
  2. 问题规模必须递减
  3. 警惕重复计算
  4. Python 默认递归深度不超过 1000

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

科雷learning

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值