37

数据结构与算法(七):迷宫回溯和八皇后问题

 4 years ago
source link: http://www.cnblogs.com/Createsequence/p/13196730.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

一、迷宫回溯问题

1.问题

一个7*8的数组模拟迷宫,障碍用1表示,通路使用0表示,给定起点(1,1)和终点(6,5),要求给出起点到终点的通路

eyqmEbq.png!webFFr2ymm.png!web

2.解题思路

  1. 首先,我们需要给程序一个寻向的基本策略,我们先假定寻向顺序为“下-右-上-左”,也就是说从起点出发,先往下走,往下走不通就往右.....以此类推
  2. 然后我们需要给走过的路一个标记,暂记为2
  3. 而当从一个方向走到一个只能原路返回的死胡同时,就给这段路标记为3
  4. 当抵达终点坐标(6,5)时程序结束

3.代码实现

3.1生成地图

/**
 * 创建一个二维数组,用于模拟8*7迷宫
 * 使用1表示不可通过的实心方块,0表示可通过砖块
 * (6,5)为默认终点,(1,1)为默认起点
 * @return
 */
public static int[][] getMap(){
    int[][] map = new int[8][7];
    //上下全置为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;
    }
    //设置挡板
    map[3][1] = 1;
    map[3][2] = 1;

    //输出地图
    System.out.println("地图的初始情况:");
    showMap(map);

    return map;
}

/**
 * 展示地图
 * @param map
 */
public static void showMap(int[][] map) {
    for(int i = 0;i < 8;i++){
        for(int j = 0;j < 7;j++){
            System.out.print(map[i][j] + " ");
        }
        System.out.println();
    }
}

3.2 寻路逻辑的实现

对于这个寻路程序,我们可以看见,往四个方向走的过程实际上除了方向外动作上是一样的;而具体分析同一个方向,每走过一个坐标的动作也是一样的,我们对流程进行分析:

int[x][y]==1

表现为代码实际上就是一个递归的过程:

  • 找路是方法体
  • 找到了(6,5)或者死胡同是终止条件
/**
 * 给定起始点,根据地图找路
 * 使用2表示可以走通的路,使用3表示走过但是不通的路
 * @param map 地图二维数组
 * @param x 起始点横坐标
 * @param y 起始点纵坐标
 * @return
 */
public static boolean findWay(int[][] map, int x, int y) {
    //如果走到了终点就终止
    if (map[6][5] == 2){
        return true;
    }else {
        //只有为0的路才能通过
        if (map[y][x] == 0) {
            //如果该点可以走通就打上标记
            map[y][x] = 2;
            if (findWay(map, x, y + 1)) {
                //向下递归
                return true;
            } else if (findWay(map, x + 1, y)) {
                //向右递归
                return true;
            } else if (findWay(map, x, y - 1)) {
                //向上递归
                return true;
            } else if (findWay(map, x - 1, y)) {
                //向左递归
                return true;
            } else {
                //都走不通说明是死胡同
                map[y][x] = 3;
                return false;
            }
        }else {
            //不为0说明要么是死路要么是障碍
            return false;
        }
    }
}

3.3 运行结果

feEVvqA.png!webuERBn2f.png!web

findWay() 方法中的终止条件从 map[6][5] == 2 换成其他坐标即可更换终点位置,

棋盘大小和障碍物位置不影响 findWay() 方法寻路。

二、八皇后问题

1.问题

皇后问题,一个古老而著名的问题,是 回溯算法 的典型案例。该问题由国际西洋棋棋手马克斯·贝瑟尔于 1848 年提出:

在 8×8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,求有多少种摆法?

uEZVrmE.jpg!web

2.解题思路

  1. 首先,我们先使用一个长度为8数组来表示八皇后的摆放位置, 数组下标+1即表示棋盘的第几行数组下标对应的存放的数字+1即为棋盘的第几列 。举个例子:

    arr = {0,2,3,8,4,6,2,7}

    其中,元素0下标为0,即表示 第一行第一列 ;元素2下标为1,即表示 第二行第三列 ......以此类推。

  2. 任意假设任意坐标分标为 (x1,y1),(x2,y2) ,也就是用数组表示为 arr[x1]=y1,arr[x2]=y2 的两个皇后不允许在同一列,我们可以理解为:

    arr[x1] != arr[x2] ;

    而任意坐标的皇后不允许在同一斜线,即 (x2-x1)=(y2-y1) ,也就是斜率不应当相同,我们可以理解为:

    Math.abs(x2-x1) != Math.abs(arr[x2]-arr[x1])

    (注: Math.abs() 为求绝对值方法)

3.代码实现

3.1 检查摆放位置的代码实现

在前面明确了如何用数组表示位置,以及如何检查皇后是否允许摆放后,我们有如下代码:

//表示皇后位置的数组
int[] arr = new int[8];

/**
 * 检查第n个皇后是否与前面摆放的皇后冲突
 * @param n
 * @return
 */
public boolean check(int n) {
    //检查第n层之前的皇后位置
    for (int i = 0; i < n; i++) {
        // arr[i] == arr[n] 检查是否同一列
        // Math.abs(n - i) == Math.abs(arr[n] - arr[i]) 检查是否同一斜线
        if (arr[i] == arr[n] ||
            Math.abs(n - i) == Math.abs(arr[n] - arr[i])) {
            return false;
        }
    }
    return true;
}

3.2 完整代码

接着我们需要考虑如何使用递归方法来做到以下效果:

使用一个方法遍历第n行的每一列,检查每一列是否可以放置皇后:

  1. 如果可以放置皇后,将位置出入arr[n]中,然后递归调用自己,传入n+1开始遍历下一行.....以此类推
  2. 如果不可以放置皇后,就跳过该列检查下一列,如果可以就重复步骤1
  3. 若n行中全部位置都不合适,则结束本层返回上一层n-1层,重复步骤1
  4. 如果最后n=8,即八个皇后全部放置完毕,记一次完成摆放,然后结束递归返回第一层,继续检查第一层的下一列

最终代码实现结果如下:

/**
 * @Author:黄成兴
 * @Date:2020-06-26 20:53
 * @Description:八皇后问题
 */
public class EightQueens {

    public static void main(String[] args) {
        EightQueens eightQueens = new EightQueens();
        eightQueens.set(0);
        System.out.println("共有摆法:" + eightQueens.count);
    }

    //记录八皇后有几种摆法
    int count = 0;

    //表示皇后位置的数组
    int[] arr = new int[8];

    /**
     * 摆放皇后
     * @param n 第几个皇后
     */
    private void set(int n) {
        //如果放置好了第8个皇后
        if (n == 8){
            show();
            //记录一种摆放方式
            count++;
            //回到第一层继续递归
            return;
        }

        //遍历第n行的每一列
        for (int i = 0; i < 8; i++) {
            //将该皇后放置在第n行第i列
            arr[n] = i;
            //检查放置位置是否合适
            if (check(n)){
                //如果位置合适,就递归找下一个(n+1)皇后的摆放位置
                set(n + 1);
            }
            //如果位置不合适,就跳过这一列检查下一列
        }
    }

    /**
     * 检查第n个皇后是否与前面摆放的皇后冲突
     * @param n
     * @return
     */
    public boolean check(int n) {
        //检查第n层之前的皇后位置
        for (int i = 0; i < n; i++) {
            // arr[i] == arr[n] 检查是否同一列
            // Math.abs(n - i) == Math.abs(arr[n] - arr[i]) 检查是否同一斜线
            if (arr[i] == arr[n] ||
                Math.abs(n - i) == Math.abs(arr[n] - arr[i])) {
                return false;
            }
        }
        return true;
    }
    

    /**
     * 展示某一摆法中八皇后的摆放位置
     */
    public void show() {
        for (int i : arr) {
            System.out.print(i + " ");
        }
        System.out.println();
    }
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK