python基础学习-递归函数
函数执行流程
1 | # http://pythontutor.com/visualize.html#mode=edit |
- 全局帧中生成foo1、foo2、foo3、main函数对象
- main函数调用
- main中查找内建函数print压栈,将常量字符串压栈,调用函数,弹出栈顶
- main中全局查找函数foo1压栈,将常量100、101压栈,调用函数foo1,创建栈帧。print函数压栈,字符串和变量b、b1压栈,调用函数,弹出栈顶,返回值
- main中全局查找foo2函数压栈,将常量200压栈,调用foo2,创建栈帧。foo3函数压栈,变量c引用压栈,调用foo3,创建栈帧。foo3完成print函数调用后返回。foo2恢复调用,执行print后,返回值。main中foo2调用结束弹出栈顶。main继续执行print函数调用,弹出栈顶。main函数返回。
- 内存中分栈和堆,对函数来讲,它有一个栈,栈就是落盘子,只能从上面拿。栈是一个内存区域,它一直不停在被使用。它是一个先进后出,后进先出的。谁在压栈,谁就在使用内存,内存中的其他数据会先保存起来,保存起来以后给它压下去,之后再把print()压在它上面。这里指PPT中的代码def main(): print(“main called”)一段。这样当前就是print环境了,然后将字符常量也压到栈里。调用函数就是print()真的要执行了,它会把print()栈内的所有数据依次拿出来执行,执行完以后,栈上的数据就依次拿完了,它刚压进去的数据就消耗完了,这时print就可以弹出了,print一弹出,之前有为main保存的数据,依次再加载起来。main函数就可以继续执行了。之后在main中查找foo1并压栈,将常量100、101压栈。要依次用掉函数内的数据。在栈内要创建一段,这一段叫帧,因为在栈上,所以叫栈帧。函数的执行过程就是压栈与弹出的过程
1 | # 字节码不是重点,当对语言掌握的很好了,写程序没问题了,再来了解字节码 |
递归Recursion
函数直接或者间接调用自身就是递归
递归需要有边界条件、递归前进段、递归返回段
递归一定要有边界条件
当边界条件不满足的时候,递归前进
当边界条件满足的时候,递归返回
斐波那契数列 Fibonacci number: 1,1,2,3,5,8,13,21,34,55,89,144, …
如果设F(n) 为该数列的第n项,那么这句话可以写成如下形式:F(n)=F(n-1)+F(n-2)
F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)
1 | pre = 0 |
- 递归要求
- 递归一定要有退出条件,递归调用一定要执行到这个退出条件。没有退出条件的递归调用 ,就是无限调用
- 递归调用 的深度不宜过深
- Python对递归调用的深度做了限制,以保护解释器
- 超过递归深度限制,抛出RecursionError: maxinum recursion depth exceeded 超出最大深度
- sys.getrecursionlimit()
1 | # 用pycharm测试 |
递归的性能
1 | # for 循环 |
- 循环稍微复杂一些,但是只要不是死循环,可以多次迭代直至算出结果
- fib函数代码极简易懂,但是只能获取到最外层的函数调用,内部递归结果都是中间结果。而且给定一个n都要进行近2n次递归,深度越深,效率越低。为了获取斐波那契数列需要外面再套一个n次的循环,效率就更低了
- 递归还有深度限制,如果递归复杂,函数反复压栈,栈内存很快就溢出了
- 思考:这个极简的递归代码能否提高性能?
1 | # 斐波寻契数列的改进 |
- 间接递归
1 | def foo1(): |
递归总结
- 递归是一种很自然的表达,符合逻辑思维
- 递归相对运行效率低,每一次调用函数都要开辟栈帧
- 递归有深度限制,如果递归层次太深,函数反复压栈,栈内存很快就溢出了
- 如果是有限次数的递归,可以使用递归调用,或者使用循环代替,循环代码稍微复杂一些,但是只要不是死循环,可以多次迭代直至算出结果
- 绝大多数递归,都可以使用循环实现
- 即使递归代码很简洁,但是能不用则不用递归
- 个人感觉,递归最重要的有三点。一是定义时,函数的参数就是变量,它一直在变化,所以可以向下执行。二是找到公式,也就是函数要完成的功能,如何不断的用一个公式递归自己,这可以通过要实现的功能来发现其中的规律。三是返回,实际就是告诉函数到什么地方就可以一级一级地向上返回值了,这个也可以从要实现的功能中找到,看功能到什么时候就可以结束。
python基础学习-递归函数