[关闭]
@runzhliu 2018-01-28T07:31:06.000000Z 字数 2541 阅读 909

Scala 递归思考(上)

Scala 递归


参考资料
1. https://codeday.me/bug/20171011/84027.html
2. http://blog.csdn.net/jasonding1354/article/details/50525735
3. 《快学 Scala》
4. 《深入理解 Scala》
5. 《图解算法》
6. https://twitter.github.io/scala_school/zh_cn/collections.html#fold

1 递归与函数式编程

在函数式编程中,递归是随处可见的,递归比在命令式编程中更为重要。递归是表达函数的最常用方式,但是也有两个缺点:

栈在这里是一个重要的编程概念,要理解递归,必须理解什么是栈。计算机内部使用被称为调用栈的栈。使用栈虽然很方便,但是也要付出代价,存储详尽信息可能要占用大量的内存,每个函数调用都占用一定内存的话,如果栈很高,意味着计算机存储了大量函数调用的信息,一般情况下,会有两种选择:

嵌套方法是指一个很长的方法里还有其他「辅助」方法,阶乘是比较常见的需要辅助方法的情况。

  1. import scala.annotation.tailrec
  2. def factorial(i: Int): Long = {
  3. @tailrec
  4. def fact(i: Int, accumulator: Int): Long = {
  5. if (i <= 1) accumulator
  6. else fact(i - 1, i * accumulator)
  7. }
  8. fact(i, 1)
  9. }
  10. factorial: (i: Int)Long
  11. (0 to 5) foreach(i => println(factorial(i)))

尾递归表示调用递归函数是该函数中最后一个表达式,该表达式的返回值就是所调用的递归函数的返回值。尾递归非常重要,因为这是把函数优化为循环的最重要的一种递归。循环可以消除潜在栈溢出的风险,同时也因为消除了函数调用开销而提升效率。原生的 JVM 还不支持尾递归优化,但 scalac 可以支持。

Scala 编译器对尾递归做了有限的优化。为了排除在生产环境中出现栈空间的崩溃,可以加一个 tailrec 关键词,编译器会告诉你代码是否正确地实现了尾递归的。如果 fact 不是尾递归,编译器就会抛出错误。

2 递归和尾递归

递归函数的核心是设计好递归表达式,并且确定算法的边界条件。要写出尾部递归并不容易,函数设计需要下苦心。

  1. // 阶乘递归
  2. def factorial(n: BigInt): BigInt = {
  3. if (n <= 1) 1 else n * factorial(n - 1)
  4. }
  5. // 阶乘尾递归
  6. import scala.annotation.tailrec
  7. def factorialTailRecursive(n: BigInt): BigInt = {
  8. @tailrec
  9. def _loop(acc: BigInt, n: BigInt): BigInt =
  10. if (n <= 1) acc else _loop(acc * n, n - 1)
  11. _loop(1, n)
  12. }
  13. // 菲波那切数列递归
  14. def fibonacci(n: Int): Int =
  15. if (n <= 2) 1 else fibonacci(n - 1) + fibonacci(n - 2)
  16. // 菲波那切数列尾递归
  17. def fibonacciTailRecursive(n: Int): Int = {
  18. @tailrec
  19. def _loop(n: Int, acc1: Int, acc2: Int): Int =
  20. if (n <= 2) acc2 else _loop(n - 1, acc2, acc1 + acc2)
  21. _loop(n, 1, 1)
  22. }
  23. // 求列表长度尾递归
  24. def lengthTailRecursive[A](ls: List[A]): Int = {
  25. @tailrec
  26. def lengthR(result: Int, curList: List[A]): Int = curList match {
  27. case Nil => result
  28. case _ :: tail => lengthR(result + 1, tail)
  29. }
  30. lengthR(0, ls)
  31. }

3 Scala 中使用到的尾递归

在较旧的版本的 Scala 集合中有几个方法操作集合,foldLeft 和 reduceLeft 有一个比 foldRight 和 reduceRight 重大的优势,它们是尾递归的,可以在 Scala 的尾递归优化中获益。还记得尾递归必须是一次队规中最后一个运行的操作吗?
在 Scala 2.12.2 中,这个性能优势被取消了,因为都是用到了尾递归。(未考证,待查)

  1. @tailrec
  2. private def foldl[B](start: Int, end: Int, z: B, op: (B, A) => B): B =
  3. if (start == end) z
  4. else foldl(start + 1, end, op(z, this(start)), op)
  5. @tailrec
  6. private def foldr[B](start: Int, end: Int, z: B, op: (A, B) => B): B =
  7. if (start == end) z
  8. else foldr(start, end - 1, op(this(end - 1), z), op)
  9. override /*TraversableLike*/
  10. def foldLeft[B](z: B)(op: (B, A) => B): B =
  11. foldl(0, length, z, op)
  12. override /*IterableLike*/
  13. def foldRight[B](z: B)(op: (A, B) => B): B =
  14. foldr(0, length, z, op)
  15. override /*TraversableLike*/
  16. def reduceLeft[B >: A](op: (B, A) => B): B =
  17. if (length > 0) foldl(1, length, this(0), op) else super.reduceLeft(op)
  18. override /*IterableLike*/
  19. def reduceRight[B >: A](op: (A, B) => B): B =
  20. if (length > 0) foldr(0, length - 1, this(length - 1), op) else super.reduceRight(op)
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注