【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进入我的博客主页

文/JasonDing(简书作者)
原文链接:http://www.jianshu.com/p/d177c30f59de
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

时间: 2024-08-07 02:58:41

【Scala】尾递归优化的相关文章

JVM原生不支持尾递归优化,但是Scala编译器支持

The JVM doesn't support TCO natively, so tail recursive methods will need to rely on the Scala compiler performing the optimization.----------"Scala in Depth" 3.5.2 Jvm本身是不支持尾递归优化得,需要编译器支持,而Java编译器不支持,但是Scala支持.写一个简单的计算1到n的和的递归算法验证一下. public cla

Gcc 优化选项 与尾递归优化

今天做高性能计算机系统的作业的时候,发现gcc中的优化选项有很多应用 . 例如对于C源码: #include <stdio.h> #include <stdlib.h> int main() { int x[101],y[101]; int a,i; a = 5; for(i=0;i<=100;i++) { x[i] = i+1; y[i] = i; } for(i=100; i>=0; i--) y[i] += a*x[i]; return 0; } 1.直接用gcc

对SNL语言的解释器实现尾递归优化

对于SNL语言解释器的内容可以参考我的前一篇文章<使用antlr4及java实现snl语言的解释器>.此文只讲一下"尾递归优化"是如何实现的--"尾递归优化"并不是一个语言实现必须要做的,但这是一个比较有趣的东西,所以我还是想拿来讲一讲. 在前一篇文章中有一个例子: program recursion    procedure f(integer d);    begin        write(d);        f(d + 1)    endbe

Python开启尾递归优化!

本文和大家分享的主要是python中尾递归相关使用方法,希望通过本文的分享能帮助大家更好的学习python语言,一起来看看吧. 一般递归与尾递归 一般递归 def normal_recursion(n): if n == 1: return 1 else: return n + normal_recursion(n-1) 执行: normal_recursion(5)5 + normal_recursion(4)5 + 4 + normal_recursion(3)5 + 4 + 3 + nor

.NET 4.6的RyuJIT尾递归优化的Bug

今天看到园子里有一篇新闻稿.NET 4.6的RyuJIT编译器中发现严重的Bug提到,在.Net 4.6的x64程序中默认启用新的JIT程序RyuJIT在处理尾递归指令的时候有一个Bug,导致无法得到正确的结果. 微软在其官方BlogRyuJIT Bug Advisory in the .NET Framework 4.6更是较为详细的介绍了这一bug.虽然尾递归使用得并不多(貌似在F#中有很多应用),但这个bug算是比较严重的了: 这个问题只有在应用了代码优化之后才会出现,由于多数开发者与项目

一个很Cool的Idear-&gt;Python的尾递归优化

偶然在国外一个网站瞅到的,非常的酷,发出来共享一下.一般来说,Python和Java,C#一样是没有尾递归自动优化的能力的,递归调用受到调用栈长度的限制被广泛的诟病,但是这个狂人用一个匪夷所思的方法解决了这个问题并在Python上实现了,从此Python的递归调用再也不用受到调用栈长度的制约,太酷了. 首先我们还是从递归说起,之前我发过一篇 <浅谈递归过程以及递归的优化>其中用到了斐波那契数来作为例子.线性递归的算法由于太过一低效就被我们Pass掉了,我们先来看尾递过方式的调用: 1 def 

尾递归优化

来自廖雪峰的官方网站 fact(n)可以表示为n x fact(n-1),只有n=1时需要特殊处理. 于是,fact(n)用递归的方式写出来就是: def fact(n): if n==1: return 1 return n * fact(n - 1) 上面就是一个递归函数.可以试试: >>> fact(1) 1 >>> fact(5) 120 >>> fact(100) 933262154439441526816992388562667004907

python 函数式编程尾递归优化 day16

函数编程的特征: 1不可变:不用变量保存状态,不修改变量 #非函数式 a = 1 def incr_test1(): global a#一旦更改全局变量后后面再调用a就容易乱 a += 1 return a incr_test1() print(a) def bar(): print('from bar') def foo(): print('from foo') return bar n = foo() n() return可以返回任何数值,包括自己 def hanle(): print('f

Lua function函数,可变参数, 局部函数,尾递归优化

在Lua中,函数是作为"第一类值"(First-Class Value),这表示函数可以存储在变量中,可以通过参数传递给其他函数,或者作为函数的返回值(类比C/C++中的函数指针),这种特性使Lua具有极大的灵活性. Lua对函数式编程提供了良好的支持,可以支持嵌套函数. 另外,Lua既可以调用Lua编写的函数,还可以调用C语言编写的函数(Lua所有的标准库都是C语言写的). 定义一个函数 function hello() print('hello') end hello函数不接收参数