递归实例——迷宫问题、八皇后问题
本篇博文通过尚硅谷韩顺平老师《Java数据结构与算法》课程所做,在此非常感谢!
迷宫问题
问题引入
有一个迷宫,四周都是墙,求出一个小球从起点到终点的路径;
思路分析
我们先进行如下约定:
- 将迷宫初始化为二维数组;
- 当地图的(i, j)点为null,表示该点还没有探索过;
- 当为"墙"时,表示该点为墙;
- 当为"路"时,表示该点可以走;
- 当为"*"时,表示该点已经探索过了,但是走不通;
- 同时为了防止出现多条可以选择的路导致程序不能正常的运行,我们还需要制定策略:在有多条路可以选择时,按照 上、下、左、右 的优先顺序进行选择,如果该点走不通,再回溯;
编码
package edu.hebeu.maze;
/**
* 迷宫问题
* @author 13651
*
*/
public class Maze {
private static String[][] MAP;
// public static enum STRAGTEGY {
// UP_DOWN_LEFT_RIGHT, UP_DOWN_RIGHT_LEFT,
// UP_LEFT_DOWN_RIGHT, UP_LEFT_RIGHT_DOWN,
// UP_RIGHT_DOWN_LEFT, UP_RIGHT_LEFT_DOWN,
// }
/**
* 私有化构造器
*/
private Maze() {
}
/**
* 初始化迷宫
*/
public static Maze init() {
MAP = new String[8][7];
// 左右围墙建立
for(int i = 0; i < MAP.length; i++) {
buildWall(i, 0);
buildWall(i, 6);
}
// 上下围墙建立
for(int i = 0; i < MAP[0].length; i++) {
buildWall(0, i);
buildWall(7, i);
}
// 建造其他围墙
buildWall(2, 1);
buildWall(2, 2);
for(int i = 1; i < 4; i++) {
buildWall(i, 4);
}
for(int i = 2; i < 5; i++) {
buildWall(5, i);
}
buildWall(3, 2);
// 初始化开始位置和起始位置
MAP[1][1] = "起点";
MAP[6][5] = "终点";
return new Maze();
}
/**
* 在指定位置建造墙
* @param i 行号
* @param j 列号
*/
public static void buildWall(int i , int j) {
MAP[i][j] = "墙";
}
/**
* 用来显示地图
*/
public void showMap() {
for(int i = 0; i < 8; i++) {
for(int j = 0; j < 7; j++) {
System.out.print(MAP[i][j] + "\t");
}
System.out.println();
}
}
/**
* 给小球获得路径
* i,j表示:(i, j)表示从地图的哪个位置开始出发,这里我们默认为起点位置(1, 1)
* 如果小球能到达终点位置(6, 5),则路径找到了
*
* 约定:
* 当地图的(i, j)点为null,表示该点还没有探索过;
* 当为"墙"时,表示该点为墙;
* 当为"路"时,表示该点可以走;
* 当为"*"时,表示该点已经探索过了,但是走不通;
*
* 定制策略:
* 在有多条路可以选择时,按照 上、右、下、左 的优先顺序进行选择,如果该点走不通,再回溯
*
* @param i 行位置
* @param j 列位置
* @return 该点能否走通 ? true : false
*/
public boolean setWay(int i, int j) {
// TODO 如果终点变成"路",则已经找到通路
if("路".equals(MAP[6][5])) {
return true;
}
// TODO 程序执行到此说明还没有找到最终通路
// TODO 如果当前点为null(即还没有走过)
if(MAP[i][j] == null || "起点".equals(MAP[i][j]) || "终点".equals(MAP[i][j])) {
// TODO 假定该点可以走通
MAP[i][j] = "路";
// TODO 按照指定的策略走
if(setWay(i - 1, j)) { // 如果向上走的通
return true;
} else if(setWay(i, j + 1)) { // 如果向右走的通
return true;
} else if(setWay(i + 1, j)) { // 如果向下走的通
return true;
} else if(setWay(i, j - 1)) { // 如果向左走的通
return true;
}
// 程序执行到此,即上右下左都走不通,该点是死路
MAP[i][j] = "*";
return false;
}
// TODO 程序执行到此,说明如果当前点要么为"*",要么为"墙",即此点走不通;要么是"路",即此点已走,但是还不知道能否走通,所以也返回false
return false;
}
public static void main(String[] args) {
Maze maze = Maze.init();
System.out.println("迷宫:");
maze.showMap();
System.out.println("路线(按照上右下左的策略):");
maze.setWay(1, 1); // 从(1, 1)开始
maze.showMap();
}
}
测试
八皇后问题
问题引入
八皇后问题,一个古老而著名的问题,回溯算法的典型案例,在8*8的国际象棋棋盘上摆放八个皇后,使其不能相互攻击(每个皇后不在同一行、同一列、同一斜线上),问有多少种摆放方法?
思路分析
- 第一个皇后先放在第一行的第一列;
- 第二个皇后放在第二行的第一列,判断是否与前面摆放的皇后冲突,如果冲突,就放在第二列再判断,如果冲突,再放到第三列再判断,找到一个合适的然后执行下一步…,直至把所有列摆放完;
- 第三个皇后放到第三行的第一列,判断是否与前面摆放的皇后冲突,如果冲突,就放在第二列再判断,如果冲突,再放到第三列再判断,找到一个合适的然后执行下一步…,直至把所有列摆放完;
- 依次类推,直到第八个皇后也能放在一个不冲突的位置,此时得到了一个解(正确的摆放);
- 当得到一个正确解时,再退栈到上一个栈,开始回溯,…(即将第一个皇后摆放在第一行,第一列的所有解全部得到);
- 然后将第一个皇后放到第一行的第二列,依次执行后续的2、3、4、5步骤…(即将第一个皇后摆放在第一行,第二列的所有解全部得到);
- 以此类推,…将第一个皇后摆放在每一行,每一列的全部解得到(即全部解)
*注意:*为了更加直观,可以将摆放的方式使用一维数组表示如:arry[queensMax] = {…};此时,该数组每个元素的含义(即array[i] = val)就是:第i+1个皇后放在第i+1行,第val+1列;
编码
package edu.hebeu.queens8;
/**
* 这个类用来解决八皇后问题
* 八皇后问题,一个古老而著名的问题,回溯算法的典型案例,在8*8的国际象棋棋盘上摆放八个皇后,使其
* 不能相互攻击(每个皇后不在同一行、同一列、同一斜线上),问有多少种摆放方法?
*
* @author 13651
*
* 思路分析:
* 为了更加直观,可以将摆放的方式使用一维数组表示如:arry[queensMax] = {...};
* 此时,该数组每个元素的含义(即array[i] = val)就是:第i+1个皇后放在第i+1行,第val+1列
*
* 1. 第一个皇后先放在第一行的第一列;
* 2. 第二个皇后放在第二行的第一列,判断是否与前面摆放的皇后冲突,如果冲突,就放在第二列再判断,如果冲突,再
* 放到第三列再判断,直至把所有列摆放完,找到一个合适的
* 3. 第三个皇后放到第三行的第一列,判断是否与前面摆放的皇后冲突,如果冲突,就放在第二列再判断,如果冲突,再
* 放到第三列再判断,直至把所有列摆放完,找到一个合适的
* 4. 依次类推,直到第八个皇后也能放在一个不冲突的位置,此时得到了一个解(正确的摆放)
* 5. 当得到一个正确解时,再退栈到上一个栈,开始回溯,...(即将第一个皇后摆放在第一行,第一列的所有解全部得到)
* 6. 然后将第一个皇后放到第一行的第二列,依次执行后续的2、3、4、5步骤.......(即将第一个皇后摆放在第一行,第二列的所有解全部得到)
* 7. 以此类推,...将第一个皇后摆放在每一行,每一列的全部解得到(即全部解)
*
*/
public class EightQueens {
/**
* 棋盘上需要摆放的皇后的数量
*/
private int queensMax = 8;
/**
* 这个一维数组用来保存每一种的放置方法,如:arry[queensMax] = {...};
* 此时,该数组每个元素的含义(即array[i] = val)就是:第i+1个皇后放在第i+1行,第val+1列
*/
private int[] place = new int[queensMax];
/**
* 该变量用来保存有几种解法
*/
public int resCount;
/**
* 该变量用来保存doPlace()方法执行了几次
*/
public int doPlaceCount;
/**
* 该变量用来保存doPlace()方法内的for循环共执行了几次
*/
public int doPlaceForCount;
/**
* 私有化构造器
*/
private EightQueens() {
}
/**
* 开始游戏的方法
*/
public static void play() {
EightQueens eightQueens = new EightQueens();
eightQueens.doPlace(0); // 从第一个皇后开始摆放
System.out.println("共有解法:" + eightQueens.resCount);
System.out.println("doPlace()方法执行的次数:" + eightQueens.doPlaceCount);
System.out.println("doPlace()方法内for循环的次数:" + eightQueens.doPlaceForCount);
}
/**
* 该方法用来在第queenNum行 按照列依次判断 放置(先判断能否放置)第queenNum个皇后
* @param queenNum
*/
public void doPlace(int queenNum) {
doPlaceCount++;
if(queenNum == queensMax) { // 如果放置的皇后等于棋盘上需要摆放的皇后的数量(即此时已经摆放完了,因为queenNum是从0开始的)
resCount++;
showRes(); // 将本次的摆放方式打印输出
return;
}
// TODO 第 queenNum行 依次按照列能否放置 第queenNum个皇后
for(int i = 0; i < 8; i++) {
doPlaceForCount++;
place[queenNum] = i; // 将第queenNum个皇后 放置在queenNum行的 第i列
if(!attack(queenNum)) { // 如果此时放置的皇后 与前面的所有皇后不会产生攻击(即可以放置)
// TODO 开始递归,放置第queenNum + 1 个皇后
doPlace(queenNum + 1);
}
// 如果此时放置的皇后 与前面的所有皇后会产生攻击(即不可以放置),就会通过for循环将第queenNum皇后放到第queenNum行的第i+1列
}
}
/**
* 该方法用来判断放置第n个皇后之后,是否与前面的皇后冲突
* @param queenNum 放置的第n个皇后
* @return 是否冲突 ? true : false
*/
private boolean attack(int queenNum) {
// TODO 遍历该皇后的前面几个皇后
for(int i = 0; i < queenNum; i++) {
// 因为第x个皇后就是在x行,所以这些皇后必定不在同一行,因此我们只需要判断这些皇后是否在同一列或同一斜线
if(place[i] == place[queenNum] // 如果在同一列
|| Math.abs(queenNum - i) == Math.abs(place[queenNum] - place[i]) // 如果在同一斜线
) {
return true;
}
}
// 程序执行到此,说明要添加的皇后与前面的皇后都没有冲突
return false;
}
/**
* 该方法用来打印摆放的结果
*/
private void showRes() {
System.out.println("摆放方式(数组每个元素的含义(即array[i] = val)就是:第i+1个皇后放在第i+1行,第val+1列):");
for(int i = 0; i < place.length; i++) {
System.out.print(place[i] + "\t");
}
System.out.println();
System.out.println("图示:");
String[][] checkerboard = new String[8][8];
for(int i = 0; i < queensMax; i++) {
checkerboard[i][place[i]] = "皇后";
}
for(int i = 0; i < 8; i++) {
for(int j = 0; j < 8; j++) {
System.out.print(checkerboard[i][j] + "\t");
}
System.out.println();
}
System.out.println();
System.out.println();
}
}
测试类的编写
package edu.hebeu.queens8;
public class Test {
public static void main(String[] args) {
EightQueens.play();
}
}
测试