python 递归函数

1、函数执行流程:

def foo1(a, a1=1):
  print("foo1 called", a, a1)
    
def foo2(b):
  foo3(b)
  print("foo2 called", b)
    
def foo3(c):
  print("foo3 called", c)
    
def main():
  print("main called")
  foo1(100, 101)
  foo2(200)
  print("main ending")
    
main()
    
# 函数执行流程:
1.全局帧中生成 foo1、foo2、foo3、main 函数对象;
2.main 函数调用;
3.main 中查找内建函数 print 压栈,将常量字符串压栈,调用函数,弹出栈顶;
4.main 中全局查找函数 foo1 压栈,将常量 100、101 压栈,调用函数 foo1,创建栈帧。print 函数压栈,字符串和变量 a、a1 压栈,调用函数,弹出栈顶,返回值;
5.main 中全局查找 foo2 函数压栈,将常量 200 压栈,调用 foo2,创建栈帧。foo3 函数压栈,变量 b 引用压栈,调用 foo3,创建栈帧。foo3 完成 print 函数调用后返回。foo2 恢复调用,执行 print 后,返回值。main 中 foo2 调用结束弹出栈顶,main 继续执行 print 函数,弹出栈顶,main 函数返回。

 

def foo(a):
    print('foo')
    
def main():
    print('main start')
    foo([])     # 在内存中即堆中开辟一个列表,压栈地址,弹出
    foo([])     # 在堆中再开辟一个列表,压栈地址,弹出
    print('main ending')
    
main()
    
    
def foo1(a):
    print('foo1')
    
def main():
    print('main start')
    lst = []    # 先在堆中开辟一个列表,压栈
    foo1(lst)   # 引用地址,弹出,列表不消亡
    foo1(lst)   # 再引用地址
    print('main ending')
    
main()

 

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

import sys
print(sys.getrecursionlimit())    # cpython 对递归调用的深度做了限制,限制 1000 下调用

def foo1():
    foo1()
foo1()    # 最大递归深度(函数栈太大)限制,报错

 

  例:斐波那契数列 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)

# 循环实现
a, b = 0, 1
for i in range(35):
    a, b = b, a+b
print(a)
    
# 递归实现
def fib(n):    # 函数调用特别多,一层一层
    if n < 3:  # 定义边界
        return 1
    return fib(n-1) + fib(n-2)
    print(fib(4))

def fib1(n):
    return 1 if n < 3 else fib(n-1) + fib(n-2)
    print(fib(4))
    
# 递归解析:层层解析,循环计算,效率低下
fib(5) = fib(4) + fib(3)
fib(4) = fib(3) + fib(2) fib(3) = fib(2) + fib(1)

 

  递归要求:
    递归一定要有退出条件,递归调用一定要执行到这个退出条件。没有退出条件的递归调用,就是无限调用
    递归调用的深度不宜过深
      Python 对递归调用的深度做了限制,以保护解释器
      超过递归深度限制,抛出 RecursionError: maxinum recursion depth exceeded 超出最大深度
      sys.getrecursionlimit(),查看最大深度

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

  思考:这个极简的递归代码能否提高性能呢?

# 函数调用模拟循环
def fib(n, a=0, b=1):
    a, b = b, a + b
    if n == 1:
        return a
    return fib(n-1, a, b)
print(fib(4))

 

  间接递归,是通过别的函数调用了函数自身

def foo1():
    foo2()
    
def foo2():
    foo1()

  但是,如果构成了循环递归调用是非常危险的,但是往往这种情况在代码复杂的情况下,还是可能发生这种调用。要用代码的规范来避免这种递归调用的发生。

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

 

3、递归练习:

  求 n 的阶乘:

# 循环实现
n = 6
value = 1
for i in range(1, n+1):
    value = (i * value)
print(value)

# 递归实现
def fib(n):
    return 1 if n == 1 else n * fib(n-1)
print(fib(6))

 

posted @ 2020-04-28 17:30  我听过  阅读(496)  评论(0编辑  收藏  举报