今天在做《剑指Offer》第十题时,发现了一个用尾递归的解法,由于之前对于尾递归并没有太多了解,于是查阅了一些资料,在此对其进行一个简单的总结。关于其它题目的题解与笔记,感兴趣的朋友可以到我的Github或个人博客上看看:剑指Offer笔记 Cenjie’s Blog ,
以下是正文。
递归本质
递归的本质是自己调用自己,因为是嵌套调用,所以栈帧无法回收,在递归调用的层级太多时,往往会引发调用栈溢出,也就是内存溢出。
尾递归概述
尾递归本质与递归并无区别,只不过是递归的一种特殊写法。尾递归要求递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分,例如 return 3f(n)
或者return f(n)+f(n-1)
都是不允许的。
由于尾递归也是一种递归,因此这种写法本身并不会有任何的优化效果,内存依旧会溢出,只不过一些编译器中会加入对尾递归的优化机制,在编译代码时自动根据尾递归的特性对其进行优化。
如何优化尾递归
因为在递归调用自身的时候,这一层函数已经没有要做的事情了,虽然被递归调用的函数是在当前的函数里,但是他们之间的关系已经在传参的时候了断了,也就是这一层函数的所有变量什么的都不会再被用到了,所以当前函数虽然没有执行完,不能弹出栈,但它确实已经可以出栈了,这是一方面。
另一方面,正因为调用的是自身,所以需要的存储空间是一模一样的,那干脆重新刷新这些空间给下一层利用就好了,不用销毁再另开空间。
因此,为尾递归进行优化主要分两个步骤:
1、写成尾递归的形式。
2、编译器遇到此形式时自动为其优化。
而在第十题:斐波那契数列中,由于Java没有对尾递归进行优化,因此与使用普通递归并无太大区别,依然会产生内存溢出的问题。