尾递归
尾递归的定义
如果一个函数的所有地柜形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归。当递归调用时整个函数中过最后执行的语句且它的返回值不属于表达式的一部分时,这个地柜调用就是尾递归。尾递归的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。
尾递归的原理
当编译器检测到一个函数是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。编译器可以做到这点,因为递归调用时当前活跃期内最后一条待执行的语句,于是这个调用返回时栈帧中并没有其他事情可做,因此也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在其之上重新添加一个,这样所使用的栈空间就大大缩减了,这使得实际的运行效率变得更高。
尾递归的实例
package com.fpc.Test; public class TailRescuvie { //线性递归 public int Rescuvie( int n) { return ( n == 1 )? 1 : n * Rescuvie(n - 1); } //封装用 public int tailRescuvie( int n ) { return ( n == 0 ) ? 1 : tailRescuive( n , 1 ); } //尾递归 public int tailRescuive( int n , int a ) { return ( n == 1 ) ? a : tailRescuive( n - 1 , a * n); } public static void main( String[] args ) { TailRescuvie t = new TailRescuvie(); System.out.println(t.Rescuvie(5)); System.out.println(t.tailRescuvie(5)); } }
对于线性递归,他的递归过程如下:
- Rescuvie(5)
- {5*Rescuvie(4)}
- {5*{4*Rescuvie(3)}}
- {5*{4*{3*Rescuvie(2)}}}
- {5*{4*{3*{2*Rescuvie(1)}}}}
- {5*{4*{3*{2*1}}}}
- {5*{4*{3*2}}}
- {5*{4*6}}
- {5*{24}
- 120
对于尾递归,他的递归过程如下:
- tailRescuvie(5)
- tailRescuvie(5,1)
- tailRescuvie(4,5)
- tailRescuvie(3,20)
- tailRescuvie(2,60)
- tailRescuvie(1,120)
- 120
很容易看出,普通的线性递归比尾递归更加消耗资源,在实现上来说,每次重复的过程调用都是的调用链条不断加长,系统不得不使用栈进行数据保存和回复,而尾递归就不存在这样的问题,因为他的状态完全由n和a来保存。