递归【迷宫问题和八皇后问题】

本文探讨了使用递归解决迷宫问题和八皇后问题的策略。在迷宫问题中,通过设定二维数组模拟迷宫,利用递归进行路径搜索并回溯。而在八皇后问题上,介绍了回溯算法的应用,逐步放置皇后并检查冲突,找到所有可能的解决方案。
摘要由CSDN通过智能技术生成

递归

概念
  • 简单的说: 递归就是方法自己调用自己,每次调用时传入不同的变量。
  • 递归有助于编程者解决复杂的问题,同时可以让代码变得简洁。
递归需遵循的规则
  • 执行一个方法时, 就创建一个新的受保护的独立空间(栈空间)。
  • 方法的局部变量是独立的, 不会相互影响, 比如 n 变量。
  • 如果方法中使用的是引用类型变量(比如数组), 就会共享该引用类型的数据
  • 递归必须向退出递归的条件逼近, 否则就是无限递归,出现 StackOverflowError异常。
  • 当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕。

迷宫问题

思路
  • 使用二维数组 map[][] 模拟迷宫。
  • 约定: 当 map[i][j] 为 0 表示该点没有走过;当为 1 表示墙;2 表示通路可以走 ;3 表示该点已经走过,但是走不通。
  • setWay() 方法用于找路,true 表示该路可以走通,false 表示该路走不通。
  • 在走迷宫时,需要确定一个策略(方法) 下->右->上->左 , 一步一步向前试探,如果该点走不通,再回溯。
  • 每当走到一个点时,将该点置为 2 ,暂时假设该路能走通,至于到底走不走得通,得看后面有没有找到通路。
    (1)如果后面的路能走通,从最后一个点开始返回,整个 setWay() 递归调用链都返回 true。
    (2)如果后面的路不能走通,那么将当前的点设置为 3 ,表示是死路,走不通,回溯至上一个点,看看其他方向能不能走通。
代码
public static void main(String[] args) {
    // 先创建一个二维数组,模拟迷宫
    // 地图
    int[][] map = new int[8][7];
    // 使用1 表示墙
    // 上下全部置为1
    for (int i = 0; i < 7; i++) {
        map[0][i] = 1;
        map[7][i] = 1;
    }

    // 左右全部置为1
    for (int i = 0; i < 8; i++) {
        map[i][0] = 1;
        map[i][6] = 1;
    }
    // 设置挡板, 1 表示
    map[3][1] = 1;
    map[3][2] = 1;
    map[4][4] = 1;
    map[5][4] = 1;
    map[6][4] = 1;
    map[4][4] = 1;

    // 输出地图
    System.out.println("地图的情况");
    for (int i = 0; i < 8; i++) {
        for (int j = 0; j < 7; j++) {
            System.out.print(map[i][j] + " ");
        }
        System.out.println();
    }

    // 使用递归回溯给小球找路
    setWay(map, 1, 1);
    // setWay2(map, 1, 1);

    // 输出新的地图, 小球走过,并标识过的地图
    System.out.println("小球走过,并标识过的 地图的情况");
    for (int i = 0; i < 8; i++) {
        for (int j = 0; j < 7; j++) {
            System.out.print(map[i][j] + " ");
        }
        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
 * @return 如果找到通路,就返回true, 否则返回false
 */
public static boolean setWay(int[][] map, int i, int j) {
	if (map[6][5] == 2) { // 通路已经找到ok
		return true;
	} else {
		if (map[i][j] == 0) { // 如果当前这个点还没有走过
			// 按照策略 下->右->上->左 走
			map[i][j] = 2; // 假定该点是可以走通.
			if (setWay(map, i + 1, j)) {// 向下走
				return true;
			} else if (setWay(map, i, j + 1)) { // 向右走
				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;
			}
		} else { // 如果map[i][j] != 0 , 可能是 1, 2, 3
			return false;
		}
	}
}

// 修改找路的策略,改成 上->右->下->左
public static boolean setWay2(int[][] map, int i, int j) {
	if (map[6][5] == 2) { // 通路已经找到ok
		return true;
	} else {
		if (map[i][j] == 0) { // 如果当前这个点还没有走过
			// 按照策略 上->右->下->左
			map[i][j] = 2; // 假定该点是可以走通.
			if (setWay2(map, i - 1, j)) {// 向上走
				return true;
			} else if (setWay2(map, i, j + 1)) { // 向右走
				return true;
			} else if (setWay2(map, i + 1, j)) { // 向下走
				return true;
			} else if (setWay2(map, i, j - 1)) { // 向左走
				return true;
			} else {
				// 说明该点是走不通,是死路
				map[i][j] = 3;
				return false;
			}
		} else { 
		// 如果map[i][j] != 0 , 可能是 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 1 0 1 
1 0 0 0 1 0 1 
1 0 0 0 1 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 2 2 1 
1 3 3 3 1 2 1 
1 3 3 3 1 2 1 
1 3 3 3 1 2 1 
1 1 1 1 1 1 1 

八皇后问题(回溯算法)

介绍
  • 八皇后问题, 是一个古老而著名的问题, 是回溯算法的典型案例。
  • 该问题是国际西洋棋棋手马克斯· 贝瑟尔于1848 年提出: 在 8× 8 格的国际象棋上摆放八个皇后, 使其不能互相攻击, 即: 任意两个皇后都不能处于同一行、同一列或同一斜线上, 问有多少种摆法(92种)。
思路
  • 第一个皇后先放第一行第一列。
  • 第二个皇后放在第二行第一列、 然后判断是否 OK, 如果不 OK, 继续放在第二列、 第三列、 依次把所有列都放完, 找到一个合适。
  • 继续第三个皇后, 还是第一列、 第二列…… ,直到第 8 个皇后也能放在一个不冲突的位置, 算是找到了一个正确解。
  • 当得到一个正确解时, 在栈回退到上一个栈时, 就会开始回溯, 即将第一个皇后, 放到第一列的所有正确解,全部得到。
  • 然后回头继续第一个皇后放第二列, 后面继续循环执行 1, 2, 3, 4 的步骤。
  • 关于 array 数组的说明:
    (1)理论上应该创建一个二维数组来表示棋盘, 但是实际上可以通过算法, 用一个一维数组即可解决问题: array[8] = {0 , 4, 7, 5, 2, 6, 1, 3}。
    (2)array 数组的下标代表皇后所在的行数,array 数组中的值代表皇后所在的列数。
    (3)比如 a[0] = 0 ,则表示第一个皇后在第一行第一列。
代码
public class EightQueensApp {
    /** 定义皇后数*/
    static int max = 8;
    static int[] queens = new int[max];
    /** 解决方式的总数*/
    static int count = 0;
    /** 判断冲突的总数*/
    static int conflictedCount = 0;
    public static void main(String[] args) {
        setPosition(0);
        System.out.println("解决方式的总数为 " + count);
        System.out.println("判断冲突的总数为 " + conflictedCount);
    }

    /**
     * 1) num是第 num个皇后(第几行的皇后)
     * 2) 经过 for(int pos = 0; pos < max; pos++)依次放好8皇后
     * 3) 循环递归调用 setPosition(num + 1), 
      		直到8位皇后放好完成了一个解决方式后打印
     * */
    private static void setPosition(int num) {
        /** 8位皇后已放好, 完成了一个解决方式*/
        if(num == max) {
            print();
            return;
        }

        /** 循环依次放好8皇后*/
        for(int pos = 0; pos < max; pos++) {
            /** 第 num的皇后放到 pos位置上后*/
            queens[num] = pos;
            /** 放好第 num皇后到 pos位置后, 判断是否冲突*/
            if(judge(num)) {
                /** 如果不冲突, 接着放下一个皇后*/
                setPosition(num + 1);
            }
            /** 如果冲突, 就会将第 num的皇后, 向右移一位 array[num] = pos, 再继续判断*/
        }
    }

    /** 判断规则: 棋盘上放置8个棋子(皇后), 
    且相互不冲突(皇后在同一行, 同一列和同一斜线上都属冲突)
    */
    private static boolean judge(int num) {
        conflictedCount++;
        for(int pos = 0; pos < num; pos++) {
            /**
             * 1) queens[num] == queens[pos]判断第 num个的
             	 皇后是否与 pos位置的皇后在同一个列上。
             * 2) Math.abs(num - pos) == 
              Math.abs(queens[num] - queens[pos])
              判断第 num个的皇后是否与 pos位置的皇后在同一个斜线
              上。
             * 3) 无需判断行, 因为 num每次都在递增的
             * */
            if(queens[num] == queens[pos] ||
                    Math.abs(num - pos) == Math.abs(queens[num] - queens[pos])) {
                return false;
            }
        }
        return true;
    }

    /** 打印解决方式(每次打印一种)*/
    private static void print() {
        count++;
        for (int i = 0; i < queens.length; i++) {
            System.out.print(queens[i] + " ");
        }
        System.out.println("第 " + count + "次");
    }

}

输出:
> 0 4 7 5 2 6 1 31> 0 5 7 2 6 3 1 42> 0 6 3 5 7 1 4 23> 0 6 4 7 1 3 5 24> 1 3 5 7 2 0 6 45> 1 4 6 0 2 7 5 36> 1 4 6 3 0 7 5 27...
...
> 解决方式的总数为 92
> 判断冲突的总数为 15720

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值