Python-递归函数
1、函数执行流程
1.1、函数与栈关系
C语言中,函数的活动和栈有关。
栈是后进先出的数据结构。栈是由底端向顶端生长,栈顶加入数据称为压栈、入栈,栈顶弹出数据称为出栈。
1.2、流程示例
1.2.1、代码
def add(x, y): r = x + y print(r) return r def main(): a = 1 b = add(a, 2) return b main()
1.2.2、流程分析
main调用,在栈顶创建栈帧 a = 1,在main栈帧中增加a,堆里增加1,a指向这个1 b = add(a, 2),等式右边先执行,add函数调用 add调用,在栈顶创建栈帧,压在main栈帧上面 add栈帧中增加2个变量,x变量指向1,y指向堆中新的对象2 在堆中保存计算结果3,并在add栈帧中增加r指向3 print函数创建栈帧,实参r被压入print的栈帧中 print函数执行完毕,函数返回,移除栈帧 add函数返回,移除栈帧 main栈帧中增加b指向add函数的返回值对象 main函数返回,移除栈帧
1.2.3、问题点
如果再次调用main函数,和刚才的main函数调用,有什么关系?
每一次函数调用都会创建一个独立的栈帧入栈。
因此,可以得到这样一句不准确的话:哪怕是同一个函数两次调用,每一次调用都是独立的,这两次调用没什么关系。
2、递归
2.1、简介
函数直接或者间接调用自身就是 递归
递归需要有边界条件、递归前进段、递归返回段
递归一定要有边界条件
当边界条件不满足的时候,递归前进
当边界条件满足的时候,递归返回
2.2、斐波那契数列递归示例
斐波那契数列Fibonacci number:1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ... 如果设F(n)为该数列的第n项(n∈N*),那么这句话可以写成如下形式::F(n)=F(n-1)+F(n-2) 有F(0)=0,F(1)=1, F(n)=F(n-1)+F(n-2)
2.2.1、没有递归示例
def fib_v1(n): # n>=3 a = b = 1 for i in range(n-2): a, b = b, a + b return b print(fib_v1(101)) print(fib_v1(35))
2.2.2、使用递归实现,需要使用上面的递推公式
# 递归 def fib_v2(n): if n < 3: return 1 return fib_v2(n-1) + fib_v2(n-2) # 递归 def fib_v2(n): return 1 if n < 3 else fib_v2(n-1) + fib_v2(n-2) fib_v2(35) # 9227465
2.2.3、是执行fib(35)就已经非常慢了,为什么?
递归实现很美,但是执行fib(35)就已经非常慢了,为什么? 以fib(5)为例。看了下图后,fib(6)是怎样计算的呢? 这个函数进行了大量的重复计算,所以慢。
2.3、递归要求
递归一定要有退出条件,递归调用一定要执行到这个退出条件。没有退出条件的递归调用,就是无限调用
递归调用的深度不宜过深
Python对递归调用的深度做了限制,以保护解释器
超过递归深度限制,抛出RecursionError: maxinum recursion depth exceeded 超出最大深度
sys.getrecursionlimit()
2.4、递归效率
2.4.1、改进一下fib_v2函数
# 递归 def fib_v3(n, a=1, b=1): if n < 3: return b a, b = b, a + b #print(n, a, b) return fib_v3(n-1, a, b) # 函数调用次数就成了循环次数,将上次的计算结果代入下次函数调用 fib_v3(101) # fib_v3(35)
2.4.2、小结
经过比较,发现fib_v3性能不错,和fib_v1循环版接近。但是递归函数有深度限制,函数调用开销较大。
2.5、间接递归
2.5.1、代码
def foo1(): foo2() def foo2(): foo1() foo1()
2.5.2、小结
间接递归调用,是函数通过别的函数调用了自己,这一样是递归。
只要是递归调用,不管是直接还是间接,都要注意边界返回问题。但是间接递归调用有时候是非常不明显,代码调用复杂时,很难发现出现了递归调用,这是非常危险的。
所有,使用良好的代码规范来避免这种递归的发生。
2.6、总结
递归是一种很自然的表达,符合逻辑思维
递归相对运行效率低,每一次调用函数都要开辟栈帧
递归有深度限制,如果递归层次太深,函数连续压栈,栈内存很快就溢出了
如果是有限次数的递归,可以使用递归调用,或者使用循环代替,循环代码稍微复杂一些,但是只
要不是死循环,可以多次迭代直至算出结果
绝大多数递归,都可以使用循环实现
即使递归代码很简洁,但是能不用则不用递归
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了