美文网首页数据结构与算法
08-递归-回溯-8皇后问题

08-递归-回溯-8皇后问题

作者: 紫荆秋雪_文 | 来源:发表于2022-07-05 15:24 被阅读0次

    铭记:

    \color{#DC143C}{算法 + 数据结构 = 程序}

    源码下载

    一、场景

    • 八皇后问题:在一个8X8的棋盘上,放置8个棋子
    • 要求:任意两个棋子不能处于同一行、同一列、同一斜线。问有多少摆法?

    二、思路

    • 1、在二维数组中,从第一行的第一列开始一个一个放置,直到第八列
    • 2、从第二行的第一列开始一个一个放置,直到第八列
    • 3、不断的执行1、2步骤,判断是否可以放置皇后
    • 4、这里会涉及到回溯的问题:
    • 4.1、回溯的第一种情况:当某一行(第5行)都不适合放置皇后,那么就需要回溯到第4行,并且说明第4行当前位置放置的皇后是错误的,需要撤销并且把皇后放置到下一列再试尝试1,2步骤。
    • 4.2、回溯的第二种情况:完成一次放置(如:7 2 0 5 1 4 6 3)需要逐层回退(在1-5位置不变的情况下)移动第6行的皇后到下一列,然后再继续1,2步骤
    • 回溯:表示我们之前的走法是行不通或者是行得通但是要尝试另一条路,例如步骤4中的回溯情况。例如回溯情况4.2,由于已经把第6、7、8行都已经走过,所以需要把回溯行到最后一行重新恢复原样(防止判断错误)

    三、Queen8

    package com.raven.alg.s5recursion;
    
    /**
     * 8 皇后问题
     * 思路:
     * 首先放置第一个皇后
     * 固定在第一行,从第一列开始
     * (0, 1),(0, 2),(0, 3),(0, 4),(0, 5),(0, 6),(0, 7)
     */
    public class Queen8 {
    
        // 已使用,需要跳过
        private final static Integer SUCCESS = 1;
        private final static Integer FAIL = 2;
        private final static Integer HS = 3;
    
        private static Boolean isHS = false;
        private static Boolean isFinish = false;
        private static Integer count = 0;
    
        public static void run(Integer size) {
            if (size < 1) {
                throw new RuntimeException("棋盘不能小于0");
            }
            // 1、创建棋盘
            int[][] mg = initMigong(size);
            // 棋盘
            for (int i = 0; i < size; i++) {
                for (int j = 0; j < size; j++) {
                    System.out.print(mg[i][j] + " " + " ");
                }
                System.out.println();
            }
            // 第一个皇后的位置
            go(mg, 0);
            System.out.println("Count = " + count);
        }
    
        private static void go(int[][] mg, int startX) {
            // 已放满
            if (startX == mg.length) {
                count++;
                System.out.println("已完成第 " + count + " 次");
                print2(mg);
    
                print(mg);
                isFinish = true;
                System.out.println();
                return;
            }
    
            // 从 0 ~ 7
            for (int i = 0; i < mg.length; i++) {
                // 处理回溯
                if (isHS || isFinish) {
                    isHS = false;
                    recall(mg, startX);
                }
                // 完成时进行下一遍是需要把当前回溯
                if (isFinish) {
                    isFinish = false;
                    recall(mg, startX);
                }
                mg[startX][i] = 1;
                if (SUCCESS == check(mg, startX, i)) {
                    go(mg, startX + 1);
                } else {
                    mg[startX][i] = 2;
                }
            }
        }
    
        /**
         * 校验当前位置的皇后是否与已经存在的皇后冲突
         * <p>
         * 冲突:不能在同一列、同一行、一条斜线上
         *
         * @param mg
         * @param startX
         * @param startY
         * @return
         */
        private static Integer check(int[][] mg, int startX, int startY) {
    
            for (int i = 0; i < startX; i++) {
                for (int j = 0; j < mg.length; j++) {
                    int hh = mg[i][j];
                    boolean isX = i == startX;
                    boolean isY = j == startY;
                    Boolean isXX = Math.abs(i - startX) == Math.abs(j - startY);
                    if (hh == SUCCESS && (isX || isY || isXX)) {
                        // 需要回溯
                        if (mg.length - 1 == startY) {
                            isHS = true;
                        }
                        return FAIL;
                    }
                }
            }
            return SUCCESS;
        }
    
    
        /**
         * 回溯
         *
         * @param mg
         * @param startX
         * @return
         */
        private static void recall(int[][] mg, int startX) {
            for (int i = startX; i < mg.length; i++) {
                for (int j = 0; j < mg.length; j++) {
                    if (1 == mg[i][j]) {
                        mg[i][j] = HS;
                    }
                }
            }
        }
    
    
        /**
         * 打印
         *
         * @param mg
         */
        private static void print2(int[][] mg) {
            for (int i = 0; i < mg.length; i++) {
                for (int j = 0; j < mg.length; j++) {
                    if (1 == mg[i][j]) {
                        System.out.print(j + "   ");
                    }
                }
            }
            System.out.println();
        }
    
        /**
         * 打印
         *
         * @param mg
         */
        private static void print(int[][] mg) {
            System.out.println("已完成第 " + count + " 次8皇后图:");
            for (int i = 0; i < mg.length; i++) {
                for (int j = 0; j < mg.length; j++) {
                    System.out.print(mg[i][j] + "   ");
                }
                System.out.println();
            }
        }
    
        /**
         * 初始化棋盘
         *
         * @param size
         * @return
         */
        private static int[][] initMigong(Integer size) {
            if (size < SUCCESS) {
                throw new RuntimeException("迷宫不能小于0");
            }
            // 创建迷宫
            int[][] mg = new int[size][size];
            return mg;
        }
    
    }
    
    

    四、这种方法更加取巧

    这个方法比较取巧,但是思路一样,定义一个一维数组arr = {0 , 4, 7, 5, 2, 6, 1, 3},arr的下标代表第几行的皇后,arr的值表示皇后所在的列。

    package com.raven.alg.s5recursion;
    
    public class Queue82 {
    
        //数组大小
        int max = 8;
        // arr = {0 , 4, 7, 5, 2, 6, 1, 3}
        int[] array = new int[max];
        static int count = 0;
        static int judgeCount = 0;
        public static void main(String[] args) {
            Queue82 queue8 = new Queue82();
            queue8.check(0);
            System.out.printf("共有%d种ⷨ", count);
            System.out.printf("共回溯%d次", judgeCount); // 1.5w
            System.out.println();
        }
    
    
    
        private void check(int n) {
            if(n == max) {  //n = 8 代表防止成功
                print();
                return;
            }
    
            //表示每个皇后都是从0列开始
            for(int i = 0; i < max; i++) {
                //第n+1个皇后防止在第i+1列表
                array[n] = i;
                //判断是否可以放置皇后在n+1位置
                if(judge(n)) {
                    // 防止下一行
                    check(n+1);
                }
            }
        }
    
        /**
         *  判断是否可以放置皇后
         * @param n
         * @return
         */
        private boolean judge(int n) {
            judgeCount++;
            for(int i = 0; i < n; i++) {
                //1. array[i] == array[n]  表示在同一列
                //2. Math.abs(n-i) == Math.abs(array[n] - array[i]) 表示在一个斜线上
                // n = 1  ���õ� 2�� 1 n = 1 array[1] = 1
                // Math.abs(1-0) == 1  Math.abs(array[n] - array[i]) = Math.abs(1-0) = 1
                if(array[i] == array[n] || Math.abs(n-i) == Math.abs(array[n] - array[i]) ) {
                    return false;
                }
            }
            return true;
        }
    
        //输出
        private void print() {
            count++;
            for (int i = 0; i < array.length; i++) {
                System.out.print(array[i] + " ");
            }
            System.out.println();
        }
    
    }
    
    

    相关文章

      网友评论

        本文标题:08-递归-回溯-8皇后问题

        本文链接:https://www.haomeiwen.com/subject/bwesbrtx.html