递归的概念
一个方法在执行过程中调用自身, 就称为 “递归”.
递归相当于数学上的 “数学归纳法”, 有一个起始条件, 然后有一个递推公式.
例如, 我们求 N!
起始条件: N = 1 的时候, N! 为 1. 这个起始条件相当于递归的结束条件.
递归公式: 求 N! , 直接不好求, 可以把问题转换成 N! => N * (N-1)!
递归的必要条件:
- 将原问题划分成其子问题,注意:子问题必须要与原问题的解法相同
- 递归出口
递归求 N 的阶乘
public static void main(String[] args) {
int n = 5;
int ret = factor(n);
System.out.println("ret = " + ret);
}
public static int factor(int n) {
if (n == 1) {
return 1;
}
return n * factor(n - 1); // factor 调用函数自身
}
// 执行结果
ret = 120
递归执行过程分析
递归的程序的执行过程不太容易理解, 要想理解清楚递归, 必须先理解清楚 “方法的执行过程”, 尤其是 “方法执行结束之后, 回到调用位置继续往下执行”。
public static void main(String[] args) {
int n = 5;
int ret = factor(n);
System.out.println("ret = " + ret);
}
public static int factor(int n) {
System.out.println("函数开始, n = " + n);
if (n == 1) {
System.out.println("函数结束, n = 1 ret = 1");
return 1;
}
int ret = n * factor(n - 1);
System.out.println("函数结束, n = " + n + " ret = " + ret);
return ret;
}
// 执行结果
函数开始, n = 5
函数开始, n = 4
函数开始, n = 3
函数开始, n = 2
函数开始, n = 1
函数结束, n = 1 ret = 1
函数结束, n = 2 ret = 2
函数结束, n = 3 ret = 6
函数结束, n = 4 ret = 24
函数结束, n = 5 ret = 120
ret = 120
什么时候使用递归,如何写出递归函数
一个大问题能够拆分成多个子问题。
拆分后的问题解决思路和原来完全相同。
存在递归的终止条件。
从宏观的角度来思考问题,找到拆分的条件。
一开始很难写出递归函数,可以通过对递归函数的调试(单步执行)来感受递归函数。
汉诺塔问题
汉诺塔(Tower of Hanoi),又称河内塔,是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
假设有A,B,C三个柱子,圆盘全都在A上,最终要借助B在小圆盘不放在大圆盘的情况下把圆盘放到C上。
这个问题的难点在于如何拆分,可以先从简单的情况入手,当只有一个盘子时,直接就是从A移动到C;当两个盘子时,先把第一个盘子从A移动到B,再把第二个盘子从A移动到C,最后再把第一个盘次子从B移动到C。
当盘子变成三个的时候情况就开始复杂起来了
将1盘子从A移动到C
将2盘子从A移动到B (将A上的n-1个盘子借助C移动到B上)
将1盘子从C移动到B
将3盘子从A移动到C (将n盘子从A移动到C上)
将1盘子从B移动到A
将2盘子从B移动到C (将B上的n-1个盘子借助A移动到C上)
将1盘子从A移动到C
这个问题的关键就是将整个流程抽象出以上的三步,不管有多少个盘子,思路都是这样
public static void 汉诺塔(int n,char A,char B,char C){
if (n == 1){
move(1,A,C); //终止条件
return;
}
汉诺塔(n-1,A,C,B); //将A上的n-1个盘子借助C移动到B上
move(n,A,C); //将n盘子从A移动到C上
汉诺塔(n-1,B,A,C); //将B上的n-1个盘子借助A移动到C上
}
private static void move(int n , char a, char b) {
System.out.println("将第" + n + "个盘子从" + a + "->" + b+" ");
} //将一个圆盘移动到另一个圆盘