众所周知,函数的递归是任何一门语言的噩梦,java的递归会进行压栈的操作,如果递归次数过多,代码不够完善,会造成栈溢出,scala也是如此。
首先什么是压栈呢?
简单来说,当用到某些参数或者调用函数的时候,会将这些数据压入栈中,在计算机系统中,栈数据为先进后出的,也就是说最先使用的数据会被之后关联的数据压在底下,如果要释放该数据,需要先释放在它之上的数据,也就是后进栈的数据。递归函数会不停的调用自身,进行压栈,直到递归结束才会停止压栈的行为,占据栈内存会随着递归次数的增加而指数型增长,最终导致栈溢出。
那么递归函数是什么?
def loopRecursion(n:Int)={
var res=1
for (i<- 1 to n) res*=i
res
}
如上图所示,是一个用for循环实现10的阶乘的函数,我们同样能够递归函数实现该功能。
def recursion(n:Int):Int={
if(n==1) 1
else recursion(n-1) * n
}
如上图,这种在函数体中进行自身调用行为的函数,我们称之为递归函数,递归并非不能使用,只是这种语法不是正确的使用方式:如果要得到n的阶乘的结果,需要根据n-1的阶乘的结果再乘上一个n,循环往复,直到得到1的阶乘为止,然后再挨个得出各级的结果。由于要用到除自身函数外的参数n,在得出结果、返回函数返回值之前,该函数的参数是一直压在栈中不能出栈的,这样栈中只会不停的增加数据,而不会释放数据,直到到达阙值,容易造成栈溢出。
尾递归函数又是什么呢?它为什么能解决递归的栈溢出问题?
def recursion1(n:Int):Int={
//真正循环的是func函数
def func(n1:Int,res:Int):Int={
if (n==1) res else func(n-1,res*n)
}
func(n,1)
}
尾递归虽然也是递归,但是它并不会由递归而造成栈溢出,因为尾递归函数不需要使用除开本函数外的参数,因此尾递归函数在将值存入下次的调用的初始值后就弹出栈了,栈内存也就不会溢出。
如何实现尾递归函数?
很简单的方式,我们参考for循环,可以对返回值进行存储,在该函数中嵌套一个真真的循环函数,将原函数返回值设为内部循环函数输入参数中的一员,该方法适用绝大部分情况。