文章目录
1. 概念
递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,递归相当于数学上的 “数学归纳法”, 有一个起始条件,然后有一个递推公式。它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能将原本的问题分解为更小的子问题,这是使用递归的关键。
2. 递归适用场景
- 一个大问题可以拆分成若干个子问题;
- 原问题和子问题除了数据规模不一样,求解思路完全一样;
- 存在递归终止条件。
3. 递归执行过程分析
举例:求 N 的阶乘
转化为递归问题:
- 大问题可以拆分为小问题:N 的阶乘可以拆分成 N * ( N - 1 ) 的阶乘;
- 原问题和子问题除数据规模不同求解思路完全一样:比如 5 的阶乘和 4 的阶乘求解方法相同但是数据规模不同,5 的阶乘是 5×4×3×2×1,4 的阶乘是4×3×2×1;
- 存在终止条件:最终就是求 1 的阶乘。
用数学归纳法来看:
起始条件: N = 1 的时候, N! 为 1。这个起始条件相当于递归的结束条件。
递归公式: 求 N! , 可以把问题转换成 N! => N * (N-1)。
代码示例:
/**
* 递归解决n阶乘的问题
*/
public class Factor {
public static void main(String[] args) {
int n = 5;
int result = factor(n);
System.out.println("result = " + result);
}
public static int factor(int n) {
if (n == 1) {
return 1;
}
return n * factor(n - 1); // factor 调用函数自身
}
}
执行过程图:
ps:关于 “调用栈” :方法调用的时候, 会有一个 “栈” 这样的内存空间描述当前的调用关系。 称为调用栈。每一次的方法调用就称为一个 “栈帧”,每个栈帧中包含了这次调用的参数是哪些, 返回到哪里继续执行等信息。
4. 递归练习
4.1 按顺序打印一个数字的每一位
/**
* 按顺序打印一个数字的每一位(例如 1234 打印出 1 2 3 4)
*/
public class Demo1 {
public static void main(String[] args) {
int num = 1234;
System.out.print("打印结果:" );
print(num);
}
public static void print(int num) {
// 终止条件,找到最高位
if(num > 10) {
print(num / 10 );
}
// 保留其他位
System.out.print(num % 10 + ",");
}
}
执行过程图:
4.2 递归求 1 + 2 + 3 + … + 10
/**
* 递归求 1 + 2 + 3 + ... + 10
*/
public class Demo2 {
public static void main(String[] args) {
int num = 10;
System.out.printf("结果是:" + sum(num));
}
public static int sum (int num) {
if(num == 1) {
return 1;
}
return num + sum(num - 1);
}
}
4.3 写一个递归方法,输入一个非负整数,返回组成它的数字之和
例如,输入 1729, 则应该返回1+7+2+9,它的和是19.
package demo.recursion;
/**
* 输入一个非负整数,返回组成它的数字之和. 例如,输入 1729, 则应该返回1+7+2+9,它的和是19.
*/
public class Demo3 {
public static void main(String[] args) {
int num = 1729;
System.out.println("结果是:" + sum(num));
}
public static int sum (int num) {
if(num < 10) {
return num;
}
return (num % 10) + sum(num / 10);
}
}
4.4 求斐波那契数列的第 N 项
package demo.recursion;
/**
* 求斐波那契数列的第 N 项
*/
public class Fibonacci {
public static void main(String[] args) {
int n = 10;
System.out.println(fibonacci(n));
}
public static int fibonacci(int n) {
if(n == 1 || n == 2) {
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
4.5 求解汉诺塔问题
汉诺塔问题是一个经典的问题。汉诺塔(Hanoi Tower),又称河内塔,源于印度一个古老传说。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,任何时候,在小圆盘上都不能放大圆盘,且在三根柱子之间一次只能移动一个圆盘。问应该如何操作?
变为数学问题:
下面有三个柱子,第一个A柱子上面放有圆盘,圆盘从上到下按大小排列,要求将A柱子上的圆盘移到C柱子上,并且一次只能移动一个圆盘,小圆盘上不能放大圆盘,问圆盘如何移动,移动的次数。
不难看出,我们可以利用中间的B柱子短暂的存放小圆盘,直到大圆盘在C柱子的下面。
n = 1, A->C
n = 2, x1从A->B, x2从A->C, x1从B->C 三步
n = 3, x1从A->C, x2从A->B, x1从C->B, x3从A->C, x1从B->A, x2从B->C,x1从A->C 七步
规律: n个圆盘移动的次数 2的n次方减1
实现步骤:
第一步把A上n-1个盘子通过借助辅助塔(C塔)移到了B上;
第二步把最大的一个盘子由A移到C上去;
第三步把B上n-1个盘子通过借助辅助塔(A塔)移到了C上。
代码实现:
package demo.recursion;
import javax.jws.soap.SOAPBinding;
/**
* 求解汉诺塔问题
*/
public class Demo4 {
public static void main(String[] args) {
char A = 'A';
char B = 'B';
char C = 'C';
int n = 3;
hanoiTower(n, A, B, C);
System.out.println("一共移动了" + count + "次");
}
static int count = 0; // 移动次数
public static void move(int n, char N, char M) {
System.out.println("第" + (count++) + "次移动:" + n + "号圆盘从" + N + "到" + M);
}
public static void hanoiTower(int n, char A, char B, char C) {
if (n == 1) {
move(1, A, C);
} else {
hanoiTower(n - 1, A, C, B); // C 为辅助塔
move(n, A, C);
hanoiTower(n - 1, B, A, C); // A 为辅助塔
}
}
}
运行结果:
4.6 青蛙跳台阶问题
一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
package demo.recursion;
/**
* 一只青蛙一次可以跳上 1 级台阶,也可以跳上2 级。求该青蛙跳上一个n 级的台阶总共有多少种跳法
*/
public class Demo5 {
public static void main(String[] args) {
int n = 7;
System.out.println("结果是:" + frog(n));
}
public static int frog(int n) {
if(n == 1) {
return 1;
}
if(n == 2) {
return 2;
}
return frog(n - 1) + frog(n - 2);
}
}
ps: 在这道题中有大量重复的递归计算,例如 f(n)f(n) 和 f(n - 1)f(n−1) 两者向下递归都需要计算 f(n - 2)f(n−2) 的值。所以使用别的方法更为合适比如动态规划。