定义 计算机科学中,递归是一种解决计算问题的方法,其中解决方法取决于同一类问题的更小子集 比如单链表递归遍历的例子:
1 2 3 4 5 6 void f (Node node) { if (node == null ){ return ; } f(node.next); }
说明: 1、自己调用自己,如果说每一个函数对应着一种解决方案,自己调用自己意味着解决方案是一样的(有规律的) 2、每次调用,函数处理的数据会较上次缩减(子集),而且最后会缩减至无需继续递归 3、内层函数调用(子集处理)完成,外层函数才能算调用完成
解题思路 1、确定能否使用递归求解 2、推导出递推关系,即父问题与子问题的关系,以及递归的结束条件
深入到最里层叫做递 从最里层出来叫做归 *在递的过程中,外层函数内的局部变量(以及方法参数)并未消失,归的时候还可以用到
例一- 阶乘 用递归方法求阶乘 ·阶乘的定义 n! = 1·2·3···(n-2)·(n-1)·n,其中n为自然数,当然0! = 1 ·递推关系: f(n) = { 1,n=1 n * f(n-1) ,n>1 }
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Factorial { public static int f (int n) { if (n == 1 ){ return 1 ; } return n * f(n-1 ); } public static void main (String[] args) { int f = f(5 ); System.out.println(f); } }
例二- 反向打印字符串 使用递归反向打印字符串,n为字符在整个字符串str中的索引位置 ·递:n从0开始,每次n+1,一直递到n == str.length() - 1 ·归:从n == str.length() 开始归,自然是逆序的 递推关系: f(n) = { 停止 n = str.length() f(n+1) 0 ≤ n ≤ str.lenth() - 1 }
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class ReversePrintString { public static void f (int n, String str) { if (n == str.length()){ return ; } f(n + 1 , str); System.out.println(str.charAt(n)); } public static void main (String[] args) { f(0 ,"abcd" ); } }
例三- 递归实现二分查找 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class E03BinarySearch { public static int search (int [] a, int target) { return f(a,target,0 ,a.length -1 ); } private static int f (int [] a,int target, int i, int j) { if (i > j){ return -1 ; } int m = (i + j) >>> 1 ; if (target < a[m]){ return f(a, target, i, m-1 ); }else if (a[m] < target){ return f(a, target, i+1 , j); }else { return m; } } }
例四- 递归实现冒泡排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public class E04BubbleSort { public static void sort (int [] a) { bubble1(a,a.length -1 ); } private static void bubble1 (int [] a,int j) { if (j == 0 ){ return ; } for (int i = 0 ; i < j; i++) { if (a[i] > a[i + 1 ]) { int t = a[i]; a[i] = a[i+1 ]; a[i+1 ] = t; } } bubble1(a,j - 1 ); } private static void bubble2 (int [] a,int j) { if (j == 0 ){ return ; } int x = 0 ; for (int i = 0 ; i < j; i++) { if (a[i] > a[i + 1 ]) { int t = a[i]; a[i] = a[i+1 ]; a[i+1 ] = t; x = i; } } bubble2(a,x); } }
例五- 递归实现插入排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public class E05InsertionSort { public static void sort (int [] a) { insertion(a, 1 ); } private static void insertion (int [] a, int low) { if (low == a.length) { return ; } int t = a[low]; int i = low - 1 ; while (i >= 0 && a[i] > t) { a[i+1 ] = a[i]; i--; } if (i+1 != low){ a[i+1 ] = t; } insertion(a, low + 1 ); } private static void insertion2 (int [] a,int low) { if (low == a.length) { return ; } int i = low - 1 ; while (i >= 0 && a[i] > a[i+1 ]){ int t = a[i]; a[i] = a[i+1 ]; a[i+1 ] = t; i--; } insertion2(a,low+1 ); } }
例六- 斐波那契数列 ·之前的例子是每个递归函数只包含一个自身的调用,这称之为 single recursion ·如果每个递归函数包含多个自身调用,称之为 multi recursion(多路递归) 递推关系: f(n) = { 0, n=0 1, n=1 f(n-1) + f(n-2) n>1 }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class E06Fibonacci { public static int f (int n) { if (n == 0 ){ return 0 ; } if (n == 1 ){ return 1 ; } int x = f(n - 1 ); int y = f(n - 2 ); return x + y; } }
递归的次数也符合斐波那契数列,2*f(n+1) - 1 它的时间复杂度为 O(1.618^n) 递归优化(动态规划) 空间换时间,数组存储递归值减少计算量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public static int fibonacci (int n) { int [] cache = new int [n + 1 ]; Arrays.fill(cache,-1 ); cache[0 ] = 0 ; cache[1 ] = 1 ; return f(n, cache); } public static int f (int n, int [] cache) { if (cache[n] != -1 ){ return cache[n]; } int x = f(n - 1 , cache); int y = f(n - 2 , cache); cache[n] = x + y; return cache[n]; }
解决爆栈 尾调用 如果函数的最后一步是调用一个函数,那么称为尾调用,例如
1 2 3 4 5 6 7 8 9 10 11 12 13 funtion a () { return b(); } const c = b(); return c; return b() + 1 ; return b() + x;
尾调用的好处就是可以把嵌套的函数关系变成平级的函数关系 ,外层的函数不需要占着内存等内函数执行完就会被释放掉。 优雅!!!
1 2 3 4 5 6 funtion a () { return b(); } funtion b () { return 10000 ; }
*散了吧,java编译器没优化递归的尾调用/(ㄒoㄒ)/~~
改成循环 返璞归真,还得是直接暴力循环,反正也比爆栈的报错强吧?哈哈哈
递归时间复杂度计算 主定理计算方法 若有递归式:T(n) = aT(n/b) + f(n) 其中:
T(n)是问题的运行时间,n是数据规模 a是子问题的个数 T(n/b)是子问题的运行时间,每个子问题被拆成原问题数据规模的n/b f(n)是除递归外执行的计算 令x = log[b]a,即x = log[子问题缩小倍数]子问题个数 那么可得时间复杂度为: T(n) = { O(n^x) f(n) = O(n^c)并且c < x O(n^x * log[n]) f(n) = O(n^x)并且c = x O(n^c) f(n) = O(n^x)并且c > x }例如: 1、T(n) 2T(n/2) + n^4 (此时a = 2,b = 2,x = log[b]a =1, c = 4再用上面的公式求解时间复杂度) ·此时x = 1 < 4,由后者决定整个时间复杂度O(n^4)
展开求时间复杂度 像T(n) = T(n - 1) + T(1) + n这种找不到abc的就不能用上面的办法,得使用数学方式展开求时间复杂度,类似于数列的递推公式
例如求:递归快排的时间复杂度 快速排序分区没分好的极端情况 T(n) = T(n - 1) + T(1) + n, T(1) = c 即:T(n) = T(n - 1) + c + n 下面为展开过程 T(n) = T(n - 2) + c + (n - 1) + c +n T(n) = T(n - 3) + c + (n - 2) + (n - 1) + c +n …T(n) = T(n-(n-1)) + (n - 1)c + 2+...+n = n^2/2 + 2cn+n/2 - 1 时间复杂度为O(n^2)
不会推导的可以进入外挂 https://www.wolframalpha.com/ ·例一:输入f(n) = f(n-1) + c,f(1) = c
汉诺塔 Tower of Hanol,是一个源于印度的古老传说:大梵天创建世界时做了三根金刚石柱,在一根柱子从下往上按大小顺序摞着64片黄金圆盘,大梵天命令婆罗门把圆盘重新摆放在另一根柱子上,并且规定
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public class E02HanoiTower { static LinkedList<Integer> a =new LinkedList <>(); static LinkedList<Integer> b =new LinkedList <>(); static LinkedList<Integer> c =new LinkedList <>(); static void init (int n) { for (int i = n; i >= 1 ; i--) { a.addLast(i); } } static void move (int n, LinkedList<Integer> a, LinkedList<Integer> b, LinkedList<Integer> c) { if (n == 0 ){ return ; } move(n - 1 , a, c, b); c.addLast(a.removeLast()); print(); move(n - 1 , b, a, c); } private static void print () { System.out.println("----------------------" ); System.out.println(a); System.out.println(b); System.out.println(c); } public static void main (String[] args) { init(3 ); print(); move(3 , a, b, c); print(); } }
时间复杂度估算 T(n) = 2T(n - 1) + c, T(1) = c 用展开求解 T(n) = c(2^n - 1) 即时间复杂度O(2^n)
杨辉三角 有三种方法: 第一种:直接递归 第二种:二维数组记忆法,也有递归 第三种:直接一维数组不需要递归
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 public class E03PascalTriangle { private static int element (int i, int j) { if (j == 0 || i == j){ return 1 ; } return element(i - 1 ,j - 1 ) + element(i - 1 , j); } private static void printSpace (int n , int i) { int sum = (n - 1 - i) * 2 ; for (int j = 0 ; j < sum; j++) { System.out.print(" " ); } } public static void print (int n) { for (int i = 0 ; i < n; i++) { printSpace(n , i); for (int j = 0 ; j <= i; j++) { System.out.printf("%-4d" ,element(i, j)); } System.out.println(); } } private static int element1 (int [][] triangle, int i, int j) { if (triangle[i][j] > 0 ){ return triangle[i][j]; } if (j == 0 || i == j){ triangle[i][j] = 1 ; return 1 ; } triangle[i][j] = element1(triangle, i - 1 ,j - 1 ) + element1(triangle, i - 1 , j); return triangle[i][j]; } public static void print1 (int n) { int [][] triangle = new int [n][]; for (int i = 0 ; i < n; i++) { triangle[i] = new int [i+1 ]; printSpace(n , i); for (int j = 0 ; j <= i; j++) { System.out.printf("%-4d" ,element1(triangle, i, j)); } System.out.println(); } } private static void creatRow (int [] row, int i) { if (i == 0 ){ row[0 ] = 1 ; return ; } for (int j = i; j > 0 ; j--) { row[j] = row[j] + row[j - 1 ]; } } public static void print2 (int n) { int [] row = new int [n]; for (int i = 0 ; i < n; i++) { creatRow(row,i); printSpace(n , i); for (int j = 0 ; j <= i; j++) { System.out.printf("%-4d" , row[j]); } System.out.println(); } } }