pengkunstone 2020-02-01
函数中调用其他函数是解决实际问题中常用方法,递归函数便是函数在运行过程中调用自身的函数。它像是循环的另一种表达方式,不过相较于写循环,递归函数封装性较好、可读性较强。在解决一些循环问题时,使用递归函数往往更简洁有效。以往提到递归函数时,理解仅仅到它比循环更简洁。今天读了博主程序员的人生A的博客后,学习到递归函数的调用机制以及通过尾递归优化解决栈溢出的问题,特留随笔,作温习记录用。
首先递归函数的调用是通过栈(stack)这一种数据结构实现,每多一次函数调用/返回,栈就增加/减少一层栈帧。既然提到它,不妨大致复习一下堆栈的相关。提栈就不得不提它的两个操作及一个性质:入栈(PUSH)和出栈(POP),他们按照先入后出、后入先出的顺序操作。进栈出栈就像往一个盒子里取放东西,先放的被后放的压在下面,要想取到下面的,自然要先拿出在上面的。栈之所以于程序有着至关重要的作用,是因为栈保存了一个程序调用时所需的维护信息,称为堆栈帧或活动记录。它一般包含几部分:1、函数的返回地址和参数;2、临时变量:包括函数的非静态局部变量以及编译器自动生成的其他临时变量。堆栈的实质是一种运算受限于一端的线性表。那既然是线性表,大小自然是有限的。因此,当递归函数调用次数过大(即参数过大时),便会出现“栈溢出”的现象。
解决“栈溢出”的方法则是“尾递归优化”。尾递归是指在函数返回的时候,调用自身且return语句中不能包含表达式。这样编译器就可以把尾递归做优化,使递归本身无论被调用多少次,都只占用一个栈帧,这样便不会出现栈溢出的情况。
用最简单的阶乘函数举例。
使用循环计算1*2*3*...*n:
n = int(input("输出一个数:"))if n == 0: sum = 0else: sum = 1for i in range(1,n+1): sum *= iprint("它的阶乘为:",sum)使用递归函数计算:
def recursion(n): if n==1: return 1 return n * recursion(n - 1)a = int(input("输入一个数:"))print("它的阶乘为:", recursion(a))但当a过大时,编译器便会因栈溢出而报错,这是就需要用到尾递归优化。尾递归优化:
def recursion(n): return fact_iter(n, 1)def fact_iter(num, product): if num == 1: return product return fact_iter(num - 1, num * product)优化后,函数仅返回递归函数本身,这样栈帧中数据每一次使用后更新,栈不再增长,无论调用多少次都不会导致栈溢出。遗憾的是python解释器并没有针对尾递归做优化,因此用python时即使进行了尾递归优化,还是会导致栈溢出。