1、递归的定义
- 函数直接或间接的调用自己
- 使用递归时,必须有明确的结束递归的条件
2、递归的适用场合
- 数据的定义按照递归定义(比如求n!)
- 问题的解法适用于使用递归
- 数据的结构是按递归定义的(比如二叉树)
3、线性递归
也就是普通递归,下一次递归数据的计算要依赖于上一次递归的结果和参数,当数据量较小时执行效率与尾递归几乎没区别,但当数据量较大,迭代次数较多时,由于每次递归都要在内存中开辟一个栈空间,用来存储上次递归的结果和参数,这样的算法将导致严重的内存开销,甚至造成内存溢出,抛出java.lang.StackOverflowError,下面用线性递归实现斐波那契数列。
/**
* 线性递归实现斐波拉契数列
* @param month
*/
public int fblq(int month){
if(month<3){
return 1;
}
return fblq(month-1)+fblq(month-2);//每步递归都严重依赖上一次递归结果,并占用大量的栈区空间,执行效率极低
}
测试上段程序运行时间
@Test
public void test(){
long startTime = System.currentTimeMillis();
System.out.println(fblq(50));
long endTime = System.currentTimeMillis();
System.out.println("程序运行时间:" + (endTime - startTime) + "ms"); //输出程序运行时间
}
程序运行时间:43643ms(非常慢),递归所需栈区空间,以指数级进行增长
4、尾递归
如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个函数是尾递归的。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。
/**
* 斐波那契数列尾递归
*/
public int fblq(int n,int num1,int num2){
if(n==1){
return num1;
}
return fblq(n-1,num2,num1+num2);
}
@Test
public void Test37(){
long startTime = System.currentTimeMillis();
System.out.println(fblq(50,1,1));
long endTime = System.currentTimeMillis();
System.out.println("程序运行时间:" + (endTime - startTime) + "ms"); //输出程序运行时间
}
程序运行时间:1ms,递归以线性关系增长
总结:在能使用尾递归时尽量使用尾递归,这样不仅能节省内存资源,而且执行效率更高,但是相比于普通for循环,递归的效率是较低的,所以再不是非递归不可的环境中,尽量使用普通循环代替递归算法。