转载:https://blog.csdn.net/zcyzsy/article/details/77151709
一. 递归
1.定义如下:
递归,就是在运行的过程中调用自己。
构成递归需具备的条件:
-
- 子问题须与原始问题为同样的事,且更为简单;
- 不能无限制地调用本身,须有个出口,化简为非递归状况处理。
- 终止条件:当出现某一递归函数不再调用自己时,此次就为递归的终止条件,之后就进行向上回归。
2 分析递归的工作原理:
C程序在内存中的组织方式:BSS段,数据段 ,代码段,堆(heap),栈(stack) ;
当C程序中调用了一个函数时,栈中会分配一块空间来保存与这个调用相关的信息,每一个调用都被当作是活跃的。栈上的那块存储空间称为活跃记录或者栈帧
栈帧由5个区域组成:输入参数、返回值空间、计算表达式时用到的临时存储空间、函数调用时保存的状态信息以及输出参数。
递归过程的压栈和出栈,时间和空间都有很大的消耗,
二.尾递归原理
当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。编译器可以做到这点,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其他事情可做,因此也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在其之上重新添加一个,这样所使用的栈空间就大大缩减了,这使得实际的运行效率会变得更高。
个人理解这个覆盖: 在调用这个递归函数时,目的是为了最终返回一个结果值给我,当在递归过程中,由于每次将上一个父递归的结果通过参数传递结果给下一个子递归函数,所以不用保留上一个递归函数的现场数据的指针,因为这个递归函数不用返回了,不需要返回地址指针了,所以进行覆盖,当递归到末尾时,已经求出这个递归值了,不用沿着父递归函数返回了,直接在末尾的递归函数返回结果值给用户即可。由于递归到末尾已经算出最终结果,因此不用回归,因此直接覆盖上次函数栈帧即可,在递归过程中栈中始终只有一个栈帧空间,因此很省空间
三、递归与尾递归代码比较分析
1.递归实现阶乘函数
int fact(int n) {
if (n < 0)
return 0;
else if(n == 0 || n == 1)
return 1;
else
return n * fact(n - 1);
}
2.尾递归实现阶乘函数
int facttail(int n, int res)
{
if (n < 0)
return 0;
else if(n == 0)
return 1;
else if(n == 1)
return res;
else
return facttail(n - 1, n *res);
}
代码1:在每次函数调用计算n倍的(n-1)!的值,让n=n-1并持续这个过程直到n=1为止。这种定义不是尾递归的,因为每次函数调用的返回值都依赖于用n乘以下一次函数调用的返回值,因此每次调用产生的栈帧将不得不保存在栈上直到下一个子调用的返回值确定。
代码2:函数比代码1多个参数res,除此之外并没有太大区别。res(初始化为1)维护递归层次的深度。这就让我们避免了每次还需要将返回值再乘以n。然而,在每次递归调用中,令res=n*res并且n=n-1。继续递归调用,直到n=1,这满足结束条件,此时直接返回res即可。
总结:尾递归其实最精髓就是 通过参数传递结果,达到不压栈的目的。由于递归到末尾已经算出最终结果,因此不用回归,因此直接覆盖上次函数栈帧即可,在递归过程中栈中始终只有一个栈帧空间,因此很省空间
目前操作系统的内存还不会,因此对内存的栈帧如果运行还不了解,这儿对尾指针的只占一个栈空间理解了!