前言
前一阶段在读博客,突然发现了尾递归的概念,一开始还以为是递归,但仔细一看却不是。 虽然没有深入研究,但是通过经典斐波那契数列的实现可以看出尾递归和普通递归的区别。
什么是末尾递归
如果一个函数中的所有递归调用都出现在函数的末尾,则递归函数称为末尾递归。 如果递归调用是整个函数主体中最后一次执行的语句,并且其返回值不是表达式的一部分,则此递归调用是尾部递归。 尾部递归函数的特点是在回归过程中什么都不用做。 这个特性很重要。 因为大多数现代编译器都会利用这个特征自动生成优化的代码。 (From百度词条)
当编译器检测到函数调用是尾部递归时,它将复盖当前活动记录,而不是在堆栈中创建新的。 编译器可以做到这一点。 递归调用是当前活动时段内最后一次执行的语句,因此返回此调用时,栈帧无法执行其他操作,因此不需要保存栈帧。 覆盖当前堆栈帧,而不是在其上新添加,大大减少了使用的堆栈空间,提高了实际执行效率。 (From百度词条)
斐波那契数列
我想这个数列很多人都很熟悉,但在面试中也经常被问到。
1 1 2 3 5 8 13 21 34 55 89。
用普通的算法实现的是味增:
公共信息(intf ) )。
{return n 2? 1:f(n-1 ) f ) n-2;
}
因为是递归调用,所以每次调用f函数时,f(n )都会重复计算。 这是因为,每个值最终都会分解为f(1) f(1)。
如上所述,f(5)=f )1) f )0) f )1) f )1) f )1) f )0) f )8)
请看末尾递归的实现:
公共静态信息(inta2,int a1,inta2 ) )。
{return n==0? a1:f(n-1、a2和a1 a2 );
}
递归过程将计算结果直接作为参数传递给递归方法。 这意味着递归过程不需要保存以前的计算值。 其实这个方法也可以看作是一个方法的转变。
intadd(intx,int y )=x y; intadd(s )=s;
如上述代码所示,add (1,2 )=add(3) 3;
让我们看看末尾递归的调用。 f (5,1,1 )=f ) 4,1,2 )=f (3,2,3 )=f ) 2,3,5 )=f ) 1,5,8 )=f ) 0,8,13 )。
因此,调用f (5,1,1 )时,就是变相调用了f ) 0,8,13 )。 如上所述,当编译器检测到函数调用是尾部递归时,它将复盖当前活动日志,而不是在堆栈中创建新的。 因为后续方法不依赖于前面的方法。
总结
通过斐波那契数列可以很容易地看出普通递归和末尾递归的区别,当然这只是我的浅显理解。 如果有说明错的地方再打我。