什么叫递归?举个例子,咱们排队,想知道本身排在第几个,那么咱们能够问前面的那我的,前面的人继续问前面,直到问到第一我的,这就是传递的过程。而后再从第一我的回来,这就是归(回溯)的过程。传递过去再回归回来,这就是递归。第一我的就是咱们所说的递归出口,也就是说到哪一个点应该回归了,若是没有出口,那么就会死循环了栈溢出。数组
在代码中简单来讲就是本身调用本身。拿到本身的结果再做为入参调用本身。缓存
好比咱们求阶乘:函数
5的阶乘: 5*4*3*2*1测试
咱们用递归来写,那么出口就是n=1的时候:代码以下优化
/** * 阶乘 * @param n * @return */ public static int factorial(int n) { if (n <= 1) { return 1; } return n * factorial(n - 1); }
调用过程以下:spa
走到1的时候最后再一次回来计算结果,最后返回。这就是递归。3d
再来看一个比较经典的例子,裴波那契数列,1,1,2,3,5,8,13,21....后一项永远是前两项之和,code
用递归来实现:blog
/** * 裴波那契数列 递归 * @param n * @return */ public static int recursion(int n) { if (n <= 2) { return n == 0 ? 0 : 1; } return recursion(n - 1) + recursion(n - 2); }
是否是感受递归写起来代码不多。看起来也干净。可是当执行上面裴波那契数列的代码,好比设置一个45,你会发现好久都计算不出来,为何呢?递归
递归一个递和一个归的过程无疑是增长了时间复杂度的,阶乘那个的时间复杂度还好O(2n)也就是O(n),可是裴波那契数列就是O(2^n),由于每一个值进去都有两个分支,就像1生2,2生4,4生8这种了,因此是2^n。就像这样:
从这个图里面还看出来几乎全部的值都会被屡次计算,在每个分支都去计算屡次。
因此咱们须要来优化咱们上面的代码:
1.非递归实现,按理来讲,每个递归均可以用非递归来实现
裴波那契数列非递归实现,时间复杂度O(n)
/** * 裴波那契数列循环实现 * @param n * @return */ public static int cycle(int n) { if (n <= 2) { return n <= 0 ? 0 : 1; } int f1 = 1; //n-1 int f2 = 1; //n-2 int fn = 0; // n for (int i =3; i<=n;i++) { fn = f1 + f2; f2 = f1; f1 = fn ; } return fn ; }
2.保存中间结果,刚才也说到了,裴波那契数列那个递归的实现,会让不少值屡次计算,声明一个数组作缓存,把中间结果放入数组中存起来,计算的时候先去数组中取,若是有就不计算了
public static int cache(int n) { int data[] = new int[n]; // 用数组来作缓存 return fac(n, data); } public static int fac(int n, int[] data) { if (n <= 2) return 1; //递归的终止条件 if (data[n-1] > 0) { //数组中有值就直接取出来返回,不用再去计算 return data[n-1]; } int res = fac(n - 1, data) + fac(n - 2, data); data[n-1] = res; //算出来值放到数组中 return res; }
3.尾递归。为何有些递归会栈溢出,由于每一个方法调用都会建立新的栈。若是没有控制好递归的深度,确定是会栈溢出的。
尾递归就是,函数调用在末尾,且末尾只能有函数调用,不能有其余操做。这样编译器在编译代码的时候若是发现末尾只有函数调用,不会建立新的栈。也就说最后咱们的方法返回就是返回的咱们的最终结果。如何才能作到这样呢,其实就须要将前面的计算结果传递到最后,递归出口便是结束,没有回溯的过程。
阶乘的尾递归实现:
/** * 阶乘尾递归 * @param n * @return */ public static int taiFactorial(int n, int result) { if (n <= 1) { return result; //最后返回的便是最终结果 } return taiFactorial(n - 1, n * result);//结果往下传 }
裴波那契数列尾递归:
/** * 尾递归 裴波那契 * @param pre 上上一次运算出来的结果 * @param result 上一次运算出来结果 * @param n * @return */ public static int tailRecursion(int pre, int result, int n) { if (n <= 2) { return result; } //对于下一次调用来讲 前一次结果 pre + result 前前一次result return tailRecursion(result, pre + result, n - 1); }
测试下裴波那契数列普通递归和尾递归执行效率:
public static void main(String[] args){ System.out.println("---裴波那契数列普通递归---"); long time1 = System.currentTimeMillis(); int result = recursion(45); System.out.println(result); long time2 = System.currentTimeMillis(); System.out.println(time2-time1); System.out.println("---裴波那契数列尾递归---"); result = tailRecursion(1,1,45); System.out.println(result); long time3 = System.currentTimeMillis(); System.out.println(time3-time2); }
因此咱们对于递归的使用必定要慎重!!!