递归实例——迷宫问题、八皇后问题

本文介绍了使用递归解决经典算法问题——迷宫问题和八皇后问题。在迷宫问题中,通过设定探索策略,寻找从起点到终点的可行路径。而在八皇后问题中,通过回溯算法找出所有不冲突的皇后布局。文章提供了详细的代码实现,并展示了如何在Java中实现这两种问题的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

递归实例——迷宫问题、八皇后问题

本篇博文通过尚硅谷韩顺平老师《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的国际象棋棋盘上摆放八个皇后,使其不能相互攻击(每个皇后不在同一行、同一列、同一斜线上),问有多少种摆放方法?

思路分析

  1. 第一个皇后先放在第一行的第一列;
  2. 第二个皇后放在第二行的第一列,判断是否与前面摆放的皇后冲突,如果冲突,就放在第二列再判断,如果冲突,再放到第三列再判断,找到一个合适的然后执行下一步…,直至把所有列摆放完;
  3. 第三个皇后放到第三行的第一列,判断是否与前面摆放的皇后冲突,如果冲突,就放在第二列再判断,如果冲突,再放到第三列再判断,找到一个合适的然后执行下一步…,直至把所有列摆放完;
  4. 依次类推,直到第八个皇后也能放在一个不冲突的位置,此时得到了一个解(正确的摆放);
  5. 当得到一个正确解时,再退栈到上一个栈,开始回溯,…(即将第一个皇后摆放在第一行,第一列的所有解全部得到);
  6. 然后将第一个皇后放到第一行的第二列,依次执行后续的2、3、4、5步骤…(即将第一个皇后摆放在第一行,第二列的所有解全部得到);
  7. 以此类推,…将第一个皇后摆放在每一行,每一列的全部解得到(即全部解)

*注意:*为了更加直观,可以将摆放的方式使用一维数组表示如: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();
	}
}

测试

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值