迷宫回溯、八皇后

19 年 9 月 18 日 星期三
4157 字
21 分钟

数据结构与算法

递归Recursion、迷宫回溯、八皇后问题、排列组合

对于递归大家应该都不陌生了,我们直接看递归的应用。

迷宫回溯

给定一个二维数组,用1表示墙壁,0表示道路,当使用者指定入口和出口时,程序可以从入口走出迷宫。

如这个二维数组:

text
1  1  1  1  1
1  1  0  0  1
0  0  0  1  1
1  0  0  0  1
1  0  1  1  1
1  0  1  1  1

因为要求比较低,所以实现起来比较简单:

java实现

java
package Recursion;

import java.io.*;

public class Maze {

    //用二维数组表示迷宫地图,约定1为墙壁,0为通路,2为足迹,3为重复走过的点
    int[][] mazeMap;
    //出口坐标
    int goalI;
    int goalJ;

    //以文件方式构造地图数组,这样好设计地图
    public Maze(File mapFile) throws IOException {
        loadMapFile(mapFile);
    }

    //读取地图文件中的二维数组
    public void loadMapFile(File mapFile) throws IOException {
        /*
         * 拿到文件中二维数组的行数列数
         * */
        Reader reader = new FileReader(mapFile);
        BufferedReader bufferedReader = new BufferedReader(reader);
        //查看数组行数
        int rowI = 0;
        //数组列数
        int rowJ = 0;
        //文件每行数据
        String line;
        while ((line = bufferedReader.readLine()) != null) {
            //得到数组行数
            rowI++;
            //每行以两个空格分割
            String[] splits = line.split(" {2}");
            //得到列数
            rowJ = splits.length;

        }
        System.out.println("该地图的二维数组行数:" + rowI);
        System.out.println("该地图的二维数组列数:" + rowJ);
        //初始化对应行列的数组
        mazeMap = new int[rowI][rowJ];
        System.out.println("地图数组初始化完成");

        /*
         * 载入文件中的数组数据
         * */
        //关闭流
        bufferedReader.close();
        //新建流
        reader = new FileReader(mapFile);
        bufferedReader = new BufferedReader(reader);

        //开始读取每行数据
        //数组第i行
        int i = 0;
        while (i < rowI) {
            //读取每行数据
            line = bufferedReader.readLine();
            //以两个空格分割
            String[] splits = line.split(" {2}");
            //处理当前行的数据
            for (int j = 0; j < splits.length; j++) {
                //[打印]每行数据,不换行
                System.out.print(splits[j] + "\t");
                //把当前值转为int,然后赋给二维数组
                mazeMap[i][j] = Integer.parseInt(splits[j]);
            }
            //行数自增
            i++;
            //[打印]每行打印后换行
            System.out.println();
        }
        System.out.println("地图数组数据载入完成!");
    }

    //读取稀疏数组文件
    /*
     * 暂时不需要,地图文件大的话可以转成稀疏数组存放
     * */

    //打印数组
    public void show(){
        //打印数组
        for (int[] valuex : mazeMap) {
            for (int valuey : valuex) {
                System.out.print(valuey + "\t");
            }
            System.out.println();
        }
    }


    //自动寻找迷宫路线,指定一个入口的坐标(x,y),再指定一个出口的坐标(goalI,goalJ)
    public void autoFind(int x, int y, int goalI, int goalJ) {
        if (mazeMap == null) {
            System.out.println("地图无数据,请先初始化");
        } else {
            //判断输入值是否越界,是否有效
            if (x >= mazeMap[0].length || y >= mazeMap.length || mazeMap[y][x] == 1) {
                System.out.println("该位置不在地图内,或者该位置不能做为入口");
            } else {
                //指定出口坐标
                this.goalI = goalI;
                this.goalJ = goalJ;
                //开始寻路
                System.out.println("开始寻路");
                startFind(x, y);
                //打印数组
                show();
            }
        }
    }

    //递归寻路
    public boolean startFind(int x, int y) {
        //若到达出口
        if (x == goalI && y == goalJ) {
            System.out.println("找到出口");
            return true;
        }
        //若能走通
        if (mazeMap[y][x] == 0) {
            mazeMap[y][x] = 2;
            System.out.println("找到一个点");
            show();
            //接着向上走
            if (startFind(x, y - 1)) {
                return true;
                //向右走
            } else if (startFind(x + 1, y)) {
                return true;
                //向下走
            } else if (startFind(x, y + 1)) {
                return true;
                //向左走
            } else if (startFind(x - 1, y)) {
                return true;
            } else {
                //都走不通
                System.out.println(" ");
                return false;
            }
        }else if (mazeMap[y][x] == 2){
            System.out.println("回溯");
            mazeMap[y][x] = 3;
            show();
            return false;
        }else {
            System.out.println("碰到墙");
            return false;
        }
    }
}


class mapTest {
    public static void main(String[] args) throws IOException {
        //源文件
        File mapFile = new File("D:\\aria2\\mazeMap.data");
        Maze maze = new Maze(mapFile);
        //起始坐标(0,2),终点坐标(1,5)
        maze.autoFind(0, 2, 1, 5);
    }
}

输出结果

shell
该地图的二维数组行数:6
该地图的二维数组列数:5
地图数组初始化完成
1	1	1	1	1
1	1	0	0	1
0	0	0	1	1
1	0	0	0	1
1	0	1	1	1
1	0	1	1	1
地图数组数据载入完成!
开始寻路
找到一个点
1	1	1	1	1
1	1	0	0	1
2	0	0	1	1
1	0	0	0	1
1	0	1	1	1
1	0	1	1	1
碰到墙
找到一个点
1	1	1	1	1
1	1	0	0	1
2	2	0	1	1
1	0	0	0	1
1	0	1	1	1
1	0	1	1	1
碰到墙
找到一个点
1	1	1	1	1
1	1	0	0	1
2	2	2	1	1
1	0	0	0	1
1	0	1	1	1
1	0	1	1	1
找到一个点
1	1	1	1	1
1	1	2	0	1
2	2	2	1	1
1	0	0	0	1
1	0	1	1	1
1	0	1	1	1
碰到墙
找到一个点
1	1	1	1	1
1	1	2	2	1
2	2	2	1	1
1	0	0	0	1
1	0	1	1	1
1	0	1	1	1
碰到墙
碰到墙
碰到墙
回溯
1	1	1	1	1
1	1	3	2	1
2	2	2	1	1
1	0	0	0	1
1	0	1	1	1
1	0	1	1	1

回溯
1	1	1	1	1
1	1	3	2	1
2	2	3	1	1
1	0	0	0	1
1	0	1	1	1
1	0	1	1	1
碰到墙

碰到墙
找到一个点
1	1	1	1	1
1	1	3	2	1
2	2	3	1	1
1	0	2	0	1
1	0	1	1	1
1	0	1	1	1
碰到墙
找到一个点
1	1	1	1	1
1	1	3	2	1
2	2	3	1	1
1	0	2	2	1
1	0	1	1	1
1	0	1	1	1
碰到墙
碰到墙
碰到墙
回溯
1	1	1	1	1
1	1	3	2	1
2	2	3	1	1
1	0	3	2	1
1	0	1	1	1
1	0	1	1	1

碰到墙
找到一个点
1	1	1	1	1
1	1	3	2	1
2	2	3	1	1
1	2	3	2	1
1	0	1	1	1
1	0	1	1	1
回溯
1	1	1	1	1
1	1	3	2	1
2	3	3	1	1
1	2	3	2	1
1	0	1	1	1
1	0	1	1	1
碰到墙
找到一个点
1	1	1	1	1
1	1	3	2	1
2	3	3	1	1
1	2	3	2	1
1	2	1	1	1
1	0	1	1	1
回溯
1	1	1	1	1
1	1	3	2	1
2	3	3	1	1
1	3	3	2	1
1	2	1	1	1
1	0	1	1	1
碰到墙
找到出口
1	1	1	1	1
1	1	3	2	1
2	3	3	1	1
1	3	3	2	1
1	2	1	1	1
1	0	1	1	1

Process finished with exit code 0

八皇后Eight queens

百度百科:

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

针对这个问题,我们可以把棋盘简化成一个普通数组, 把棋盘行数对应为数组下标即可,而不必使用二维数组来表示棋盘,如:

java
array[8]={0, 6, 3, 5, 7, 1, 4, 2}
//分别对应棋子从第1行到第8行的位置
//每行的值在0~7之间
  • 判断是否在同一列

    • 只需判断与之前已有的值是否相同,设当前行号为n,之前行号为pastN,若在同一列,则有:
    java
    array[n] == array[pastN]
    
  • 判断是否在同一斜线

    • 只需判断行号之间的差值,与对应行棋子所在位置的差值,是否相同,需要用绝对值来判断:
    java
    //Math.abs(a)返回a的绝对值
    Math.abs( array[n] - array[pastN] )==Math.abs( n - pastN)
    
  • 判断是否在同一行

    • 显然一维数组不可能有同一行,所以无需判断

java实现

先看一个比较简单的java实现,以后我们再对它进行优化:

java
package Recursion;

import java.util.Arrays;

public class EightQueens {

    //初始化一个数组
    private int[] array = new int[8];
    //对有效结果进行计数
    private int count = 0;

    //使用n表示棋盘的第n+1行,初始为0,表示棋盘第1行,即array[n]


    /**
     * 往棋盘中放入皇后(给数组赋值)
     * @param n 表示当前行号,范围从0到7
     */
    public void addQueen(int n) {

        //当n==8时,说明一个8*8的棋盘每行都放了,此时是要放第9个了
        if (n == 8) {
            count++;
            System.out.println("第" + count + "次的棋子已经放完:"+showArrayString());
            return;
        }

        //在当前行放棋子,从0号位放到7号位
        for (int i = 0; i < array.length; i++) {
            //放入棋子
            array[n] = i;
            //每放一个,判断棋子的位置与放过的棋子是否冲突
            //若不冲突
            if (check(n)) {
                //用递归,换到下一行做重复的事情
                addQueen(n + 1);
            }
            //没进入递归,就说明该棋子位置冲突,不用做什么,继续for循环改变棋子位置即可
        }
    }

    //判断是否冲突的方法
    public boolean check(int n) {
        //遍历棋盘的每一行
        for (int pastN = 0; pastN < n; pastN++) {
            //若与当前行的棋子在同列或同一斜线
            if (isSameColumn(n, pastN) || isSameSlant(n, pastN)) {
                return false;
            }
        }
        //循环完毕后能执行到这里,说明不与以前的棋子在同列同斜线
        return true;
    }


    /**
     * 判断是否在同一列
     *
     * @param n     表示当前行
     * @param pastN 表示n之前的任意行
     */
    public boolean isSameColumn(int n, int pastN) {

        return array[n] == array[pastN];
    }

    //判断是否在同一行
    /*这个无需判断,因为肯定不在同一行*/


    //判断是否在同一斜线,使用绝对值
    public boolean isSameSlant(int n, int pastN) {
        return Math.abs(array[n] - array[pastN]) == Math.abs(n - pastN);
    }

    //返回数组的字符串
    public String showArrayString() {
        return Arrays.toString(array);
    }

    //展示数组
    public void show(){
        System.out.println(showArrayString());
    }


}

class eightQueenTest {
    public static void main(String[] args) {
        EightQueens eightQueens = new EightQueens();
        eightQueens.addQueen(0);
    }
}

输出结果

shell
第1次的棋子已经放完:[0, 4, 7, 5, 2, 6, 1, 3]
第2次的棋子已经放完:[0, 5, 7, 2, 6, 3, 1, 4]
第3次的棋子已经放完:[0, 6, 3, 5, 7, 1, 4, 2]
第4次的棋子已经放完:[0, 6, 4, 7, 1, 3, 5, 2]
第5次的棋子已经放完:[1, 3, 5, 7, 2, 0, 6, 4]
第6次的棋子已经放完:[1, 4, 6, 0, 2, 7, 5, 3]
第7次的棋子已经放完:[1, 4, 6, 3, 0, 7, 5, 2]
第8次的棋子已经放完:[1, 5, 0, 6, 3, 7, 2, 4]
第9次的棋子已经放完:[1, 5, 7, 2, 0, 3, 6, 4]
第10次的棋子已经放完:[1, 6, 2, 5, 7, 4, 0, 3]
第11次的棋子已经放完:[1, 6, 4, 7, 0, 3, 5, 2]
第12次的棋子已经放完:[1, 7, 5, 0, 2, 4, 6, 3]
第13次的棋子已经放完:[2, 0, 6, 4, 7, 1, 3, 5]
第14次的棋子已经放完:[2, 4, 1, 7, 0, 6, 3, 5]
第15次的棋子已经放完:[2, 4, 1, 7, 5, 3, 6, 0]
第16次的棋子已经放完:[2, 4, 6, 0, 3, 1, 7, 5]
第17次的棋子已经放完:[2, 4, 7, 3, 0, 6, 1, 5]
第18次的棋子已经放完:[2, 5, 1, 4, 7, 0, 6, 3]
第19次的棋子已经放完:[2, 5, 1, 6, 0, 3, 7, 4]
第20次的棋子已经放完:[2, 5, 1, 6, 4, 0, 7, 3]
第21次的棋子已经放完:[2, 5, 3, 0, 7, 4, 6, 1]
第22次的棋子已经放完:[2, 5, 3, 1, 7, 4, 6, 0]
第23次的棋子已经放完:[2, 5, 7, 0, 3, 6, 4, 1]
第24次的棋子已经放完:[2, 5, 7, 0, 4, 6, 1, 3]
第25次的棋子已经放完:[2, 5, 7, 1, 3, 0, 6, 4]
第26次的棋子已经放完:[2, 6, 1, 7, 4, 0, 3, 5]
第27次的棋子已经放完:[2, 6, 1, 7, 5, 3, 0, 4]
第28次的棋子已经放完:[2, 7, 3, 6, 0, 5, 1, 4]
第29次的棋子已经放完:[3, 0, 4, 7, 1, 6, 2, 5]
第30次的棋子已经放完:[3, 0, 4, 7, 5, 2, 6, 1]
第31次的棋子已经放完:[3, 1, 4, 7, 5, 0, 2, 6]
第32次的棋子已经放完:[3, 1, 6, 2, 5, 7, 0, 4]
第33次的棋子已经放完:[3, 1, 6, 2, 5, 7, 4, 0]
第34次的棋子已经放完:[3, 1, 6, 4, 0, 7, 5, 2]
第35次的棋子已经放完:[3, 1, 7, 4, 6, 0, 2, 5]
第36次的棋子已经放完:[3, 1, 7, 5, 0, 2, 4, 6]
第37次的棋子已经放完:[3, 5, 0, 4, 1, 7, 2, 6]
第38次的棋子已经放完:[3, 5, 7, 1, 6, 0, 2, 4]
第39次的棋子已经放完:[3, 5, 7, 2, 0, 6, 4, 1]
第40次的棋子已经放完:[3, 6, 0, 7, 4, 1, 5, 2]
第41次的棋子已经放完:[3, 6, 2, 7, 1, 4, 0, 5]
第42次的棋子已经放完:[3, 6, 4, 1, 5, 0, 2, 7]
第43次的棋子已经放完:[3, 6, 4, 2, 0, 5, 7, 1]
第44次的棋子已经放完:[3, 7, 0, 2, 5, 1, 6, 4]
第45次的棋子已经放完:[3, 7, 0, 4, 6, 1, 5, 2]
第46次的棋子已经放完:[3, 7, 4, 2, 0, 6, 1, 5]
第47次的棋子已经放完:[4, 0, 3, 5, 7, 1, 6, 2]
第48次的棋子已经放完:[4, 0, 7, 3, 1, 6, 2, 5]
第49次的棋子已经放完:[4, 0, 7, 5, 2, 6, 1, 3]
第50次的棋子已经放完:[4, 1, 3, 5, 7, 2, 0, 6]
第51次的棋子已经放完:[4, 1, 3, 6, 2, 7, 5, 0]
第52次的棋子已经放完:[4, 1, 5, 0, 6, 3, 7, 2]
第53次的棋子已经放完:[4, 1, 7, 0, 3, 6, 2, 5]
第54次的棋子已经放完:[4, 2, 0, 5, 7, 1, 3, 6]
第55次的棋子已经放完:[4, 2, 0, 6, 1, 7, 5, 3]
第56次的棋子已经放完:[4, 2, 7, 3, 6, 0, 5, 1]
第57次的棋子已经放完:[4, 6, 0, 2, 7, 5, 3, 1]
第58次的棋子已经放完:[4, 6, 0, 3, 1, 7, 5, 2]
第59次的棋子已经放完:[4, 6, 1, 3, 7, 0, 2, 5]
第60次的棋子已经放完:[4, 6, 1, 5, 2, 0, 3, 7]
第61次的棋子已经放完:[4, 6, 1, 5, 2, 0, 7, 3]
第62次的棋子已经放完:[4, 6, 3, 0, 2, 7, 5, 1]
第63次的棋子已经放完:[4, 7, 3, 0, 2, 5, 1, 6]
第64次的棋子已经放完:[4, 7, 3, 0, 6, 1, 5, 2]
第65次的棋子已经放完:[5, 0, 4, 1, 7, 2, 6, 3]
第66次的棋子已经放完:[5, 1, 6, 0, 2, 4, 7, 3]
第67次的棋子已经放完:[5, 1, 6, 0, 3, 7, 4, 2]
第68次的棋子已经放完:[5, 2, 0, 6, 4, 7, 1, 3]
第69次的棋子已经放完:[5, 2, 0, 7, 3, 1, 6, 4]
第70次的棋子已经放完:[5, 2, 0, 7, 4, 1, 3, 6]
第71次的棋子已经放完:[5, 2, 4, 6, 0, 3, 1, 7]
第72次的棋子已经放完:[5, 2, 4, 7, 0, 3, 1, 6]
第73次的棋子已经放完:[5, 2, 6, 1, 3, 7, 0, 4]
第74次的棋子已经放完:[5, 2, 6, 1, 7, 4, 0, 3]
第75次的棋子已经放完:[5, 2, 6, 3, 0, 7, 1, 4]
第76次的棋子已经放完:[5, 3, 0, 4, 7, 1, 6, 2]
第77次的棋子已经放完:[5, 3, 1, 7, 4, 6, 0, 2]
第78次的棋子已经放完:[5, 3, 6, 0, 2, 4, 1, 7]
第79次的棋子已经放完:[5, 3, 6, 0, 7, 1, 4, 2]
第80次的棋子已经放完:[5, 7, 1, 3, 0, 6, 4, 2]
第81次的棋子已经放完:[6, 0, 2, 7, 5, 3, 1, 4]
第82次的棋子已经放完:[6, 1, 3, 0, 7, 4, 2, 5]
第83次的棋子已经放完:[6, 1, 5, 2, 0, 3, 7, 4]
第84次的棋子已经放完:[6, 2, 0, 5, 7, 4, 1, 3]
第85次的棋子已经放完:[6, 2, 7, 1, 4, 0, 5, 3]
第86次的棋子已经放完:[6, 3, 1, 4, 7, 0, 2, 5]
第87次的棋子已经放完:[6, 3, 1, 7, 5, 0, 2, 4]
第88次的棋子已经放完:[6, 4, 2, 0, 5, 7, 1, 3]
第89次的棋子已经放完:[7, 1, 3, 0, 6, 4, 2, 5]
第90次的棋子已经放完:[7, 1, 4, 2, 0, 6, 3, 5]
第91次的棋子已经放完:[7, 2, 0, 5, 1, 4, 6, 3]
第92次的棋子已经放完:[7, 3, 0, 2, 5, 1, 6, 4]

Process finished with exit code 0

所以我们可以看到,共有92种放法。

排列组合

不多说,如

java
package Recursion;

//排列组合
public class SortTest {
    public static void main(String[] args) {

        char[] array = {'A', 'B', 'C'};
        arrange(array, 0, array.length);
    }

    public static void arrange(char[] array, int start, int len) {
        if (start == len - 1) {
            for (int i = 0; i < array.length; i++)
                System.out.print(array[i]);
            System.out.println();
            return;
        }

        for (int i = start; i < len; i++) {
            char temp = array[start];
            array[start] = array[i];
            array[i] = temp;
            arrange(array, start + 1, len);
            temp = array[start];
            array[start] = array[i];
            array[i] = temp;
        }
    }
}

文章标题:迷宫回溯、八皇后

文章作者:shirtiny

文章链接:https://kizamu.anror.com/posts/data-structures-recursion[复制]

最后修改时间:


商业转载请联系站长获得授权,非商业转载请注明本文出处及文章链接,您可以自由地在任何媒体以任何形式复制和分发作品,也可以修改和创作,但是分发衍生作品时必须采用相同的许可协议。
本文采用CC BY-NC-SA 4.0进行许可。