Attention :本文的示例代码使用的Kotlin代码
递归算法里面的瑰宝
了解尾递归之前,先了解一下尾调用
在计算机学里,尾调用是指一个函数里的最后一个动作是返回一个函数的调用结果的情形,即最后一步新调用的返回值直接被当前函数的返回结果。此时,该尾部调用位置被称为尾位置(摘自百度百科)
例如:
fun foo(data) {
a(data)
return b(data)
}
这里的a(data)和b(data)都是函数调用,但是b(data)是函数返回前的最后运行的东西,所以也是所谓的尾位置。
例如:
fun foo1(data) {
return a(data) + 1
}
fun foo2(data) {
var ret = a(data)
return ret
}
fun foo3(data) {
var ret = a(data)
return (ret === 0) ? 1 : ret
}
这种就不是尾调用,对于foo1,最后一个动作是+1操作,并非是直接函数调用;对于foo3,是经过计算返回的结果,也不是尾调用。,foo2也不是尾调用
尾调用很重要的特性就是它可以不在调用栈上面添加一个新的堆栈帧,而是更新它。
接下来说一下什么是尾递归:
若一个函数在尾位置调用本身(或是一个尾调用本身的其他函数等),则称这种情况为尾递归,是递归的一种特殊情形。而形式上只要是最后一个return语句返回的是一个完整函数,它就是尾递归。这里注意:尾调用不一定是递归调用,但是尾递归一定是尾调用。
接下来通过斐波那契数列和阶乘来进一步理解尾递归的含义
斐波那契数列
常规的斐波那契数列算法可能是这样的:
fun fib(n: Int): Int {
if (n <= 2) {
return 1
}
return fib(n - 1) + fib(n - 2)
}
上面的这种递归计算最终的return操作是加法操作。所以不是尾递归。
如果用尾递归就是这样的:
/**
* 计算第n位斐波那契数列的值
*
* @param n 波纳挈数列数的第n位
* @param acc1 斐波纳挈数列的第n个数
* @param acc2 斐波纳挈数列的第n数与第n+1数的和
* @return 返回斐波那契数列值
*/
fun tailfib(n: Int, acc1: Int, acc2: Int): Int {
return if (n < 2) {
acc1
} else tailfib(n - 1, acc2, acc1 + acc2)
}
比如我们想计算第100位斐波那契数列的值,只需要fib(100,1,1)即可。
看一下测试效果,测试程序如下:
@JvmStatic
fun main(args: Array<String>) {
val thread = Thread {
var start: Long
var end: Long
start = System.currentTimeMillis()
println(fib(100))
end = System.currentTimeMillis()
println(end - start)
start = System.currentTimeMillis()
println(tailfib(20,1,1))
end = System.currentTimeMillis()
println(end - start)
}
thread.start()
}
计算结果如下;
6765
2
6765
0
阶乘
常规的计算阶乘的方法可能是这样的:
fun fac(n: BigInteger): BigInteger {
return if (n == BigInteger.ONE) {
BigInteger.ONE
} else fac(n - BigInteger.ONE) * n
}
需要注意的是,不要使用Int,因为Int取值范围小于BigInteger ,很容易爆栈,当然你是用BigInteger ,数值太大一样爆栈。
尾递归的算法是这样的:
tailrec fun tailfac(n: BigInteger, sum: BigInteger): BigInteger {
return if (n ==BigInteger.ONE) {
sum
} else tailfac(n -BigInteger.ONE, n * sum)
}
测试一下效率,测试程序如下:
20922789888000
1
20922789888000
0
读到这个地方,是不是感到尾递归的效率之高,废话不说了,点赞和喜欢吧。
网友评论