N 皇后经典算法解析 (JavaScript 版本)

837 阅读3分钟

前言

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

皇后的走法是:可以横直斜走,格数不限。因此要求皇后彼此之间不能相互攻击,等价于要求任何两个皇后都不能在同一行、同一列以及同一条斜线上。

LeetCode 链接 51.N皇后

接下来我介绍一种基于集合的回溯解法

基于集合的回溯

回溯算法是一种遍历算法,以深度优先遍历 的方式尝试所有的可能性。有些教程上也叫「暴力搜索」。回溯算法是 有方向地 搜索,区别于多层循环实现的暴力法。

思路分析

以 4 皇后问题为例,它的「搜索」过程如下。大家可以在纸上模拟下面这个过程:

image.png

搜索的过程蕴含了 剪枝 的思想。「剪枝」的依据是:题目中给出的 「N 皇后」 的摆放规则:1、不在同一行;2、不在同一列;3、不在同一主对角线方向上;4、不在同一副对角线方向上。

利用一个数组记住皇后的位置

image.png

考虑对角线(找规律)

在每一个单元格里写下行和列的 下标。会发现主对角线上的元素 ( 横坐标 - 纵坐标 )的值相等,副对角线上的元素( 横坐标 + 纵坐标 ) 的值相等

image.png

参考代码

/**
 * @param {number} n
 * @return {string[][]}
 */
var solveNQueens = function (n) {
    const solutions = []
    dfs(solutions, 0, [], new Set(), new Set(), new Set(), n)
    return solutions
};
/**
 * @param {string[][]} solutions 所有解
 * @param {number} row 当前搜索行
 * @param {number[]} path 存储皇后的数组
 * @param {set} columns 存在皇后的列集合
 * @param {set} diagonal1 存在皇后的主对角线集合
 * @param {set} diagonal2 存在皇后的副对角线集合
 * @param {number} n 皇后总数
 * @return {void}
 */
 // 因为是深度优先 所以皇后占用的 rows 这个集合被省略,深度优先算法在每一行一旦找到一个皇后会进入下一行探索
function dfs(solutions, row, path, columns, diagonal1, diagonal2, n) {
    // 到了最后一行任然满足条件则会继续调用下一行 此时 row === n 说明所有皇后已经放置完毕
    if (row === n) {
        //将结果保存在 solutions 数组内
        solutions.push(createBoard(path, n))
        return
    }
    // 搜索当前行的每一列是否可以放置皇后
    for (let i = 0; i < n; i++) { 
        // 判断是否满足当前坐标所在的 行、列、两个对角线均不存在皇后的条件,不满足条件的分支则被裁剪不会继续探索下一个深度
        if (!columns.has(i) && !diagonal1.has(row - i) && !diagonal2.has(row + i)) { 
            // 满足条件后放置皇后并且记录新放置皇后占用的列和对角线
            path.push(i)
            columns.add(i)
            diagonal1.add(row - i)
            diagonal2.add(row + i)
            // 搜索下一行
            dfs(solutions, row + 1, path, columns, diagonal1, diagonal2, n)
            // 深度优先算法调用完毕后已经完成了当前列的所有尝试,满足条件的结果已经被保存进了 solutions 数组内,尝试下一列就需要去掉当前列所在皇后占用的列和对角线
            // 回溯
            const oldColumn = path.pop()
            columns.delete(oldColumn)
            diagonal1.delete(row - oldColumn)
            diagonal2.delete(row + oldColumn)
        }
    }

}
/**
 * @param {number[]} path 保存皇后的路径
 * @return {string[][]} 棋盘
 */
function createBoard(path, n) {
    const board = []
    for (let i = 0; i < path.length; i++) {
        let item = ''
        for (let j = 0; j < n; j++) {
            if (j === path[i]) {
                item += 'Q'
            } else {
                item += '.'
            }
        }
        board.push(item)
    }
    return board
}

提交测试

image.png