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,按顺序后面的数值是前面两个数的加合.例 ...
随机推荐
- json学习系列(7)JSONBuilder的用法
JSONBuilder可以向文件中写入写入json字符串.如下面的例子: public class Test { public static void main(String args[]) thro ...
- 三角形[HDU2039]
三角形 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submiss ...
- BZOJ3799 : 字符串重组
从大到小枚举答案与T串的lcp,然后贪心 #include<cstdio> #include<cstring> char s[5010],t[5010],ans[5010]; ...
- BZOJ3750 : [POI2015]Pieczęć
枚举第一个位置,然后暴力检验. #include<cstdio> #define N 1010 int T,n,m,a,b,x,y,i,j,k,q[N*N][2],cnt;char s[N ...
- 更快、更强——解析Hadoop新一代MapReduce框架Yarn(CSDN)
摘要:本文介绍了Hadoop 自0.23.0版本后新的MapReduce框架(Yarn)原理.优势.运作机制和配置方法等:着重介绍新的Yarn框架相对于原框架的差异及改进. 编者按:对于业界的大数据存 ...
- storm源码之理解Storm中Worker、Executor、Task关系 + 并发度详解
本文导读: 1 Worker.Executor.task详解 2 配置拓扑的并发度 3 拓扑示例 4 动态配置拓扑并发度 Worker.Executor.Task详解: Storm在集群上运行一个To ...
- [Unity2D]游戏引擎介绍
由于手机游戏的流行,目前2D游戏开发的需求量也越来越大了,因此Unity3D游戏引擎也增加了2D游戏开发的支持,之前是可以通过第三方的2D游戏组件可以支持2D游戏开发,现在是官方的版本就支持了.Uni ...
- RN组件之ListView
/** * Created by DaGuo on 2016/4/7. */ 'use strict' import React,{ Component, View, Text, ListView, ...
- ARC指南3 - @property
本章介绍引入ARC后@property的使用,跟ARC之前的还是很不一样的 一..h和.m文件的变化说明 1.对于.h头文件,主要是将属性定义由retain变为strong @property ( ...
- U-Mail邮件服务系统任意文件上传+执行漏洞(runtime缺陷与验证绕过)
http://www.wooyun.org/bugs/wooyun-2010-061859