尾递归

刺骨的言语ヽ痛彻心扉 2022-08-20 04:13 309阅读 0赞

尾递归

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

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

使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减少一层栈帧。由于栈的大小不是无限的,可以使用ulimit -s查看和设置

示例函数

计算n的阶乘n!

  1. def fact(n):
  2. if n==1:
  3. return 1
  4. return n * fact(n-1)

可以试试fact(1000)看看结果,如果没有报错那么就试试fact(10000)

  1. >>> fact(1000)
  2. Traceback (most recent call last):
  3. File "<stdin>", line 1, in <module>
  4. File "<stdin>", line 4, in fact
  5. ...
  6. File "<stdin>", line 4, in fact
  7. RuntimeError: maximum recursion depth exceeded in comparison

那么该怎么办呢?

解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。

尾递归,是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使得递归本身无论调用自己多少次,都只占用一个栈帧,避免栈溢出的情况

那么我们继续看上面的fact(n)函数,由于return n * fact(n-1)并不是只有函数自己,所以这并不是尾递归。若想要改成尾递归方式,就需要改变一下代码

  1. def fact(n):
  2. return fact_iter(n, 1)
  3. def fact_iter(num, product):
  4. if num == 1:
  5. return product
  6. return fact_iter(num - 1, num * product)

fact_iter(num, product)仅仅返回函数本身,而num - 1num * product在函数调用前就会被计算,所以并不会影响函数的调用。

总结

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

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

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

发表评论

表情:
评论列表 (有 0 条评论,309人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java

    一、序言 尾调用 维基百科 > 在计算机学里,尾调用是指一个函数里的最后一个动作是返回一个函数的调用结果的情形,即最后一步新调用的返回值直接被当前函数的返回结果。

    相关

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

    相关 python

    递归函数可以方便的处理一些事物,但普通的递归是栈的堆积,如果堆积的过多就占用过多的内存资源,形象的一些递归就是就像是塔一样,从下至上层层叠加,直到,到达python的限制抛出异

    相关

    什么是尾递归 什么是尾递归呢?(tail recursion), 顾名思议,就是一种“不一样的”递归,说到它的不一样,就得先说说一般的递归。对于一般的递归,比如下面

    相关

    尾递归就是说一个递归函数,在return语句中调用了这个递归函数本身,如图所示。从理论上来说,尾递归都可以用非递归的方法实现。 ![这里写图片描述][70] [70]

    相关

    1、递归 简单的来说递归就是一个函数直接或间接地调用自身,是为直接或间接递归。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满