经典回溯算法——八皇后问题



八皇后问题是由19世纪数学家“搞死先生”(高斯先生)提出的,具体的问题是这样的:

在国际象棋的棋盘中(有8×8格)摆放8个皇后,这八个皇后不能相互攻击到(皇后的攻击方向很广:横着,竖着,斜着都能攻击),即8个皇后不能处于同行、同列、同一正反对角线上,这样就不能相互攻击到了。那么,这样的皇后占位的方法,一共有多少种呢?每一种是怎么样的呢?

 

解题思路

  1. 要在棋盘中放置8个皇后,可以一个一个皇后放置到棋盘里,在放皇后的过程中,棋盘中被该皇后所能攻击到的位置要作出标记,以提示之后将要放置的皇后不能在标记的位置中放置,在没有被标记的地方,下一个皇后是可以放置的。

  2. 假如8个皇后仍未全部放置到棋盘中,而棋盘却被全部标记满,则表示前面的皇后放置位置不能满足要求,最好的解决办法就是将在他之前的那一个皇后位置改变。

  3. 以此类推,直到将所有的皇后都能放入到棋盘中为止。

     

    根据解题思路,若不能放置皇后,则返回到它的前面一个皇后中进行修改位置。这就是一个回溯的过程,所以要解决八皇后摆放的问题,也就是可以用回溯算法来解决问题。

     

    对于要回溯问题的解决方法,可以用迭代法和递归法来实现。在这里,我就用递归的方法来讨论这个八皇后问题的解决方法。

     

    思考解题难点

    皇后占位后,所攻击的范围如何标记?

     

    这个标记的方法是一个难点,也是一个解题的突破口,有的朋友认为,可以用一个二维数组进行标记,刚开始二维数组存放全为0,皇后占位后,皇后的位置标记为2,将该皇后所能攻击到的范围在此二维数组中用1来表示(0表示未被标记,12表示被标记而不能占位)。

    这中方法是可行的,也是可以实现的,但还有一种更好的解决方法,这种方法可以不用二维数组,只用一维数组进行标记即可,那么我在这里就只介绍用一维数组进行标记的方法:

  1. 8皇后放置在8×8的棋盘中,所以每一行,每一列都最多只能有一个皇后;

  2. 因此算法执行过程中,可以用一个递归过程的传参进行每一行的摆放问题;

  3. 因为皇后的攻击范围是同行、同列、同一正反对角线的;

  4. 所以用3个一维数组进行标记即可。(一个列标记数组,一个正对角线标记数组,一个反对角线标记数组)

    为什么不用二维数组?

    在这拿列的标记数组举例子,因为在标记的过程中,如果整列需要标记,那么将这一列都标记为1,若没被占位,用0标记即可,所以不需要用到二维数组浪费空间。

  5. 刚开始数组填充全为0,而后如果有占位就用1来表示。

     

    那么列标记与正、反对角线标记中的数组下标有什么关联呢?

    i表示行,用j表示列,那么,正对角线、反对角线与ij的关系为:正对角线的下标正好是i+j,反对角线的关系正好是i-j+size-1。而且正,反对角线的数组长度应该为2*棋盘边长。(size是棋盘的边长以及皇后的数目)

     

    那么由此可以写代码了:

     

    package queen;
    
    public class Queen {
        private final int size;// 定义棋盘大小(顺便用来表示皇后的数目)
        private int[] location;// 用来皇后所在列的位置
        private int[] colsOccupied;// 占领的列
        private int[] cross1Occupied;// 占领的正对角线
        private int[] cross2Occupied;// 占领的反对角线
        private static int count;// 计算方法有多少种
    
        private static final int STATUS_OCCUPIED = 1;
        private static final int STATUS_OCCUPY_CANCELED = 0;
    
        // 初始化棋盘
        public Queen(int size) {
            this.size = size;
            location = new int[size];
            colsOccupied = new int[size];
            cross1Occupied = new int[2 * size];
            cross2Occupied = new int[2 * size];
        }
    
        // 判断这个位置有木有被标记
        private boolean isOccupied(int i, int j) {
            boolean a;
            a = (colsOccupied[j] == STATUS_OCCUPIED)
                    || (cross1Occupied[i - j + size - 1] == STATUS_OCCUPIED)
                    || (cross2Occupied[i + j] == STATUS_OCCUPIED);
            return a;
        }
    
        // 建立占领状态
        private void setStatus(int i, int j, int flag) {
            colsOccupied[j] = flag;
            cross1Occupied[i - j + size - 1] = flag;
            cross2Occupied[i + j] = flag;
        }
    
        // 第一种显示皇后占位方法
        private void printLocation1() {
            System.out.println("第" + count + "种摆放位置");
            for (int i = 0; i < size; i++) {
                System.out.println("行:" + i + "列:" + location[i]);
            }
        }
    
        // 第二种显示皇后占位方法
        private void printLocation2() {
            System.out.println("第" + count + "种摆放方式");
            int[][] printLocation = new int[size][size];
            for (int i = 0; i < size; i++) {
                printLocation[i][location[i]] = STATUS_OCCUPIED;
            }
            for (int[] i : printLocation) {
                for (int j : i) {
                    System.out.print(j + " ");
                }
                System.out.println();
            }
        }
    
        // 在第i行摆放皇后
        public void place(int i) {
            for (int j = 0; j < size; j++) {
                if (!isOccupied(i, j)) {
                    location[i] = j;// 摆放皇后
                    setStatus(i, j, STATUS_OCCUPIED);
                    if (i < size - 1) {
                        place(i + 1);
                    } else {
                        count++;
                        printLocation1();// 用第一种显示方法显示
                        printLocation2();// 用第二种显示方法显示
                    }
                    setStatus(i, j, STATUS_OCCUPY_CANCELED);
                }
            }
        }
    
        public void start() {
            place(0);// 从第0行开始摆放皇后
        }
    
        public static void main(String[] args) {
            new Queen(8).start();
    
        }
    }
    
    原文作者:八皇后问题
    原文地址: https://blog.csdn.net/weixin_42358984/article/details/80621706
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞