JAVA数据结构 07.递归 迷宫问题 八皇后问题
1. 递归介绍和解决思路
递归:简单来说,递归就是方法自己调用自己,每次调用时传入不同的变量.
递归有助于编程者解决复杂的问题(只需要关注每一次递归解决的问题),同时可以让代码变得简洁.
递归调用规则:
- 当程序执行到一个方法时,就会开辟一个独立的空间(存储再栈中);
- 每个空间的数据(局部变量)都是独立的;
- 递归必须向退出递归的条件逼近,否则就是无限递归,产生栈溢出错误
- 当一个方法执行完毕,或者遇到return时,就会返回到调用该方法的地方继续执行下面的程序.
递归解决问题的思路: 注重三个方面
- 找到整个递归的终止条件:递归应该在什么时候结束?
- 找到返回值:应该给上一级递归返回什么信息?
- 找单次递归的任务:本次递归应该做什么?
2. 递归案例:
2.1 迷宫回溯问题
场景:设置迷宫的墙壁和出口入口,寻找逃出迷宫的路径
说明:
/**
* 说明:
* 1. map 表示地图
* 2. i,j 表示从地图的哪个位置开始出发,起始位置(1,1)
* 3. 如果小球能到map[6][5]位置,则说明通路找到
* 4. 当map[i][j] 为0 表示当前位置还没有走过;为1 表示墙;为2 表示通路可以走并且已经走过;为3 表示该点走过并且上下左右四个位置都走不通
* 5. 策略: 方向判断顺序: 下->右->上->左, 如果该点四个位置都走不通,则回溯到上一个点(递归)
*/
思路分析:
- 找到整个递归的终止条件: 小球到达出口位置[6][5] || 递归回溯到初始点发现走不通
- 找返回值,应该给上一层返回什么信息? 返回该点i,j 是否能走通 boolean
- 找到单次递归的任务: 需要按照策略顺序判断四个方向是否可以走通,可以返回true,不可以返回false
代码实现:
public class MiGong {
public static void main(String args[]){
int[][] map = new int[8][7];
//初始化迷宫
setMap(map);
showMap(map);
//寻找通路
setWay(map,1,1);
showMap(map);
}
public static void setMap(int[][] map){
//上下两行
for (int i = 0; i <map[i].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;
}
public static void showMap(int[][] map){
System.out.println("此时的地图情况:");
for (int[] i:map) {
for (int data: i) {
System.out.print(data+" ");
}
System.out.println();
}
}
/**
* 说明:
* 1. map 表示地图
* 2. i,j 表示从地图的哪个位置开始出发,起始位置(1,1)
* 3. 如果小球能到map[6][5]位置,则说明通路找到
* 4. 当map[i][j] 为0 表示当前位置还没有走过;为1 表示墙;为2 表示通路可以走并且已经走过;为3 表示该点走过并且上下左右四个位置都走不通
* 5. 策略: 方向判断顺序: 下->右->上->左, 如果该点四个位置都走不通,则回溯到上一个点(递归)
* @param map
* @param i
* @param j
*/
/**
* 递归解决问题的思路:
* 1. 找到整个递归的终止条件: 小球到达出口位置[6][5] || 递归回溯到初始点发现走不通
* 2. 找返回值,应该给上一层返回什么信息? 返回该点i,j 是否能走通 boolean
* 3. 找到单次递归的任务: 需要按照策略顺序判断四个方向是否可以走通,可以返回true,不可以返回false
*/
public static boolean setWay(int[][] map ,int i,int j){
if(map[6][5] == 2){
//通路已经找到,递归的终止条件
return true;
}else{
if(map[i][j] == 0){
//表示该点还没有走过
map[i][j] = 2; //假定该点是可以走通的,如果无法走通,则回溯时将值改为3
if(setWay(map,i+1,j)){
//向下走
return true;
}else if(setWay(map,i,j+1)){
//向右走 //elseif 表示 向上走失败后 再向右走
return true;
}else if(setWay(map,i-1,j)){
//向上走
return true;
}else if(setWay(map,i,j-1)){
//向左走
return true;
}else{
map[i][j] = 3;
return false; //四个方向都走不通,就判断走不通 将该点值变为3
}
}else{
// map = 1 2 3 时均不走
return false;
}
}
}
}
结果:
此时的地图情况:
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.2 八皇后问题
场景:在8*8的国际象棋棋盘上摆放8个皇后,使其不能互相攻击:即任意两个皇后都不能处于同一行,同一列或同一斜线上,问有多少种摆放.
思路分析:
- 第一个皇后放在第一行第一列;
- 第二个皇后放在第二行第一列,判断是否ok,如果不符合规则,继续放在第二列/第三列…,依次把所有列都放完,找到一个合适的
- 继续第三个皇后,直到第八个皇后也能放在不冲突的位置,算找到了一个正确解
- 如果一个棋子将8个位置全部尝试后仍然没有正确解,则前面的几个棋子出错,需要回溯进行修改; (回溯重点)
- 当得到一个正确解时,回退到上一个栈,开始回溯,即将第一个皇后,放到第一行第一列的所有正确解全部得到;
- 1 2 3 4 5 步骤结束后,皇后放在第一行第一列的所有解得到,将第一个皇后放在第一行第二列,继续循环12345步骤,得到所有解.
说明:
- 将得到的结果用一维数组进行表示,如:arr=[0,4,7,5,2,6,1,3]; 对应arr下标表示第几行,arr的值表示第几列
代码实现:
注意点:如果一个棋子n将8个位置全部尝试后仍然没有正确解,则回回溯到前一个棋子n-1,将其往后移动一位(array[n-1] = i;),再遍历判断n(从第一列开始)
public class Queue8 {
//定义皇后的个数
int max = 8;
//定义数组array,保存皇后防止位置的结果,比如arr=[0,4,7,5,2,6,1,3];
int[] array = new int[max];
static int count = 0;
public static void main(String args[]){
Queue8 queue8 = new Queue8();
queue8.check(0);
System.out.println("共找到多少种解法:"+count);
}
/**
* 放置第n个皇后
* 递归解决问题的思路:
* 1. 找到整个递归的终止条件: 当防止第8个皇后结束以后,即n=8时
* 2. 找到单次递归的任务: 一次递归需要将这个皇后应该放的第n行的 8个列从左到右遍历一遍,如果找到不与前n个皇后冲突的格子,则放入皇后,接下来继续防止n+1个皇后
*
* 注意: 如果一个棋子n将8个位置全部尝试后仍然没有正确解,则回回溯到前一个棋子n-1,将其往后移动一位(array[n-1] = i;),再遍历判断n(从第一列开始)
* @param n
*/
public void check(int n){
if(n == 8){
showArray();
return;
}
//依次放入皇后,并判断是否冲突
for (int i = 0; i <max ; i++) {
//先把当前这个皇后放在第n行的第一列
array[n] = i;
if(judge(n)){
//不冲突,就防止下一个皇后,直到第八个放完
check(n+1);
}
//如果冲突了,继续遍历i++, array[n] = i++;
}
}
/**
* 需要一个判断棋子是否冲突的方法
* 判断参数n皇后是否与已经放置的前n-1个皇后冲突
* 冲突规则:
* 1.同一列:array[i] == array[n]
* 2.同一斜线: Math.abs(n-i) == Math.abs(array[n] - array[i]) 两个棋子的行距离等于列距离
* @param n
* @return
*/
public boolean judge(int n){
for (int i = 0; i <n ; i++) {
if(array[i] == array[n] || Math.abs(n-i) == Math.abs(array[n] - array[i])){
return false;
}
}
return true;
}
//打印一次结果
public void showArray(){
for (int i = 0; i <8 ; i++) {
System.out.print(array[i]+" ");
}
System.out.println();
count++;
}
}
结果:
0 4 7 5 2 6 1 3
0 5 7 2 6 3 1 4
0 6 3 5 7 1 4 2
0 6 4 7 1 3 5 2
1 3 5 7 2 0 6 4
1 4 6 0 2 7 5 3
1 4 6 3 0 7 5 2
1 5 0 6 3 7 2 4
1 5 7 2 0 3 6 4
1 6 2 5 7 4 0 3
1 6 4 7 0 3 5 2
1 7 5 0 2 4 6 3
2 0 6 4 7 1 3 5
2 4 1 7 0 6 3 5
2 4 1 7 5 3 6 0
2 4 6 0 3 1 7 5
2 4 7 3 0 6 1 5
2 5 1 4 7 0 6 3
2 5 1 6 0 3 7 4
2 5 1 6 4 0 7 3
2 5 3 0 7 4 6 1
2 5 3 1 7 4 6 0
2 5 7 0 3 6 4 1
2 5 7 0 4 6 1 3
2 5 7 1 3 0 6 4
2 6 1 7 4 0 3 5
2 6 1 7 5 3 0 4
2 7 3 6 0 5 1 4
3 0 4 7 1 6 2 5
3 0 4 7 5 2 6 1
3 1 4 7 5 0 2 6
3 1 6 2 5 7 0 4
3 1 6 2 5 7 4 0
3 1 6 4 0 7 5 2
3 1 7 4 6 0 2 5
3 1 7 5 0 2 4 6
3 5 0 4 1 7 2 6
3 5 7 1 6 0 2 4
3 5 7 2 0 6 4 1
3 6 0 7 4 1 5 2
3 6 2 7 1 4 0 5
3 6 4 1 5 0 2 7
3 6 4 2 0 5 7 1
3 7 0 2 5 1 6 4
3 7 0 4 6 1 5 2
3 7 4 2 0 6 1 5
4 0 3 5 7 1 6 2
4 0 7 3 1 6 2 5
4 0 7 5 2 6 1 3
4 1 3 5 7 2 0 6
4 1 3 6 2 7 5 0
4 1 5 0 6 3 7 2
4 1 7 0 3 6 2 5
4 2 0 5 7 1 3 6
4 2 0 6 1 7 5 3
4 2 7 3 6 0 5 1
4 6 0 2 7 5 3 1
4 6 0 3 1 7 5 2
4 6 1 3 7 0 2 5
4 6 1 5 2 0 3 7
4 6 1 5 2 0 7 3
4 6 3 0 2 7 5 1
4 7 3 0 2 5 1 6
4 7 3 0 6 1 5 2
5 0 4 1 7 2 6 3
5 1 6 0 2 4 7 3
5 1 6 0 3 7 4 2
5 2 0 6 4 7 1 3
5 2 0 7 3 1 6 4
5 2 0 7 4 1 3 6
5 2 4 6 0 3 1 7
5 2 4 7 0 3 1 6
5 2 6 1 3 7 0 4
5 2 6 1 7 4 0 3
5 2 6 3 0 7 1 4
5 3 0 4 7 1 6 2
5 3 1 7 4 6 0 2
5 3 6 0 2 4 1 7
5 3 6 0 7 1 4 2
5 7 1 3 0 6 4 2
6 0 2 7 5 3 1 4
6 1 3 0 7 4 2 5
6 1 5 2 0 3 7 4
6 2 0 5 7 4 1 3
6 2 7 1 4 0 5 3
6 3 1 4 7 0 2 5
6 3 1 7 5 0 2 4
6 4 2 0 5 7 1 3
7 1 3 0 6 4 2 5
7 1 4 2 0 6 3 5
7 2 0 5 1 4 6 3
7 3 0 2 5 1 6 4
共找到多少种解法:92