博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
尾递归优化
阅读量:6122 次
发布时间:2019-06-21

本文共 1946 字,大约阅读时间需要 6 分钟。

 

来自

 

fact(n)可以表示为n x fact(n-1),只有n=1时需要特殊处理。

于是,fact(n)用递归的方式写出来就是:

def fact(n):    if n==1: return 1 return n * fact(n - 1)

上面就是一个递归函数。可以试试:

>>> fact(1)1>>> fact(5) 120 >>> fact(100) 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000L

如果我们计算fact(5),可以根据函数定义看到计算过程如下:

===> fact(5)===> 5 * fact(4)===> 5 * (4 * fact(3)) ===> 5 * (4 * (3 * fact(2))) ===> 5 * (4 * (3 * (2 * fact(1)))) ===> 5 * (4 * (3 * (2 * 1))) ===> 5 * (4 * (3 * 2)) ===> 5 * (4 * 6) ===> 5 * 24 ===> 120

递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。

使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。可以试试fact(1000)

>>> fact(1000)Traceback (most recent call last):  File "
", line 1, in
File "
", line 4, in fact ... File "
", line 4, in fact RuntimeError: maximum recursion depth exceeded

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

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

上面的fact(n)函数由于return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中:

def fact(n):    return fact_iter(1, 1, n) def fact_iter(product, count, max): if count > max: return product return fact_iter(product * count, count + 1, max)

可以看到,return fact_iter(product * count, count + 1, max)仅返回递归函数本身,product * countcount + 1在函数调用前就会被计算,不影响函数调用。

通过增加参数,使得递归函数只需要返回其本身,使用第二个参数来计算递归函数的的递归层数,使用第三个参数来控制递归层数,最终结果减少了(n-1)个栈的空间开销。

经典的递归函数是通过返回值中的(n-1)以及函数体中的n>0来计算以及控制递归的层数。

fact(5)对应的fact_iter(1, 1, 5)的调用如下:

===> fact_iter(1, 1, 5)===> fact_iter(1, 2, 5) ===> fact_iter(2, 3, 5) ===> fact_iter(6, 4, 5) ===> fact_iter(24, 5, 5) ===> fact_iter(120, 6, 5) ===> 120

尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。

转载于:https://www.cnblogs.com/lswit/p/4456074.html

你可能感兴趣的文章
Scroll Depth – 衡量页面滚动的 Google 分析插件
查看>>
Windows 8.1 应用再出发 - 视图状态的更新
查看>>
自己制作交叉编译工具链
查看>>
Qt Style Sheet实践(四):行文本编辑框QLineEdit及自动补全
查看>>
[物理学与PDEs]第3章习题1 只有一个非零分量的磁场
查看>>
深入浅出NodeJS——数据通信,NET模块运行机制
查看>>
onInterceptTouchEvent和onTouchEvent调用时序
查看>>
android防止内存溢出浅析
查看>>
4.3.3版本之引擎bug
查看>>
SQL Server表分区详解
查看>>
使用FMDB最新v2.3版本教程
查看>>
SSIS从理论到实战,再到应用(3)----SSIS包的变量,约束,常用容器
查看>>
STM32启动过程--启动文件--分析
查看>>
垂死挣扎还是涅槃重生 -- Delphi XE5 公布会归来感想
查看>>
淘宝的几个架构图
查看>>
Android扩展 - 拍照篇(Camera)
查看>>
JAVA数组的定义及用法
查看>>
充分利用HTML标签元素 – 简单的xtyle前端框架
查看>>
设计模式(十一):FACADE外观模式 -- 结构型模式
查看>>
iOS xcodebuile 自动编译打包ipa
查看>>