尾递归

尾递归

如果要说尾递归的话,那么首先应该先说一下递归函数。递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但是循环的逻辑不如递归清晰易理解。

在这里我们假定读者已经了解递归函数的基本概念,便不作过多赘述。

使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减少一层栈帧。由于栈的大小不是无限的,可以使用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 - 1num * product在函数调用前就会被计算,所以并不会影响函数的调用。

总结

尾递归调用时,如果做了尾递归的优化,那么栈就不会因为递归函数不断调用自己导致栈溢出。

遗憾的是,Python没有对尾递归进行优化,大部分编程语言也没有对尾递归做优化。所以,上面的修改依然会导致栈溢出。

最好的方式还是把递归函数写成循环模式!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章