基本定义
递归就是方法自己调用自己,每次调用时传入不同的变量。递归有助于解决复杂问题,同时可以让代码变得更简洁。
递归应用
- 各种数学问题,如八皇后、汉诺塔、阶乘、迷宫、球和篮子的问题等
- 各种算法中也会使用递归,如快排、归并排序、二分查找、分治算法等
- 递归是用栈解决问题
阶乘递归调用
public class Test {
public static void main(String[] args){
int res = factorial(3);
System.out.println(res); // 3! = 6
}
// 求阶乘,阶乘公式:n! = 1 * 2 * 3 * ... * n
public static int factorial(int n){
if(n == 1){
return 1;
}else {
return factorial(n - 1) * n;
}
}
}
执行过程如下图:
递归的相关原则
- 执行一个方法时,就创建一个新的受保护的独立空间
- 方法的局部变量是独立的,不会相互影响
- 如果方法中使用的是引用类型变量,如数组,就会共享该引用类型的数据
- 递归必须向退出递归的条件逼近,否则就是无限递归,出现栈溢出
- 当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用就将结果返回给谁
递归实现斐波那契数列
斐波那契数列:1,1,2,3,5,8,13....
规律:从第三个数开始,第n个数等于前两个数的和
代码如下:
public class Test {
public static void main(String[] args){
int res = fibonacci(7);
System.out.println(res);
}
// 斐波那契数列:1,1,2,3,5,8,13...
public static int fibonacci(int n){
// 如果是 1 或者 2 ,返回 1
if(n == 1 || n == 2){
return 1;
}else { // 否则返回前两个数之和
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
}
递归实现猴子吃桃子问题
问题描述:有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个。以后每天猴子都吃其中的一半,然后再多吃一个。当第十天时,想再吃时(但还没吃),发现只有 1 个桃子了。问:最初总共有多少个桃子?
思路分析:倒推回去,即:
- 第 10 天时,桃子总数为 1,那么设第 9 天时一共有 x 个桃子,则 x - (x / 2 + 1) = 1 => x = 2 * (1 + 1) = 4 个桃子,所以第 9 天一共有 4 个桃子
- 第 9 天时,桃子总数为 4,那么设第 8 天时一共有 x 个桃子,则 x - (x / 2 + 1) = 4 => x = 2 * (4 + 1) = 10 个桃子,所以第 8 天一共有 10 个桃子
- 第 8 天时,桃子总数为 10,那么设第 7 天时一共有 x 个桃子,则 x - (x / 2 + 1) = 10 => x = 2 * (10 + 1) = 22 个桃子,所以第 7 天一共有 22 个桃子
- 依次类推,得到以下公式:x = 2 * (n + 1),x表示某一天总共有的桃子数,n表示后一天的桃子总数。
- 带入问题,第 10 天总共有 1 个桃子,即第九天剩余 1 个桃子=>可以得到第九天的桃子总数,第九天的桃子总数又是第八天的剩余桃子,因此,某一天的桃子总数 = 前一天的剩余桃子。依次类推,得到第二天的桃子总数,即可求得第一天的剩余桃子,进而求得第一天的桃子总数。
如下图所示:
代码如下:
public class Test {
public static void main(String[] args){
int res = eatPeach(1);
System.out.println(res); // 1534
}
public static int eatPeach(int day){
// 第 10 天的桃子总数为 1
if(day == 10){
return 1;
}
// 其他天的桃子总数
return 2 * (eat(day + 1) + 1);
}
}
递归实现迷宫问题
public class Test {
public static void main(String[] args){
// 声明一个 8*7 的二维数组
int[][] map = new int[8][7];
// 初始化迷宫地图
// 把第一行和最后一行,第一列和最后一列数组元素都设置为1
for(int i = 0;i < map[0].length;i++){
map[0][i] = 1;
map[7][i] = 1;
}
for(int i = 0;i < map.length;i++){
map[i][0] = 1;
map[i][6] = 1;
}
map[3][1] = 1;
map[3][2] = 1;
// map[2][2] = 1;
// 利用递归寻找迷宫路径
MiGong mg = new MiGong();
mg.findWay(map, 1, 1);
mg.printMap(map);
}
}
class MiGong {
// map 为迷宫地图,实际上为一个二维数组
// i 表示当前老鼠所在行
// j 表示当前老鼠所在列
// 寻路策略为:下->右->上->左
// 在map数组中,元素0:表示无障碍物可以走,1:表示有障碍物,2:表示可以走并且已走过,3表示是死路
public boolean findWay(int[][] map,int i,int j){
if(map[6][5] == 2){ // 到达目标点
return true;
}else {
// 如果map[i][j]元素为0,表示无障碍物,可以走
if(map[i][j] == 0){
map[i][j] = 2; // 首先标记map[i][j]位置可以走
// 按照寻路策略寻找下一个可以走的位置
if(findWay(map, i+1, j)){ // 下
return true;
}else if(findWay(map, i, j+1)){ // 右
return true;
}else if(findWay(map, i-1, j)){ // 上
return true;
}else if(findWay(map, i, j-1)){ // 左
return true;
}else {
map[i][j] = 3; // 将该位置标记为死路
return false;
}
}else{ // 如果map[i][j]不为0,即等于1或2或3,都表示不能走
return false;
}
}
}
// 打印地图,即打印二维数组查看情况
public void printMap(int[][] map){
System.out.println("=======当前迷宫情况======");
for(int i = 0;i < map.length;i++){
for(int j = 0;j < map[i].length;j++){
System.out.print(map[i][j] + " ");
}
System.out.println();
}
}
}
递归实现汉诺塔
问题描述:有三根石柱子,在一根柱子上从下往上按照大小顺序叠放着64片圆盘。现要求把圆盘从下面开始,按照大小顺序重新摆放在另一根柱子上。并且规定:在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
代码如下:
public class Test {
public static void main(String[] args){
HanoiTower ht = new HanoiTower();
ht.move(5, 'A', 'B', 'C');
}
}
class HanoiTower{
// num:圆盘的总数
// a:A柱子
// b:B柱子
// c:C柱子
public void move(int num, char a, char b, char c){
if(num == 1) {
// 只有一个圆盘,则直接从 A 柱子移动到 C 柱子
System.out.println(a + " -> " + c);
} else {
// 多于两个圆盘时,将圆盘分成两种:最大的圆盘和其他圆盘,其中把其他圆盘看成一个整体
// 1.先借助 C 柱子把其他圆盘从 A 柱子移动到 B 柱子
move(num-1, a, c, b);
// 2、再把最大的圆盘移动到 C 柱子
System.out.println(a + " -> " + c);
// 3、再把其他圆盘借助 A 柱子从 B 柱子移动到 C 柱子
move(num - 1, b, a, c);
}
}
}