Py学习  »  Python

尾递归优化原理与Python实现(以Fibonacci数列和小明爬楼梯问题为例)

Python小屋 • 5 年前 • 449 次点击  

首先祝全体屋友中秋节快乐!

众所周知,在函数递归调用时,要保存函数调用的位置以便使得被调函数结束后能够返回正确的位置,这个信息保存在线程栈中。由于栈的空间有限,所以如果函数递归调用深度超过一定限制,会导致栈崩溃。并且,如果需要保存大量返回位置并且逐级返回的话,也会耗费大量的时间,使得代码运行速度非常慢。

所谓尾递归,是指函数调用出现在函数的尾部最后一条语句,并且函数返回值不作为其他表达式的一部分。如果编译器支持尾递归优化的话,这种情况下将不会保存返回位置,从而避免栈崩溃。因此,通过改写递归函数,改用尾递归的话,会大幅度提高运行速度,并且不会栈崩溃。

例如,下面是经典的Fibonacci数列中第n项求解的问题,第一段代码没有使用尾递归,第二段代码使用了尾递归。

上面两段代码的运行速度有天壤之别,如下图所示:

bingo,太棒了,果然速度提高很多。

然而,不要高兴的太早,把代码中的n修改为1200,交换两个函数调用的顺序,重新测试结果如下:

还是栈崩溃。。。。

看来要真正实现尾递归优化,只是改写代码还不够啊,还需要编译器或解释器的支持才行。从上面的情况来看,Python解释器默认并没有支持尾递归优化。

网上有一个使用修饰器修改栈中参数实现尾递归优化的方法,不过代码是Python 2的,我进行了简单修改,变成了Python 3的版本。

为了验证代码的正确性,上面的代码同时给出了迭代法实现,并且把问题规模增大到2300,运行结果如下,可见迭代还是无敌的啊:


再例如,小明爬楼梯的问题,问题描述可以参考以前的推文Python两种方法求解登楼梯问题(京东2016笔试题),如果改为尾递归的话,继续使用上面代码中的尾递归修饰器,代码如下:

运行结果如下:

上面的实现看起来已经很完美了,但又是类定义,又是修饰器,还要操作栈帧,好像很复杂的样子,有没有更简单的实现呢?

答案是确定的,以小明爬楼梯的问题为例:使用嵌套函数定义+生成器函数实现尾递归优化的代码如下:

这样真的可以吗?我们让事实来说话,修改测试代码:

运行结果如下:


思考题:

1)文中的尾递归修饰器作用到普通递归函数上,会有作用吗?

2)所有递归函数都可以改成尾递归吗?试试组合数求解的递归函数,普通递归代码见针对递归函数的优化与Python修饰器实现,第一位成功改写为尾递归优化版本代码(或能够证明无法改写)并留言的屋友将获签名赠书一本,可以从董付国老师Python系列教材中任选一本:

  • 《Python程序设计(第2版)》清华大学出版社(2018年8月第9次印刷)

  • 《Python可以这样学》清华大学出版社(2018年7月第6次印刷)(本书已在台湾发行繁体版)

  • 《Python程序设计基础(第2版)清华大学出版社(2018年9月第5次印刷)

  • 《中学生可以这样学Python》清华大学出版社(2018年5月第2次印刷)

  • 《Python程序设计开发宝典》清华大学出版社(2018年2月第3次印刷)

  • 《玩转Python轻松过二级》清华大学出版社(2018年7月第3次印刷)

  • 《Python程序设计基础与应用》机械工业出版社(2018年9月第1次印刷)


董老师127课免费视频地址: https://pan.baidu.com/s/1jJeAs8Q 密码: px59

下期预告:Python批量下载电子邮箱附件+Excel文件多WorkSheets批量合并


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/CMwdtJyojI
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/24327
 
449 次点击