递归
1. 递归是什么
递归就是方法自己调用自己,每次调用时传入不同的变量。
递归有助于编程者解决复杂的问题,同时可以让代码变得简洁。
1.1 递归的运行说明
根据JVM的运行规则:
- 当程序执行到一个方法时,就会在栈空间中开辟一块新的栈帧空间;
- 每个空间中都是互相独立的,也就是说数据(局部变量)不会互通
运行解释:
- 当进入到main方法时,会在栈中开辟一块main方法栈帧,然后依次执行语句,
- 执行到test(4)方法时,会进入到test方法体,从而开辟新的栈帧…然后以此类推
- 当方法运行完之后,栈中的内存空间会自动回收,从图中可以看到,最先回收的是n=2的栈帧,然后n=3…最后回到main方法
- 所以,该程序的输出顺序为n=2,n=3,n=4
1.2 递归的应用场景
- 各种数学问题如: 8皇后问题 , 汉诺塔, 阶乘问题, 迷宫问题, 球和篮子的问题(google编程大赛)
- 各种算法中也会使用到递归,比如快排,归并排序,二分查找,分治算法等.
- 将用栈解决的问题–>递归代码比较简洁
1.3 递归必须遵守的规则
1、每执行一个方法,就创建一个新的受保护的独立空间(栈空间) — 默认遵守了(main)
2、方法的局部变量是独立的,不会相互影响, 比如n变量
3、如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据。(因为引用类型变量存放在堆中,堆是共享内存空间)
4、递归必须向退出递归的条件逼近
,否则就是无限递归,出现StackOverflowError,栈溢出)
5、当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕。
2. 递归回溯算法(迷宫问题)
根据递归的实际应用场景,这里选用迷宫问题来描述递归回溯算法
2.1 设计思路
- 使用二维数组来模拟迷宫,并手动加上挡板(围墙用1表示,可走空间用0表示,能走通的路线用2表示,走过但是无法通行的路线用3表示)
- 指定起点和终点,判断走到终点时跳出递归
- 设置行走规则:下=>右=>上=>左
2.2 代码实现
生成迷宫
private static int[][] createMiGong() {
int[][] miGong = new int[8][7];
for (int i = 0; i < miGong.length; i++) {
for (int j = 0; j < miGong[i].length; j++) {
miGong[0][j]
= miGong[miGong.length - 1][j]
= miGong[i][0]
= miGong[i][miGong[i].length - 1]
= miGong[3][1]
= miGong[3][2] = 1;
System.out.print(miGong[i][j] + "\t");
}
System.out.println();
}
return miGong;
}
1 1 1 1 1 1 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 1 1 0 0 0 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 1 1 1 1 1 1
走迷宫
/**
* 走迷宫
*/
public static boolean miGongHuiSu(int[][] mig, int i, int j) {
//到达出口直接返回true,只要返回值为true就开始回溯了
if (mig[6][5] == 2) {
return true;
} else {
//还没走过
if (mig[i][j] == 0) {
//假设走通了
mig[i][j] = 2;
//往下走如果到达终点,那么就返回true
if (miGongHuiSu(mig, i + 1, j)) {
return true;
} else if (miGongHuiSu(mig, i, j + 1)) {
//往右走
return true;
} else if (miGongHuiSu(mig, i - 1, j)) {
//往上走
return true;
} else if (miGongHuiSu(mig, i, j - 1)) {
//往左走
return true;
} else {
//上下左右都走不通走不通
mig[i][j] = 3;
return false;
}
} else {
//当mig[i][j]!=0时就返回false,说明当前的位置要么是墙,要么走过了,要么走不通
return false;
}
}
}
2.3 完整代码
public class Demo14 {
public static void main(String[] args) {
//8行7列迷宫 加上挡板 ,1为挡板
int[][] miGong = createMiGong();
System.out.println("开始走迷宫啦~~");
miGongHuiSu(miGong, 1, 1);
show(miGong);
}
/**
* 生成迷宫
* @return
*/
private static int[][] createMiGong() {
int[][] miGong = new int[8][7];
for (int i = 0; i < miGong.length; i++) {
for (int j = 0; j < miGong[i].length; j++) {
miGong[0][j]
= miGong[miGong.length - 1][j]
= miGong[i][0]
= miGong[i][miGong[i].length - 1]
= miGong[3][1]
= miGong[3][2] = 1;
System.out.print(miGong[i][j] + "\t");
}
System.out.println();
}
return miGong;
}
/**
* 展示迷宫
*/
public static void show(int[][] mig) {
for (int[] ints : mig) {
for (int anInt : ints) {
System.out.print(anInt + "\t");
}
System.out.println();
}
}
/**
* 走迷宫
*/
public static boolean miGongHuiSu(int[][] mig, int i, int j) {
//到达出口直接返回true,只要返回值为true就开始回溯了
if (mig[6][5] == 2) {
return true;
} else {
//还没走过
if (mig[i][j] == 0) {
//假设走通了
mig[i][j] = 2;
//往下走
if (miGongHuiSu(mig, i + 1, j)) {
// 如果能到达终点,那么就返回true,否则会因为返回的是false到下面的else if当中
return true;
} else if (miGongHuiSu(mig, i, j + 1)) {
//往右走
return true;
} else if (miGongHuiSu(mig, i - 1, j)) {
//往上走
return true;
} else if (miGongHuiSu(mig, i, j - 1)) {
//往左走
return true;
} else {
//上下左右都走不通走不通
mig[i][j] = 3;
return false;
}
} else {
//当mig[i][j]!=0时就返回false,说明当前的位置要么是墙,要么走过了,要么走不通
return false;
}
}
}
}
输出结果
如果不理解建议多debug看
1 1 1 1 1 1 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 1 1 0 0 0 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 1 1 1 1 1 1
开始走迷宫啦~~
1 1 1 1 1 1 1
1 2 0 0 0 0 1
1 2 2 2 0 0 1
1 1 1 2 0 0 1
1 0 0 2 0 0 1
1 0 0 2 0 0 1
1 0 0 2 2 2 1
1 1 1 1 1 1 1
2.4 修改迷宫行走策略
上=>右=>下=>左
这段代码贴到完整代码里面并加上相关调用即可使用
/**
* 修改迷宫行走策略: 上-》右-》下-》左
* 并且加上步长计算,可以用来比较最短路径,
*/
public static boolean miGongHuiSu2(int[][] mig, int i, int j, int step) {
//到达出口
if (mig[6][5] == 2) {
System.out.println(step);
return true;
} else {
//还没走过
if (mig[i][j] == 0) {
step++;
//假设走通了
mig[i][j] = 2;
//往上走
if (miGongHuiSu2(mig, i - 1, j, step)) {
return true;
} else if (miGongHuiSu2(mig, i, j + 1, step)) {
//往右走
return true;
} else if (miGongHuiSu2(mig, i + 1, j, step)) {
//往下走
return true;
} else if (miGongHuiSu2(mig, i, j - 1, step)) {
//往左走
return true;
} else {
step--;
//上下左右都走不通走不通
mig[i][j] = 3;
return false;
}
} else {
step--;
//mig[i][j]!=0,eg:1,2,3
return false;
}
}
}
输出结果
修改策略之后走迷宫~~
10 // 步长
1 1 1 1 1 1 1
1 2 2 2 2 2 1
1 0 0 0 0 2 1
1 1 1 0 0 2 1
1 0 0 0 0 2 1
1 0 0 0 0 2 1
1 0 0 0 0 2 1
1 1 1 1 1 1 1
3. 八皇后问题
3.1 八皇后问题介绍
1、八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,
2、人话:任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
3.2 设计思路
操作步骤:
- 第一个皇后先放第一行第一列
- 第二个皇后放在第二行第一列、然后判断是否OK[是否冲突], 如果不OK,继续放在第二列、第三列… 直到找到一个合适的位置
- 继续第三个皇后,还是第一列、第二列……直到第8个皇后也能放在一个不冲突的位置(在这个过程中可能发生回退到步骤二调整之前放好的皇后),等开始找第九个皇后的时候算找到了一个正确解
- 当得到一个正确解时开始回溯,栈会回退到上一个栈,后移上一个栈中皇后的位置,甚至会回退多个栈,调整多个皇后的位置,就是按照步骤2-3的思路进行递归回溯。
- 反复1-4步骤就能得到所有的解
说明
:理论上应该创建一个二维数组来表示棋盘,但是实际上可以通过算法,用一个一维数组即可解决问题.arr[8] = {0 , 4, 7, 5, 2, 6, 1, 3}
arr[i]
:表示第i+1行,即第i+1个皇后;
arr[i] = val
:表示第i+1个皇后,放在第i+1行的第val+1列
3.3 代码实现
public class Demo15 {
private final static Integer MAX = 8;
/**
* 这里使用一维数组来存放解法
*/
private final static Integer[] ARRAY = new Integer[MAX];
/**
* 记录解法个数
*/
private static Integer count = 0;
/**
* 记录判断次数
*/
private static Integer bif = 0;
public static void main(String[] args) {
check(0);
System.out.printf("一共有 %d 种解法\n", count);
System.out.printf("一共判断了%d次", bif);
}
/**
* 判断皇后是否冲突
* 3.不用判断是否同一行,因为在设计的时候,通过数组角标代表行,N每次都在递增(不会出现在同一行的结果)
*
* @param n 第几个皇后,或者是第几行皇后。
*/
private static boolean judge(int n) {
bif++;
for (int i = 0; i < n; i++) {
/*
* 1、ARRAY[i].equals(ARRAY[n]) 表示判断 第n个皇后是否和前面的n-1个皇后在同一列
* 2、Math.abs(n-i) == Math.abs(ARRAY[n] - ARRAY[i])
* 表示判断第n个皇后是否和第i皇后是否在同一斜线(也就是求斜率),
* 因为Math.abs()求的是绝对值,所以只需要考虑(y2-y1)/(x2-x1) = 1这种情况
*/
if (ARRAY[i].equals(ARRAY[n]) || Math.abs(n - i) == Math.abs(ARRAY[n] - ARRAY[i])) {
return false;
}
}
return true;
}
/**
* 放置第n个皇后
*/
private static void check(int n) {
// 表示判断到第九个皇后,说明八皇后都没有冲突,可以直接打印结果了
if (n == MAX) {
print();
return;
}
/*
* 每次调用check函数都从第一列开始放置,ARRAY[n]代表第n+1行
*/
for (int i = 0; i < MAX; i++) {
ARRAY[n] = i;
// 这里说明没冲突,可以递归调用函数来判断下一个皇后的位置
if (judge(n)) {
check(n + 1);
}
// 有冲突就会i++进入for的下一个循环,这里实现了回溯
}
}
/**
* 输出皇后的摆放结果
*/
private static void print() {
count++;
for (Integer integer : ARRAY) {
System.out.print(integer + " ");
}
System.out.println();
}
}
上一篇 | 总目录 | 下一篇 |
---|---|---|
三、栈 | 数据结构篇(Java)目录 | 五、排序(上:冒泡、简单选择) |