讲解Python中的递归函数


Posted in Python onApril 27, 2015

在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。

举个例子,我们来计算阶乘n! = 1 x 2 x 3 x ... x n,用函数fact(n)表示,可以看出:

fact(n) = n! = 1 x 2 x 3 x ... x (n-1) x n = (n-1)! x n = fact(n-1) x n

所以,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 "<stdin>", line 1, in <module>
 File "<stdin>", line 4, in fact
 ...
 File "<stdin>", 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 * count和count + 1在函数调用前就会被计算,不影响函数调用。

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

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

遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。

有一个针对尾递归优化的decorator,可以参考源码:

http://code.activestate.com/recipes/474088-tail-call-optimization-decorator/

我们后面会讲到如何编写decorator。现在,只需要使用这个@tail_call_optimized,就可以顺利计算出fact(1000):

>>> fact(1000)
402387260077093773543702433923003985719374864210714632543799910429938512398629020592044208486969404800479988610197196058631666872994808558901323829669944590997424504087073759918823627727188732519779505950995276120874975462497043601418278094646496291056393887437886487337119181045825783647849977012476632889835955735432513185323958463075557409114262417474349347553428646576611667797396668820291207379143853719588249808126867838374559731746136085379534524221586593201928090878297308431392844403281231558611036976801357304216168747609675871348312025478589320767169132448426236131412508780208000261683151027341827977704784635868170164365024153691398281264810213092761244896359928705114964975419909342221566832572080821333186116811553615836546984046708975602900950537616475847728421889679646244945160765353408198901385442487984959953319101723355556602139450399736280750137837615307127761926849034352625200015888535147331611702103968175921510907788019393178114194545257223865541461062892187960223838971476088506276862967146674697562911234082439208160153780889893964518263243671616762179168909779911903754031274622289988005195444414282012187361745992642956581746628302955570299024324153181617210465832036786906117260158783520751516284225540265170483304226143974286933061690897968482590125458327168226458066526769958652682272807075781391858178889652208164348344825993266043367660176999612831860788386150279465955131156552036093988180612138558600301435694527224206344631797460594682573103790084024432438465657245014402821885252470935190620929023136493273497565513958720559654228749774011413346962715422845862377387538230483865688976461927383814900140767310446640259899490222221765904339901886018566526485061799702356193897017860040811889729918311021171229845901641921068884387121855646124960798722908519296819372388642614839657382291123125024186649353143970137428531926649875337218940694281434118520158014123344828015051399694290153483077644569099073152433278288269864602789864321139083506217095002597389863554277196742822248757586765752344220207573630569498825087968928162753848863396909959826280956121450994871701244516461260379029309120889086942028510640182154399457156805941872748998094254742173582401063677404595741785160829230135358081840096996372524230560855903700624271243416909004153690105933983835777939410970027753472000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

小结

使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。

针对尾递归优化的语言可以通过尾递归防止栈溢出。尾递归事实上和循环是等价的,没有循环语句的编程语言只能通过尾递归实现循环。

Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。

Python 相关文章推荐
跟老齐学Python之不要红头文件(2)
Sep 28 Python
windows下Python实现将pdf文件转化为png格式图片的方法
Jul 21 Python
[原创]pip和pygal的安装实例教程
Dec 07 Python
Python设计模式之命令模式原理与用法实例分析
Jan 11 Python
详解python运行三种方式
May 13 Python
python如何实现视频转代码视频
Jun 17 Python
Python time库基本使用方法分析
Dec 13 Python
python 检测图片是否有马赛克
Dec 01 Python
python使用scapy模块实现ping扫描的过程详解
Jan 21 Python
详解pandas映射与数据转换
Jan 22 Python
只用50行Python代码爬取网络美女高清图片
Jun 02 Python
详解Python如何批量采集京东商品数据流程
Jan 22 Python
理解Python中函数的参数
Apr 27 #Python
Python中自定义函数的教程
Apr 27 #Python
在Python中使用dict和set方法的教程
Apr 27 #Python
在Python中使用判断语句和循环的教程
Apr 25 #Python
详解Python中列表和元祖的使用方法
Apr 25 #Python
详解Python当中的字符串和编码
Apr 25 #Python
详细解析Python当中的数据类型和变量
Apr 25 #Python
You might like
php导出csv数据在浏览器中输出提供下载或保存到文件的示例
2014/04/24 PHP
php+mysqli使用预处理技术进行数据库查询的方法
2015/01/28 PHP
PHP设计模式之适配器模式代码实例
2015/05/11 PHP
用HTML/JS/PHP方式实现页面延时跳转的简单实例
2016/07/18 PHP
datePicker——日期选择控件(with jquery)
2007/02/20 Javascript
增强的 JavaScript 的 trim 函数的代码
2007/08/13 Javascript
jQuery图片滚动图片的效果(另类实现)
2013/06/02 Javascript
JS二维数组的定义说明
2014/03/03 Javascript
jqueryMobile使用示例分享
2016/01/12 Javascript
jQuery实现的动态文字变化输出效果示例【附演示与demo源码下载】
2017/03/24 jQuery
vue轮播图插件vue-concise-slider的使用
2018/03/13 Javascript
vue实现购物车抛物线小球动画效果的方法详解
2019/02/13 Javascript
在vue中使用setInterval的方法示例
2019/04/16 Javascript
VSCode使用之Vue工程配置eslint
2019/04/30 Javascript
EasyUI 数据表格datagrid列自适应内容宽度的实现
2019/07/18 Javascript
JS中比较两个Object数组是否相等方法实例
2019/11/11 Javascript
[06:36]吞吞映像top1
2014/06/20 DOTA
python 上下文管理器使用方法小结
2017/10/10 Python
python中requests使用代理proxies方法介绍
2017/10/25 Python
python实现微信发送邮件关闭电脑功能
2018/02/22 Python
Python的高阶函数用法实例分析
2019/04/11 Python
Python完成毫秒级抢淘宝大单功能
2019/06/06 Python
Python使用matplotlib 模块scatter方法画散点图示例
2019/09/27 Python
Python列表list常用内建函数实例小结
2019/10/22 Python
Python基于requests库爬取网站信息
2020/03/02 Python
python文件排序的方法总结
2020/09/13 Python
python实现图片转字符画
2021/02/19 Python
HTML5 创建canvas元素示例代码
2014/06/04 HTML / CSS
英国内衣连锁店:Boux Avenue
2018/01/24 全球购物
iPad和Surface Pro蓝牙键盘:Brydge
2018/11/10 全球购物
阿玛尼美妆俄罗斯官网:Giorgio Armani Beauty RU
2020/07/19 全球购物
初二政治教学反思
2014/01/12 职场文书
承办会议欢迎词
2014/01/17 职场文书
护士节慰问信
2015/02/15 职场文书
离婚协议书范本(2016最新版)
2016/03/18 职场文书
MySQL系列之十五 MySQL常用配置和性能压力测试
2021/07/02 MySQL