JavaScript解决N皇后问题

326 阅读5分钟

「这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战」。

说在前面

N皇后是递归回溯算法中的经典题目,而说起N皇后,我们就会想想起经典的八皇后问题,八皇后问题英文Eight queens),是由国际象棋棋手马克斯·贝瑟尔于1848年提出的问题,是回溯算法的典型案例。

问题表述为:在8×8格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。如果经过±90度、±180度旋转,和对角线对称变换的摆法看成一类,共有42类。计算机发明后,有多种计算机语言可以编程解决此问题。

同理现在可以将八皇后延伸至N皇后,也是一样的规则。

描述

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。

示例 1:

N皇后.png 输入:n = 4 输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] 解释:如上图所示,4 皇后问题存在两个不同的解法。

思路

使用深搜和回溯的思路来进行解题,遍历整个棋盘,判断当前位置能否放置棋子,能放的话就给当前位置添加标记,然后往下一层继续搜索填充,不能放的话则进行回溯,回退到上一个标记点,取消标记并继续搜索,重新寻找满足条件的位置,不断循环这些步骤。

一行一行地摆放,在确定一行中的那个皇后应该摆在哪一列时,需要当前列是否合法,如果合法,则将皇后放置在当前位置,并进行递归,回溯。每行都摆满皇后时,则产生了一种解法,将所有解法收集并返回。 判断合法:当前将要摆放’Q’的位置和其他已摆放‘Q’的位置不能在同一列,且不能在同一条45度斜线或135度斜线上。这里判断是否在同一条斜线上可通过当前将要摆放’Q’的位置和其他已摆放‘Q’的位置横坐标之差和纵坐标之差的绝对值是否相等来判断。

八皇后问题如果用穷举法需要尝试8的8次方也就是16,777,216种情况。每一列放一个皇后,可以放在第 1 行,第 2 行,……,直到第8行。穷举的时候从所有皇后都放在第1行的方案开始,检验皇后之间是否会相互攻击。如果会,把列H的皇后挪一格,验证下一个方案。移到底了就“进位”到列G的皇后挪一格,列H的皇后重新试过全部的8行。这种方法是非常低效率的,因为它并不是哪里有冲突就调整哪里,而是盲目地按既定顺序枚举所有的可能方案。

回溯算法优于穷举法。将列A的皇后放在第一行以后,列B的皇后放在第一行已经发生冲突。这时候不必继续放列C的皇后,而是调整列B的皇后到第二行,继续冲突放第三行,不冲突了才开始进入列C。如此可依次放下列A至E的皇后,如图2所示。将每个皇后往右边横向、斜向攻击的点位用叉标记,发现列F的皇后无处安身。这时回溯到列E的皇后,将其位置由第4行调整为第8行,进入列F,发现皇后依然无处安身,再次回溯列E。此时列E已经枚举完所有情况,回溯至列D,将其由第2行移至第7行,再进入列E继续。按此算法流程最终找到如图3所示的解,成功在棋盘里放下了8个“和平共处”的皇后。继续找完全部的解共92个。

回溯算法求解N皇后问题的原则是:有冲突解决冲突,没有冲突往前走,无路可走往回退,走到最后是答案。为了加快有无冲突的判断速度,可以给每行和两个方向的每条对角线是否有皇后占据建立标志数组。放下一个新皇后做标志,回溯时挪动一个旧皇后清除标志。

代码

/**
 * @param {number} n
 * @return {string[][]}
 */
var solveNQueens = function(n) {
    var t = [],
        temp = [],
        res = [];
    for(let i = 0; i < n; i++){
        t.push(0);
    }
    for(let i = 0; i < n; i++){
        temp.push([...t]);
    }
    //判断能否摆放
    var jungle = function(i,j,n){
        //横
        for(let k = 0;k < n; k++){
            if(temp[i][k] == 1) return 0;
        }
        //竖
        for(let k = 0;k < n; k++){
            if(temp[k][j] == 1) return 0;
        }
        //斜
        for(let k = 1; k + j < n && i - k >= 0; k++){
            if(temp[i - k][j + k] == 1) return 0;
        }
        for(let k = 1; k + i < n && j - k >= 0; k++){
            if(temp[i + k][j - k] == 1) return 0;
        }
        for(let k = 1; k + i < n && j + k < n; k++){
            if(temp[i + k][j + k] == 1) return 0;
        }
        for(let k = 1; i - k >= 0 && j - k >= 0; k++){
            if(temp[i - k][j - k] == 1) return 0;
        }
        return 1;
    }
    //深搜遍历
    var dfs = function(e,n){
        for(let i = 0;i < n; i++){
            if(temp[e][i] == 0 && jungle(e,i,n)){
                // console.log(e,i);
                temp[e][i] = 1;
                // console.log(temp);
                if(e == n-1){
                    // console.log(temp);
                    var ans = [];
                    for(let i1 = 0; i1 < n; i1++){
                        var a = "";
                        for(let i2 = 0; i2 < n; i2++){
                            if(temp[i1][i2] == 0) a += ".";
                            else a += "Q";
                        }
                        ans.push(a);
                    }
                    res.push(ans);
                }else{
                    dfs(e+1,n);
                }
            }
            temp[e][i] = 0;
        }
    }
    dfs(0,n);
    return res;
};