递归和尾递归的区别和实现

转载:https://blog.csdn.net/zcyzsy/article/details/77151709

一. 递归

1.定义如下:
递归,就是在运行的过程中调用自己。
构成递归需具备的条件:

    1. 子问题须与原始问题为同样的事,且更为简单;
    2. 不能无限制地调用本身,须有个出口,化简为非递归状况处理。
    3. 终止条件:当出现某一递归函数不再调用自己时,此次就为递归的终止条件,之后就进行向上回归。

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即可。

总结:尾递归其实最精髓就是 通过参数传递结果,达到不压栈的目的由于递归到末尾已经算出最终结果,因此不用回归,因此直接覆盖上次函数栈帧即可,在递归过程中栈中始终只有一个栈帧空间,因此很省空间

目前操作系统的内存还不会,因此对内存的栈帧如果运行还不了解,这儿对尾指针的只占一个栈空间理解了!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值