【Scala】尾递归优化
以递归方式思考
递归通过灵巧的函数定义,告诉计算机做什么。在函数式编程中,随处可见递归思想的运用。
下面给出几个递归函数的例子:
object RecursiveExample extends App{
// 数列求和例子
def sum(xs: List[Int]): Int =
if (xs.isEmpty)
1
else
xs.head + sum(xs.tail)
// 求最大值例子
def max(xs: List[Int]): Int =
if (xs.isEmpty)
throw new NoSuchElementException
else if (xs.size == 1)// 递归的边界条件
xs.head
else
if (xs.head > max(xs.tail)) xs.head else max(xs.tail)
// 翻转字符串
def str_reverse(xs: String): String =
if (xs.length == 1)
xs
else
str_reverse(xs.tail) + xs.head
// 快速排序例子
def quicksort(ls: List[Int]): List[Int] = {
if (ls.isEmpty)
ls
else
quicksort(ls.filter(_ < ls.head)) ::: ls.head :: quicksort(ls.filter(_ > ls.head))
//quicksort(ls.filter(x => x < ls.head)) ::: ls.head :: quicksort(ls.filter(x => x > ls.head))
}
}
我们以上面代码最后一个快速排序函数为例,使用递归的方式,其代码实现非常的简洁和通俗易懂。递归函数的核心是设计好递归表达式,并且确定算法的边界条件。上面的快速排序中,认为空列表就是排好序的列表,这就是递归的边界条件,这个条件是递归终止的标志。
尾递归
递归算法需要保持调用堆栈,效率较低,如果调用次数较多,会耗尽内存或栈溢出。然而,尾递归可以克服这一缺点。
尾递归是指递归调用是函数的最后一个语句,而且其结果被直接返回,这是一类特殊的递归调用。由于递归结果总是直接返回,尾递归比较方便转换为循环,因此编译器容易对它进行优化。
递归求阶乘的经典例子
普通递归求解的代码如下:
def factorial(n: BigInt): BigInt = {
if (n <= 1)
1
else
n * factorial(n-1)
}
上面的代码,由于每次递归调用n-1的阶乘时,都有一次额外的乘法计算,这使得堆栈中的数据都需要保留。在新的递归中要分配新的函数栈。
运行过程就像这样:
factorial(4)
--------------
4 * factorial(3)
4 * (3 * factorial(2))
4 * (3 * (2 * factorial(1)))
4 * (3 * (2 * 1))
而下面是一个尾递归版本,在效率上,和循环是等价的:
import scala.annotation.tailrec
def factorialTailRecursive(n: BigInt): BigInt = {
@tailrec
def _loop(acc: BigInt, n: BigInt): BigInt =
if(n <= 1) acc else _loop(acc*n, n-1)
_loop(1, n)
}
这里的运行过程如下:
factorialTailRecursive(4)
--------------------------
_loop(1, 4)
_loop(4, 3)
_loop(12, 2)
_loop(24, 1)
该函数中的_loop在最后一步,要么返回递归边界条件的值,要么调用递归函数本身。
改写成尾递归版本的关键:
尾递归版本最重要的就是找到合适的累加器,该累加器可以保留最后一次递归调用留在堆栈中的数据,积累之前调用的结果,这样堆栈数据就可以被丢弃,当前的函数栈可以被重复利用。
在这个例子中,变量acc就是累加器,每次递归调用都会更新该变量,直到递归边界条件满足时返回该值。
对于尾递归,Scala语言特别增加了一个注释@tailrec,该注释可以确保程序员写出的程序是正确的尾递归程序,如果由于疏忽大意,写出的不是一个尾递归程序,则编译器会报告一个编译错误,提醒程序员修改自己的代码。
菲波那切数列的例子
原始的代码很简单:
def fibonacci(n: Int): Int =
if (n <= 2)
1
else
fibonacci(n-1) + fibonacci(n-2)
尾递归版本用了两个累加器,一个保存较小的项acc1,另一个保存较大项acc2:
def fibonacciTailRecursive(n: Int): Int = {
@tailrec
def _loop(n: Int, acc1: Int, acc2: Int): Int =
if(n <= 2)
acc2
else
_loop(n-1, acc2, acc1+acc2)
_loop(n, 1, 1)
}
几个列表操作中使用尾递归的例子
求列表的长度
def lengthTailRecursive[A](ls: List[A]): Int = {
@tailrec
def lengthR(result: Int, curList: List[A]): Int = curList match {
case Nil => result
case _ :: tail => lengthR(result+1, tail)
}
lengthR(0, ls)
}
翻转列表
def reverseTailRecursive[A](ls: List[A]): List[A] = {
@tailrec
def reverseR(result: List[A], curList: List[A]): List[A] = curList match {
case Nil => result
case h :: tail => reverseR(h :: result, tail)
}
reverseR(Nil, ls)
}
去除列表中多个重复的元素
这里要求去除列表中多个连续的字符,只保留其中的一个。
// If a list contains repeated elements they should be replaced with
// a single copy of the element.
// The order of the elements should not be changed.
// Example:
// >> compress(List('a, 'a, 'a, 'a, 'b, 'c, 'c, 'a, 'a, 'd, 'e, 'e, 'e, 'e))
// >> List('a, 'b, 'c, 'a, 'd, 'e)
def compressTailRecursive[A](ls: List[A]): List[A] = {
@tailrec
def compressR(result: List[A], curList: List[A]): List[A] = curList match {
case h :: tail => compressR(h :: result, tail.dropWhile(_ == h))
case Nil => result.reverse
}
compressR(Nil, ls)
}
转载请注明作者Jason Ding及其出处
Github博客主页(http://jasonding1354.github.io/)
GitCafe博客主页(http://jasonding1354.gitcafe.io/)
CSDN博客(http://blog.csdn.net/jasonding1354)
简书主页(http://www.jianshu.com/users/2bd9b48f6ea8/latest_articles)
**Google搜索jasonding1354进入我的博客主页
【Scala】尾递归优化的更多相关文章
- Scala进阶之路-尾递归优化
Scala进阶之路-尾递归优化 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 递归调用有时候能被转换成循环,这样能节约栈空间.在函数式编程中,这是很重要的,我们通常会使用递归方法来 ...
- Scala尾递归
递归函数应用 首先,我们来对比两个递归方法的求值步骤. 假设有方法gcd,用来计算两个数的最大公约数.下面是欧几里得算法的实现: def gcp(a: Int, b: Int): Int = if ( ...
- .NET 4.6的RyuJIT尾递归优化的Bug
今天看到园子里有一篇新闻稿.NET 4.6的RyuJIT编译器中发现严重的Bug提到,在.Net 4.6的x64程序中默认启用新的JIT程序RyuJIT在处理尾递归指令的时候有一个Bug,导致无法得到 ...
- 对SNL语言的解释器实现尾递归优化
对于SNL语言解释器的内容可以参考我的前一篇文章<使用antlr4及java实现snl语言的解释器>.此文只讲一下"尾递归优化"是如何实现的--"尾递归优化& ...
- Python尾递归优化
Python开启尾递归优化 cpython本身不支持尾递归优化, 但是一个牛人想出的解决办法:实现一个 tail_call_optimized 装饰器 #!/usr/bin/env python2.4 ...
- kotlin递归&尾递归优化
递归: 对于递归最经典的应用当然就是阶乘的计算啦,所以下面用kotlin来用递归实现阶乘的计算: 编译运行: 那如果想看100的阶乘是多少呢? 应该是结果数超出了Int的表述范围,那改成Long型再试 ...
- Py编程方法,尾递归优化,map函数,filter函数,reduce函数
函数式编程 1.面向过程 把大的问题分解成流程,按照流程来编写过程 2.面向函数 面向函数编程=编程语言定义的函数+数学意义上的函数先弄出数学意义上的方程式,再用编程方法编写这个数学方程式注意面向函数 ...
- Scala Tail Recursion (尾递归)
Scala对尾递归进行了优化,甚至提供了专门的标注告诉编译器需要进行尾递归优化.不过这种优化仅限于严格的尾递归,间接递归等情况,不会被优化. 尾递归的概念 递归,大家都不陌生,一个函数直接或间接的调用 ...
- 探索c#之尾递归编译器优化
阅读目录: 递归运用 尾递归优化 编译器优化 递归运用 一个函数直接或间接的调用自身,这个函数即可叫做递归函数. 递归主要功能是把问题转换成较小规模的子问题,以子问题的解去逐渐逼近最终结果. 递归最重 ...
随机推荐
- XML Schema and XMLspy notes
Introduction An xml documents consists of elements, attributes and text. There are two structures in ...
- javascript创建对象的一些方式
通过创建一个Object实例 var person = new Object(); person.name = "zhouquan"; person.age = 21; perso ...
- C和指针 第九章 字符串 字符 字节
C语言中没有字符串类型,字符串是以NUL结尾的字符数组组成的. 高级字符串查找: //计算字符串起始部分,有多少字符是在group中 size_t strspn(char const * str, c ...
- PHP Strom 配置less 并设置编译后在远程开发模式下自动上传css文件
ctrl+alt+s ->File Watchers->add-> 其中Argument中的-x代表最后编译过后的css文件为压缩过的 此时就可以用了,但是编译过后less可以自动上 ...
- SQL/T-SQL实例参考
,D.[Score] B_Score ,'Distince'= CASE WHEN C.Score > D.Score THEN C.[Score] - D.[Score] WHEN C.Sco ...
- Python:如何删除文件中的空白行?
def delblankline(infile,outfile): infopen = open(infile,'r') outfopen = open(outfile,'w') lines = in ...
- mysql触发器,答题记录表同步教学跟踪(用户列表)
aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAABVQAAAOOCAIAAABgEw4AAAAgAElEQVR4nOy92VcT27r/zX+xLtflvt
- windows Service
用c#中创建一个windows服务非常简单,与windows服务相关的类都在System.ServiceProcess命名空间下. 每个服务都需要继承自ServiceBase类,并重写相应的启动.暂停 ...
- 在Mac OS X Yosemite 10.10.3 中搭建第一个 ASP.NET 5 Web 项目
终于有时间在 Mac 上安装一下 ASP.NET 5,网上有许多教程,但是多数的时间比较早了,版本不是最新,搭着 Build 2015 的春风,我也实践一下 Mac OS X 上的 ASP.NET 5 ...
- python备忘
1.引用已经编写好的.py文件(Windows系统) >>>import sys >>>sys.path.append("C:/python") ...