Python递归函数 —— 优雅的写代码(不过运行效率确实低)


这里是一段防爬虫文本,请读者忽略。
本文原创首发于CSDN,作者IDYS
博客首页:https://blog.csdn.net/weixin_41633902/
本文链接:https://blog.csdn.net/weixin_41633902/article/details/107775824
未经授权,禁止转载!恶意转载,后果自负!尊重原创,远离剽窃!


写在开头的话

  • 请记住:实践是掌握知识的最快方法
  • 如果你只是怀着看看的态度去快速浏览文章,而不去认认真真的把文章里面讲的任何一个知识点去实践一遍,那么你永远也掌握不了它
  • 生命不息,折腾不止!

Python 递归函数

00. 递归

  • 函数直接或者间接调用自身就是递归
  • 递归需要有边界条件、递归前进段、递归返回段
  • 递归一定要有边界条件
  • 当边界条件不满足的时候,递归前进
  • 当边界条件满足的时候,递归返回

  • 递归要求

    • 递归一定要有退出条件,递归调用一定要执行到这个退出条件。没有退出条件的递归调用,就是无限调用
  • 递归调用的深度不宜过深

    • Python对递归调用的深度做了限制,以保护解释器
    • 超过递归深度限制,抛出RecursionError: maximum recursion depth exceeded in comparison异常
    • sys.getrecursionlimit():查看系统调用深度

01. 演示:用递归调用求斐波那契数列

  • 代码演示
import sys

def fn(n):
    if n == 1 or n == 2:   # 边界条件
        return 1   # 返回段
    else:
        return fn(n-1) + fn(n-2)   # 前进段


if __name__ == "__main__":
    num = fn(30)
    print(num)
    print(sys.getrecursionlimit())   # 查看递归调用最大深度
  • 运行结果
832040
1000

02. 递归调用的性能

  • 循环稍微复杂一些,但是只要不是死循环,可以多次迭代直至算出结果
  • fib函数代码极简易懂,但是只能获取到最外层的函数调用,内部递归结果都是中间结果,而且给定一个n都要进行2n次递归,深度越深,效率越低。为了获取斐波那契数列,需要外面再套一个n次循环,效率就更低了
  • 递归还有深度限制,如果递归复杂,函数反复压栈,栈内存很快就溢出了

  1. 递归调用性能:代码演示
import datetime


def fn(n):
    if n == 1 or n == 2:   # 边界条件
        return 1   # 返回段
    else:
        return fn(n-1) + fn(n-2)   # 前进段


if __name__ == "__main__":
    start = datetime.datetime.now()
    print(fn(30))
    print((datetime.datetime.now() - start).total_seconds())
  • 运行结果
832040
0.353054

  1. 普通的循环函数:求斐波那契数列演示
import datetime


def fn(n):
    i, j = 1, 1
    for k in range(n-2):
        i, j = i+j, i
    return i


if __name__ == "__main__":
    start = datetime.datetime.now()
    print(fn(30))
    print((datetime.datetime.now() - start).total_seconds())
  • 运行结果
832040
0.0

发现递归调用花费的时间远远大于普通函数所花费的时间


  1. 改进版斐波那契数列递归调用
  • 代码演示
import datetime

def fn(n,i,j):
    if n == 2:
        return i
    else:
        i, j = i+j, i
        return fn(n-1, i, j)


if __name__ == "__main__":
    start = datetime.datetime.now()
    print(fn(999, 1, 1))
    print((datetime.datetime.now() - start).total_seconds())
  • 运行结果
26863810024485359386146727202142923967616609318986952340123175997617981700247881689338369654483356564191827856161443356312976673642210350324634850410377680367334151172899169723197082763985615764450078474174626
0.002992

这次发现就算其递归调用999次,其运行时间也仅仅只有0.002992秒


03. 递归总结

  • 递归是一种很自然的表达,符合逻辑思维
  • 递归相对运行效率低,每一次调用函数都要开辟栈帧
  • 递归有深度限制,如果递归层次太深,函数反复压栈,栈内存很快就溢出了
  • 如果是有限次数的递归,可以使用递归调用,或者使用循环代替,循环代码稍微复杂一些,但是只要不是死循环,可以多次迭代直至算出结果
  • 绝大多数递归,都可以使用循环实现
  • 即使递归代码很整洁,但是能不用则不用递归

04. 例题

4.1 求n的阶乘

  • 代码
def fn(n):
    if n == 1:
        return 1
    else:
        return fn(n -1) * n


if __name__ == "__main__":
    print(fn(6))

  • 运行结果
720

4.2 将一个数逆序放入列表中,例如1234 -> list[4,3,2,1]

  • 方法一
def fn(n, list1):
    if len(str(n)) == 1:
        list1.append(str(n))
        return
    else:
        str1 = str(n)
        list1.append(str1[-1])
        str1 = str1[:-1]
        fn(int(str1), list1)


if __name__ == "__main__":
    list1 = []
    fn(5612, list1)
    print(list1)
  • 运行结果
['2', '1', '6', '5']

  • 方法二
def fn(num, list1):
    if len(str(num)) == 1:
        list1.append(num)
        return list1
    else:
        list1.append(num % 10)
        num = num // 10
        return fn(num, list1)

if __name__ == "__main__":
    list1 = []
    fn(345689156, list1)
    print(list1)
  • 运行结果
[6, 5, 1, 9, 8, 6, 5, 4, 3]

4.3 猴子吃桃问题

  • 一只猴子第一天摘下若干个桃子,当即吃了一半,觉得吃得不爽,又多吃了一个桃子,依次每天都是吃了一半多一个,最后到第十天只剩下1个桃子了。请问,第一天摘下多少桃子

  • 解决方法
def fn(n):
    if n == 1:
        return 1
    else:
        return 2*(fn(n - 1) + 1)


if __name__ == "__main__":
    print(fn(10))
    
  • 运行结果
1534

写在最后的话:

  • 无论每个知识点的难易程度如何,我都会尽力将它描绘得足够细致
  • 欢迎关注我的CSDN博客,IDYS’BLOG
  • 持续更新内容
    linux基础 | 数据通信(路由交换,WLAN) | Python基础 | 云计算
  • 如果你有什么疑问,或者是难题。欢迎评论或者私信我。你若留言,我必回复!
  • 虽然我现在还很渺小,但我会做好每一篇内容。谢谢关注!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值