八皇后问题(递归回溯)

递归回溯

八皇后问题是递归回溯中非常经典的问题,这个问题甚至在计算机产生前就已经存在了
它到底是什么意思呢

八皇后

国际象棋中的皇后,可以横向、纵向、斜向移动。
如何在一个8X8的棋盘上放置8个皇后,使得任意两个皇后都不在同一条横线、竖线、斜线方向上?
让我们来举个栗子,下图的绿色格子是一个皇后在棋盘上的“封锁范围”,其他皇后不得放置在这些格子:
八皇后问题(递归回溯)_第1张图片
下图的绿色格子是两个皇后在棋盘上的“封锁范围”,其他皇后不得放置在这些格子:
八皇后问题(递归回溯)_第2张图片
以高斯为代表的许多数学家先后研究过这个问题。
后来,当计算机问世,通过计算机程序的运算可以轻松解出这个问题。

所谓递归回溯,本质上是一种枚举法。这种方法从棋盘的第一行开始尝试摆放第一个皇后,摆放成功后,递归一层,再遵循规则在棋盘第二行来摆放第二个皇后。如果当前位置无法摆放,则向右移动一格再次尝试,如果摆放成功,则继续递归一层,摆放第三个皇后……

如果某一层看遍了所有格子,都无法成功摆放,则回溯到上一个皇后,让上一个皇后右移一格,再进行递归。如果八个皇后都摆放完毕且符合规则,那么就得到了其中一种正确的解法。
图解这个过程

  1. 第一层递归,尝试在第一行摆放第一个皇后:
    八皇后问题(递归回溯)_第3张图片
  2. 第二层递归,尝试在第二行摆放第二个皇后(前两格被第一个皇后封锁,只能落在第三格):
    八皇后问题(递归回溯)_第4张图片
  3. .第三层递归,尝试在第三行摆放第三个皇后(前四格被第一第二个皇后封锁,只能落在第五格):
    八皇后问题(递归回溯)_第5张图片
  4. 第四层递归,尝试在第四行摆放第四个皇后(第一格被第二个皇后封锁,只能落在第二格):
    八皇后问题(递归回溯)_第6张图片
  5. 第五层递归,尝试在第五行摆放第五个皇后(前三格被前面的皇后封锁,只能落在第四格):
    八皇后问题(递归回溯)_第7张图片
  6. 由于所有格子都“绿了”,第六行已经没办法摆放皇后,于是进行回溯,重新摆放第五个皇后到第八格。:
    八皇后问题(递归回溯)_第8张图片
  7. 第六行仍然没有办法摆放皇后,第五行也已经尝试遍了,于是回溯到第四行,重新摆放第四个皇后到第七格。:
    八皇后问题(递归回溯)_第9张图片

  8. 继续摆放第五个皇后,以此类推……

解决八皇后问题,可以分为两个层面:
1.找出第一种正确摆放方式,也就是深度优先遍历。
2.找出全部的正确摆放方式,也就是广度优先遍历。
之前的文章已经分析过dfs与bfs的适用范围,
dfs适用于判定是否存在可能的解
bfs适用于找到全局最优解
自然的。dfs会找到第一个符合条件的解,而bfs会找到所有存在的解

这里我们用dfs实现

1.表示棋盘

        int size=8;//棋盘8*8
        int num=8;//8个皇后
        //创建棋盘0代表没有落子,1代表该位置有皇后
        //比如chessBoard[3][4]代表四行五列
        int chessBoard[][]=new int[size][size];

2.判断皇后落点是否合法

    public boolean check(int x,int y){
        for (int i = 0; i < y; i++) {
            //纵向检查
            if (chessBoard[x][i]==1) {
                return false;
            }
            //检查左横向
            if (x-1-i>=0&&chessBoard[x-1-i][y-1-i]==1) {
                return false;
            }
            //检查右横向
            if (x+1+i1+i][y-1-i]==1) {
                return false;
            }
        }

        return true;
    }

3.如何回溯递归,这是本算法的核心

    public boolean settleQueen(int n){
        //base case 行数超过8说明已经找到答案,跳出
        if (n==num) {
            return true;
        }
        //遍历当前行,逐一格子验证
        for (int i = 0; i < chessBoard.length; i++) {
            //将当前行清0,避免回溯的时候出现脏数据
            for (int j = 0; j < chessBoard.length; j++) {
                chessBoard[j][n]=0;
            }
            //检查合法性,符合则修改元素并进行下一次递归
            if (check(i, n)) {
                chessBoard[i][n]=1;
                //递归如果返回true说明已经找完了 没有就进行下一步递归
                if (settleQueen(n+1)) {
                    return true;
                }
            }
        }
        return false;//遍历完都未找到满足条件的解
    }

4.输出结果
很简单,直接把二维数组打印

    public static void print(){
        for (int i = 0; i < chessBoard.length; i++) {
            for (int j = 0; j < chessBoard[i].length; j++) {
                System.out.print(chessBoard[i][j]);
            }
            System.out.println();
        }
    }

5.组合程序,就是这个样子

public class EightQueen {
    static int size=8;//棋盘8*8
    static int num=8;//8个皇后
    //创建棋盘0代表没有落子,1代表该位置有皇后
    //比如chessBoard[3][4]代表四行五列
    static int chessBoard[][]=new int[size][size];
    public static void main(String[] args) {
        settleQueen(0);//从第一行开始遍历
        print();//打印结果
    }
    //这里直接用笨办法遍历了,其实这种不断重复的遍历可以通过预处理数组实现优化
    public static boolean check(int x,int y){
        for (int i = 0; i < y; i++) {
            //纵向检查
            if (chessBoard[x][i]==1) {
                return false;
            }
            //检查左横向
            if (x-1-i>=0&&chessBoard[x-1-i][y-1-i]==1) {
                return false;
            }
            //检查右横向
            if (x+1+i1+i][y-1-i]==1) {
                return false;
            }
        }

        return true;
    }
    public static boolean settleQueen(int n){
        //base case 行数超过8说明已经找到答案,跳出
        if (n==num) {
            return true;
        }
        //遍历当前行,逐一格子验证
        for (int i = 0; i < chessBoard.length; i++) {
            //将当前行清0,避免回溯的时候出现脏数据
            for (int j = 0; j < chessBoard.length; j++) {
                chessBoard[j][n]=0;
            }
            //检查合法性,符合则修改元素并进行下一次递归
            if (check(i, n)) {
                chessBoard[i][n]=1;
                //递归如果返回true说明已经找完了 没有就进行下一步递归
                if (settleQueen(n+1)) {
                    return true;
                }
            }
        }
        return false;//遍历完都未找到满足条件的解
    }
    public static void print(){
        for (int i = 0; i < chessBoard.length; i++) {
            for (int j = 0; j < chessBoard[i].length; j++) {
                System.out.print(chessBoard[i][j]);
            }
            System.out.println();
        }
    }
}

其中一种结果

10000000
00000010
00001000
00000001
01000000
00010000
00000100
00100000

你可能感兴趣的:(蓝桥杯,常用算法,Java转C++)