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,按顺序后面的数值是前面两个数的加合.例 ...
随机推荐
- JVM的栈内存
每当启动一个新线程时,Java虚拟机都会为它分配一个Java栈.Java栈以帧为单位保存线程的运行状态.虚拟机只会直接对Java栈执行两种操作:以帧为单位的压栈和出栈. 某个线程正在执行的方法被称为该 ...
- 寒假D3 A Find the Lost Sock
Alice bought a lot of pairs of socks yesterday. But when she went home, she found that she has lost ...
- Comparing the Performance of .NET Serializers(zz)
The .NET framework comes with a variety of different serializers. Hopefully, my overview of these se ...
- Distributed RPC —— 分布式RPC
This tutorial showed how to do basic stream processing on top of Storm. There's lots more things you ...
- 【wikioi】1034 家园(最大流+特殊的技巧)
http://wikioi.com/problem/1034/ 太神了这题. 其实一开始我以为是费用流,但是总感觉不对. 原因是我没看到一句话,特定的时刻到达特定的点!! 也就是说,并不是每艘船每次都 ...
- SpringMVC、Struts1、Struts2和SSH2框架中单例与多例的解析
struts1是单例提供服务,请求数据绑定在actionform里,form当做参数传入调用方法,不用实例变量就不会出现并发问题. spring mvc的contrller也是这样的模式 struts ...
- 如何获取checkboxlist的多个选中项
string[] array = dt.Rows[0]["s_type"].ToString().Split('|'); foreach (ListI ...
- Struts2_ValueStack,OGNL详解
一.ValueStack 1.ValueStack是一个接口,在struts2中使用OGNL(Object-Graph Navigation Language)表达式实际上是使用实现了Value ...
- Java接口、Java抽象类、C++抽象类的区别
由于这三种数据类型都是为了创建类层次结构的顶层构架,且用法有些许相似之处,这里简单区分一下: 接口: 接口用interface关键字定义, 名字一般使用-able形式的形容词. 接口通常定义抽象方法和 ...
- ajax的同步和异步问题 (转)
async默认的设置值为true,这种情况为异步方式,当ajax发送请求后,在等待server端返回的数据的过程中,程序会继续执行ajax块后面的代码,直到服务器返回正确的结果才会去执行success ...