关于尾递归优化

众所周知,函数调用例如A调用B,由于B执行结束后需要继续执行A,因此我们得把A在调用B时所处地址或者其他上下文信息进行保存。最常见的就是将地址保存在栈中。

若存在A->B->C,则此时栈内容依次存放函数A、B、C执行的信息。由于一个进程用于保存函数信息的栈容量有限,若类似的调用过多,则会导致栈溢出。

通常来说,正常的函数调用,其关系链并不会过长,因此通常不会出现这个问题。但对于递归函数这种情况,递归次数取决于问题规模,则很容易出现这个问题。

为了解决这个问题,聪明的程序员们提出了尾递归优化,在廖雪峰老师Python教程关于递归函数一篇中(这篇文章肯定不是最好的关于递归函数讲解的文章,只是刚好看到,所以拿来引用),有这么一句话

解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

同时,廖雪峰老师还给出了相关的例子。对于求解某数阶乘,最普通的递归写法如下:

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

函数执行过程如下

===> 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

修改成尾递归的形式则如下:

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函数):

===> fact_iter(5, 1)
===> fact_iter(4, 5)
===> fact_iter(3, 20)
===> fact_iter(2, 60)
===> fact_iter(1, 120)
===> 120

尾递归前后最大的变化就如同廖雪峰老师所言,函数返回仅包含一句自身调用,并不包含更多的表达式需要执行计算。因此,在这种情况下,实际上通过优化,我们可以让原来A->B->C 然后A<-B<-C的过程简化为A->B->C一次性。因为我们不需要拿着C函数给的信息回头继续执行B函数。放在递归函数中,即我们只需要一直向下执行即可,而不需要返回,因此我们也不需要保存沿途函数的栈帧。 这样自然不会栈溢出。

不过,以上只是理想状态。 通常我们把递归函数写成了可以被尾递归优化的形式,但具体能不能达到效果,还得看编译器。大多数编译器并不会因为我们的代码是可以被尾递归优化的,就进行优化,而是按照传统的函数调用方式,依旧去记录沿途经过的函数的栈帧,这样还是会栈溢出!

那么编译器是如何去优化这类代码,以实现真正的尾递归优化的呢?实际上这并不在本文的讨论当中,不过笔者看了一些文章,大致上就是将其转换为循环的形式,以减少栈的占用。 具体可参考《尾递归为啥能优化?》

// 全文完

因笔者能力有限,若文章内容存在错误或不恰当之处,欢迎留言、私信批评指正。
Email:YePeanut[at]foxmail.com

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python解释器没有针对尾递归优化,所以即使在Python中使用尾递归方式编写函数,也可能导致栈溢出的问题。 尾递归优化可以在函数返回的时候,调用自身本身,并且,在return语句中不包含表达式,这样编译器或者解释器就可以将尾递归进行优化。最后的结果是,不管递归函数进行多少次调用,都只占用一个栈帧,从而避免了栈溢出的情况。然而,Python解释器并没有对尾递归进行优化,所以即使使用尾递归方式编写函数,也可能导致栈溢出问题的出现。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [python基础编程:Python递归及尾递归优化操作实例分析](https://blog.csdn.net/haoxun11/article/details/104976941)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [python-递归函数及尾递归优化](https://blog.csdn.net/wdnysjqr/article/details/80374203)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Python尾递归优化实现代码及原理详解](https://download.csdn.net/download/weixin_38545923/13705703)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值