递归思想
1.计算连续整数的和
循环解法
// 从 from 一直加到 to
// 循环解法:
def sum1(from: BigInt, to: BigInt): BigInt = {
var num = from
var sum: BigInt = 0
while (num <= to) {
sum += num
num += 1
}
sum
}
递归解法
def sum(from : BigInt , to : BigInt):BigInt ={
if (from == to) from
else from + sum(from+1 , to)
}
说明:
- 递归算法,一般来说比较简单,符合人们的思维方式,但是由于需要保持调用堆栈,效率比较低,在调用次数较多时,更经常耗尽内存。
- 因此,程序员们经常用递归实现最初的版本,然后对它进行优化,改写为循环以提高性能。尾递归于是进入了人们的眼帘。
- 上面的这个递归调用不是尾递归, 因为有一次额外的加法调用, 这导致每一次递归调用留在堆栈中的数据都必须保留. 所以很容易出现
StackOverflowError
.
2.尾递归
- 尾递归就是指递归调用是函数的最后一个语句, 而且结果被直接返回. 基本上一个栈就够用的,不断地刷新这个栈,重复使用,基本上不会出现栈溢出.
- 尾递归一般情况下,参数列表总是比正常递归多一个
def sum1(from :BigInt,to:BigInt,sum :BigInt):BigInt={
if (from == to ) from + sum
else sum1 (from +1, to,sum+from)
}
说明:
- 以上的调用,由于调用结果都是直接返回,所以之前的递归调用留在堆栈中的数据可以丢弃,只需要保留最后一次的数据,这就是尾递归容易优化的原因所在
- 而它的秘密武器就是上面的
sum
,它是一个累加器(accumulator,习惯上翻译为累加器,其实不一定非是“加”,任何形式的积聚都可以),用来积累之前调用的结果,这样之前调用的数据就可以被丢弃了。 - scala 已经对尾递归做了优化, 可以放心使用.(TCO: tail call optimization)
- 如果自己不确定是否为尾递归, 可以加注解:
scala.annotation.tailrec
. Scala 可以判断是否为尾递归,如果不是则会报错. - 将一个常规递归改写成尾递归并不难。我们可以做预计算,将部分结果放置在参数中,而不是在递归调用方法返回的时候做乘法操作。
斐波那契数列
正常递归
def fibonacci(n: Int): Int = {
if (n <= 2) 1
// 不是直接返回函数调用, 而是有 + 运算, 所以不是尾递归
else fibonacci(n - 1) + fibonacci(n - 2)
}
尾递归
def fibonacci1(n:Int ,a1 : Int,a2:Int): Int={
/*
n表示将来要计算第 n 项. 可以理解成需要计算 n 次
a2 就是我们要求的值
假设计算第 5 项:
第 1 次: f(5, 0, 1) 计算出来第 1 项
第 2 次: f(4, 1, 1) 计算出来第 2 项
第 3 次: f(3, 1, 2) 计算出来第 3 项
第 4 次: f(2, 2, 3) 计算出来第 4 项
第 5 次: f(1, 3, 5) ...
*/
if(n=1) a2
else fibonacci1(n-1,a2,a1+a2)
}
阶层
def factorical(n:Int,tmp:Long):Long={
if (n==1) tmp
else factorical(n-1,tmp*n)
}
字符串反转
def reverse(str:String,tmp:String):String={
if (str.length ==0) tmp
else reverse(str.tail,str.head+tmp)
}
计算最大值
def max(arr:Array[Int],m:Int):Int={
if(arr.length==0) return m
if(arr.head>m) max(arr.tail,arr.head)
else max(arr.tail,m)
}
尾递归的局限
-
由于 JVM 的限制,对尾递归深层次的优化比较困难,因此,Scala 对尾递归的优化很有限,它只能优化形式上非常严格的尾递归。
-
如果尾递归不是直接调用,而是通过函数值。 不能优化
val foo = fac _
def fac(n:Int,tmp:Long):Long ={
if (n==1) tmp
else factorical(n-1,tmp*n) //不是尾递归
}
- 间接递归不会被优化 间接递归(有人叫做蹦床调用 trampoline call),指不是直接调用自身,而是通过其他的函数最终调用自身的递归
def foo(n:Int):Int={
if(n==0) 0
else bar(n)
}
//间接调用递归不能被优化
def bar(n:Int):Int={
foo(n-1)
}