尾递归
尾递归
如果要说尾递归的话,那么首先应该先说一下递归函数。递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但是循环的逻辑不如递归清晰易理解。
在这里我们假定读者已经了解递归函数的基本概念,便不作过多赘述。
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减少一层栈帧。由于栈的大小不是无限的,可以使用ulimit -s查看和设置
。
示例函数
计算n的阶乘n!
def fact(n):
if n==1:
return 1
return n * fact(n-1)
可以试试fact(1000)看看结果,如果没有报错那么就试试fact(10000)
>>> fact(1000)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in fact
...
File "<stdin>", line 4, in fact
RuntimeError: maximum recursion depth exceeded in comparison
那么该怎么办呢?
解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
尾递归,是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使得递归本身无论调用自己多少次,都只占用一个栈帧,避免栈溢出的情况
那么我们继续看上面的fact(n)
函数,由于return n * fact(n-1)
并不是只有函数自己,所以这并不是尾递归。若想要改成尾递归方式,就需要改变一下代码
def fact(n):
return fact_iter(n, 1)
def fact_iter(num, product):
if num == 1:
return product
return fact_iter(num - 1, num * product)
fact_iter(num, product)
仅仅返回函数本身,而num - 1
和num * product
在函数调用前就会被计算,所以并不会影响函数的调用。
总结
尾递归调用时,如果做了尾递归的优化,那么栈就不会因为递归函数不断调用自己导致栈溢出。
遗憾的是,Python没有对尾递归进行优化,大部分编程语言也没有对尾递归做优化。所以,上面的修改依然会导致栈溢出。
最好的方式还是把递归函数写成循环模式!
还没有评论,来说两句吧...