Scala Tail Recursion (尾递归)
Scala对尾递归进行了优化,甚至提供了专门的标注告诉编译器需要进行尾递归优化。不过这种优化仅限于严格的尾递归,间接递归等情况,不会被优化。
尾递归的概念
递归,大家都不陌生,一个函数直接或间接的调用它自己,就是递归了。我们来看一个简单的,计算阶乘的例子。
def factorial(n: Int): Int = {
if( n <= 1 ) 1
else n * factorial(n-1)
}
以上factorial方法,在n>1时,需要调用它自身,这是一个典型的递归调用。如果n=5,那么该递归调用的过程大致如下:
factorial(5)
5 * factorial(4)
5 * (4 * factorial(3))
5 * (4 * (3 * factorial(2)))
5 * (4 * (3 * (2 * factorial(1))))
5 * (4 * (3 * (2 * 1)))
120
递归算法,一般来说比较简单,符合人们的思维方式,但是由于需要保持调用堆栈,效率比较低,在调用次数较多时,更经常耗尽内存。 因此,程序员们经常用递归实现最初的版本,然后对它进行优化,改写为循环以提高性能。尾递归于是进入了人们的眼帘。
那么,什么是尾递归?尾递归是指递归调用是函数的最后一个语句,而且其结果被直接返回,这是一类特殊的递归调用。 由于递归结果总是直接返回,尾递归比较方便转换为循环,因此编译器容易对它进行优化。现在很多编译器都对尾递归有优化,程序员们不必再手动将它们改写为循环。
以上阶乘函数不是尾递归,因为递归调用的结果有一次额外的乘法计算,这导致每一次递归调用留在堆栈中的数据都必须保留。我们可以将它修改为尾递归的方式。
def factorialTailrec(n: BigInt, acc: BigInt): BigInt = {
if(n <= 1) acc
else factorialTailrec(n-1, acc * n)
}
现在我们再看调用过程,就不一样了,factorialTailrec每一次的结果都是被直接返回的。还是以n=5为例,这次的调用过程如下。
factorialTailrec(5, 1)
factorialTailrec(4, 5) // 1 * 5 = 5
factorialTailrec(3, 20) // 5 * 4 = 20
factorialTailrec(3, 60) // 20 * 3 = 60
factorialTailrec(2, 120) // 60 * 2 = 120
factorialTailrec(1, 120) // 120 * 1 = 120
120
以上的调用,由于调用结果都是直接返回,所以之前的递归调用留在堆栈中的数据可以丢弃,只需要保留最后一次的数据,这就是尾递归容易优化的原因所在, 而它的秘密武器就是上面的acc,它是一个累加器(accumulator,习惯上翻译为累加器,其实不一定非是“加”,任何形式的积聚都可以),用来积累之前调用的结果,这样之前调用的数据就可以被丢弃了。
出处:http://meetfp.com/zh/blog/tail-recursion
Scala Tail Recursion (尾递归)的更多相关文章
- scala tail recursive优化,复用函数栈
在scala中如果一个函数在最后一步调用自己(必须完全调用自己,不能加其他额外运算子),那么在scala中会复用函数栈,这样递归调用就转化成了线性的调用,效率大大的提高.If a function c ...
- 尾递归(Tail Recursion)和Continuation
递归: 就是函数调用自己. func() { foo(); func(); bar(); } 尾调用:就是在函数的最后,调用函数(包括自己). foo(){ return bar(); } 尾递归:就 ...
- 拾遗:关于“尾递归”- tail recursion
定义[个人理解]: 尾递归,即是将外层得出的常量计算因子,以函数参数的形式逐层向内传递,即内层调用整合外层调用的产出,整个递归的结果最终由最内层的一次函数调用得出:而通常的递归则是外层调用阻塞.等待内 ...
- scala实战学习-尾递归函数
求 $$ \Sigma\sideset{^b_a}f(x) $$ object sumfunc{ def sum(f: Int => Int)(a: Int)(b:Int): Int = { @ ...
- 用尾递归和普通递归实现n!算法,二者比较
尾递归 - Tail Recursion尾递归是针对传统的递归算法而言的, 传统的递归算法在很多时候被视为洪水猛兽. 它的名声狼籍, 好像永远和低效联系在一起.尾递归就是从最后开始计算, 每递归一次就 ...
- 0031 Java学习笔记-梁勇著《Java语言程序设计-基础篇 第十版》英语单词
第01章 计算机.程序和Java概述 CPU(Central Processing Unit) * 中央处理器 Control Unit * 控制单元 arithmetic/logic unit /ə ...
- Scala尾递归
递归函数应用 首先,我们来对比两个递归方法的求值步骤. 假设有方法gcd,用来计算两个数的最大公约数.下面是欧几里得算法的实现: def gcp(a: Int, b: Int): Int = if ( ...
- Scala HandBook
目录[-] 1. Scala有多cool 1.1. 速度! 1.2. 易用的数据结构 1.3. OOP+FP 1.4. 动态+静态 1.5. DSL 1.6 ...
- 泛函编程(3)-认识Scala和泛函编程
接着昨天的文章,再示范一个稍微复杂一点的尾递归tail recursion例子:计算第n个Fibonacci数.Fibonacci数第一.第二个数值分别是0,1,按顺序后面的数值是前面两个数的加合.例 ...
随机推荐
- OUYA设备的购买和安装
OUYA设备的购买和安装 在为OUYA这款游戏终端做具体的开发之前,建议读者先上手感受一下这款设备的游戏体验.本节所涉及的内容包括,OUYA设备的购买.安装.设置,以及最终上手开始游戏这一系列的过程本 ...
- 不容易系列之(4)——考新郎[HDU2049]
不容易系列之(4)——考新郎 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)To ...
- [译]JavaScript:函数的作用域链
原文:http://blogs.msdn.com/b/jscript/archive/2007/07/26/scope-chain-of-jscript-functions.aspx 在JavaScr ...
- HDU 3966(树链剖分+点修改+点查询)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3966 题目大意:营地的分布成树型.每个营地都有一些人,每次修改修改一条链上的所有营地的人数,每次查询单 ...
- CentOS6.4 配置Tengine
1.安装Nginx所需的pcre-devel库 yum install -y gcc gcc-c++ wget ftp://ftp.csx.cam.ac.uk/pub/software/program ...
- Java递归搜索指定文件夹下的匹配文件
import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Queue; /** ...
- LINQ Count/Sum/Min/Max/Avg
参考:http://www.cnblogs.com/peida/archive/2008/08/11/1263384.html Count/Sum/Min/Max/Avg用于统计数据,比如统计一些数据 ...
- android之打开网页
首先改写strings.xml文件 代码如下: <resources> <string name="app_name">Intent应用</strin ...
- NodeJs - 100
Nodejs官方文档 https://nodejs.org/en/docs/ Nodejs官方网站 https://nodejs.org/en/ Nodejs的特征: 1.采用非阻塞性IO机制:—— ...
- 我对Java的java.lang.Class这个类的深入理解
类是对同一类事物的描述,字段具体的值只有到类实例化时才会指定,静态字段除外.所有的类也是同一类事物,用Class这个类来描述.Class类与String.Person等类是同一个级别的.java的字节 ...