浅谈尾递归
2016-06-22 11:21
295 查看
今天在围观大神博客时,看到尾递归这个名词,这里对自己看到的做个总结。
1. 递归
一个函数直接或间接的调用自身,这个函数就是一个递归函数。尾递归也是一种特殊的递归函数。
如计算一个阶乘函数:
这里fac(n)在计算过程中会不停的调用自身。
递归函数的特点:
(1)递归就是在过程或函数里调用自身。
(2)在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。(上面n==1,就是递归出口)
递归函数在调用自身的过程中,需要将每一层函数的returnAddress,局部变量等保存在栈存储中进行后面的运算,所以当递归深度过大时,递归函数会出现栈溢出的错误。
2. 尾递归
尾递归是一种特殊的递归,满足的要求是:函数的最后执行代码除了调用函数自身外,不再执行其他运算。
上面的函数就是一个尾递归函数 ,因为最后执行代码是调用函数自身。
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. 递归与尾递归总结
1. 递归
一个函数直接或间接的调用自身,这个函数就是一个递归函数。尾递归也是一种特殊的递归函数。
如计算一个阶乘函数:
public static int fac(int n) { if(n ==0 ) { return 1; } if(n==1) { return 1; } else { return n*fac(n-1); } }
这里fac(n)在计算过程中会不停的调用自身。
递归函数的特点:
(1)递归就是在过程或函数里调用自身。
(2)在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。(上面n==1,就是递归出口)
递归函数在调用自身的过程中,需要将每一层函数的returnAddress,局部变量等保存在栈存储中进行后面的运算,所以当递归深度过大时,递归函数会出现栈溢出的错误。
2. 尾递归
尾递归是一种特殊的递归,满足的要求是:函数的最后执行代码除了调用函数自身外,不再执行其他运算。
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. 递归与尾递归总结
相关文章推荐
- 前端技术:jQuery
- 增加listview的item点击水纹效果
- codeforce A. Alyona and Numbers
- 百度2017暑期实习生编程题-单词接龙
- .NET基于Redis缓存实现单点登录SSO的解决方案
- 15.3节练习
- 区别和详解:jQuery extend()和jQuery.fn.extend()
- 图的几种表示方法
- Arduino学习日记-使用GY-30光照传感器
- 流媒体介绍
- 论代码审查的重要性
- AndroidStudio 常用快捷键
- static全解
- C++之类的成员函数的原理
- 2016.6.22笔记(1)-线程之间的通信
- C#控制Excel Sheet使其自适应页宽与列宽的方法
- Html.Action Html.RenderAction Html.Partial Html.RenderPartial Url.Action Html.ActionLink 大括号和小括号区别
- Android延时的几种常用方式
- 人月神话读书札记
- 如何在Beyond Compare中快速同步文件夹的