今天在围观大神博客时,看到尾递归这个名词,这里对本身看到的作个总结。html
1. 递归算法
一个函数直接或间接的调用自身,这个函数就是一个递归函数。尾递归也是一种特殊的递归函数。函数
如计算一个阶乘函数:post
public static int fac(int n) { if(n ==0 ) { return 1; } if(n==1) { return 1; } else { return n*fac(n-1); } }
这里fac(n)在计算过程当中会不停的调用自身。优化
递归函数的特色:url
(1)递归就是在过程或函数里调用自身。spa
(2)在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。(上面n==1,就是递归出口)code
递归函数在调用自身的过程当中,须要将每一层函数的returnAddress,局部变量等保存在栈存储中进行后面的运算,因此当递归深度过大时,递归函数会出现栈溢出的错误。htm
2. 尾递归blog
尾递归是一种特殊的递归,知足的要求是:函数的最后执行代码除了调用函数自身外,再也不执行其余运算。
public static int facTail(int n,int m) { if(n==0) { return 1; } if(n==1) { return m; } else { return facTail(n-1,m*n); } }
上面的函数就是一个尾递归函数 ,由于最后执行代码是调用函数自身。
3.编译器是怎样优化尾递归的?
咱们知道递归调用是经过栈来实现的,每调用一次函数,系统都将函数当前的变量、返回地址等信息保存为一个栈帧压入到栈中,那么一旦要处理的运算很大或者数据不少,有可能会致使不少函数调用或者很大的栈帧,这样不断的压栈,很容易致使栈的溢出。
咱们回过头看一下尾递归的特性,函数在递归调用以前已经把全部的计算任务已经完毕了,他只要把获得的结果全交给子函数就能够了,无需保存什么,子函数其实能够不须要再去建立一个栈帧,直接把就着当前栈帧,把原先的数据覆盖便可。相对的,若是是普通的递归,函数在递归调用以前并无完成所有计算,还须要调用递归函数完成后才能完成运算任务,好比return n * fact(n - 1);这句话,这个fact(n)在算完fact(n-1)以后才能获得n * fact(n - 1)的运算结果真后才能返回。
综上所述,编译器对尾递归的优化实际上就是当他发现你丫在作尾递归的时候,就不会去不断建立新的栈帧,而是就着当前的栈帧不断的去覆盖,一来防止栈溢出,二来节省了调用函数时建立栈帧的开销,用《算法精解》里面的原话就是:“When a compiler detects a call that is tail recursive, it overwrites the current activation record instead of pushing a new one onto the stack.”
目前编译器支持尾递归优化的语言有C,因此在其余语言下能够考虑将递归替换成迭代循环迭代来实现。
参考:
1. 浅谈尾递归
2. 递归与尾递归总结