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 (尾递归)的更多相关文章

  1. scala tail recursive优化,复用函数栈

    在scala中如果一个函数在最后一步调用自己(必须完全调用自己,不能加其他额外运算子),那么在scala中会复用函数栈,这样递归调用就转化成了线性的调用,效率大大的提高.If a function c ...

  2. 尾递归(Tail Recursion)和Continuation

    递归: 就是函数调用自己. func() { foo(); func(); bar(); } 尾调用:就是在函数的最后,调用函数(包括自己). foo(){ return bar(); } 尾递归:就 ...

  3. 拾遗:关于“尾递归”- tail recursion

    定义[个人理解]: 尾递归,即是将外层得出的常量计算因子,以函数参数的形式逐层向内传递,即内层调用整合外层调用的产出,整个递归的结果最终由最内层的一次函数调用得出:而通常的递归则是外层调用阻塞.等待内 ...

  4. scala实战学习-尾递归函数

    求 $$ \Sigma\sideset{^b_a}f(x) $$ object sumfunc{ def sum(f: Int => Int)(a: Int)(b:Int): Int = { @ ...

  5. 用尾递归和普通递归实现n!算法,二者比较

    尾递归 - Tail Recursion尾递归是针对传统的递归算法而言的, 传统的递归算法在很多时候被视为洪水猛兽. 它的名声狼籍, 好像永远和低效联系在一起.尾递归就是从最后开始计算, 每递归一次就 ...

  6. 0031 Java学习笔记-梁勇著《Java语言程序设计-基础篇 第十版》英语单词

    第01章 计算机.程序和Java概述 CPU(Central Processing Unit) * 中央处理器 Control Unit * 控制单元 arithmetic/logic unit /ə ...

  7. Scala尾递归

    递归函数应用 首先,我们来对比两个递归方法的求值步骤. 假设有方法gcd,用来计算两个数的最大公约数.下面是欧几里得算法的实现: def gcp(a: Int, b: Int): Int = if ( ...

  8. Scala HandBook

    目录[-] 1.   Scala有多cool 1.1.     速度! 1.2.     易用的数据结构 1.3.     OOP+FP 1.4.     动态+静态 1.5.     DSL 1.6 ...

  9. 泛函编程(3)-认识Scala和泛函编程

    接着昨天的文章,再示范一个稍微复杂一点的尾递归tail recursion例子:计算第n个Fibonacci数.Fibonacci数第一.第二个数值分别是0,1,按顺序后面的数值是前面两个数的加合.例 ...

随机推荐

  1. javascript优化--10模式(设计模式)01

    单体模式:保证一个特定类仅有一个实例;即第二次使用同一个类创建新对象时,应该得到与第一个所创建对象完全相同对象: 在JS中,可以认为每次在使用对象字面量创建对象的时候,实际上就在创建一个单体: 当使用 ...

  2. Xamarin Android提示找不到资源属性定义

    Xamarin Android提示找不到资源属性定义 错误信息:”Resource.Attribute”未包含”actonBarSize”的定义Xamarin Android经常会出现找不到资源属性的 ...

  3. FireBug使用总结

    FireBug是一个用于网站前端的开发工具,它是Firefox浏览器的一个拓展插件,它可以调试JavaScript.查看DOM.分析CSS.监控网络流量以及进行Ajax交互等.提供了几乎前端开发需要的 ...

  4. onclientclick和onclick区别

    OnClientClick是客户端脚本,一般使用javascript,在客户端,也就是IE中运行,点击后马上执行OnClick是服务器端事件处理函数,使用C#或者vb.net,在服务器端,也就是IIS ...

  5. div里嵌套了img底部会有白块问题和图片一像素问题解决

    div里嵌套了img底部会有白块 因为img默认是按基线(baseline)对齐的.对比一下图片和右边的p, q, y等字母,你会发现这三个字母的“小尾巴”和图片下方的空白一样高.下面这张图中的黑线就 ...

  6. 静态页分页功能js代码

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  7. C++ Get Current Time 获取当前时间

    在做项目中,我们经常需要获取系统的当前时间,那么如何获取呢,参见下面的代码: /* asctime example */ #include <stdio.h> /* printf */ # ...

  8. 微课程--Android--Activity组建与Intent

    安卓的四大基本组件 Activity是经常使用的组件 1 展示用户界面 2 响应用户操作 Service 1 在后台长时间运行 2 没有用户界面 ContentProvider 1 管理和共享应用数据 ...

  9. windows batch语法

    windows BATCH基本知识扩展名是bat(在nt/2000/xp/2003下也可以是cmd)的文件就是批处理文件. ==== 注 =============================== ...

  10. Nginx 笔记与总结(15)nginx 实现反向代理 ( nginx + apache 动静分离)

    在 nginx 中,proxy 用来实现反向代理,upstream 用来实现负载均衡. 例如有两台服务器,nginx 服务器作为代理服务器,执行 .html 文件,apache 服务器上执行 .php ...