屈婉玲版《算法设计与分析》JavaScript实现-n皇后问题

127 阅读1分钟

屈老师关于使用回溯来解决N皇后问题的讲解视频地址:www.bilibili.com/video/BV1Ls… ,屈老师的讲解逻辑缜密,通俗易懂,这里不再画蛇添足,主要着眼于代码的实现,也算是抛砖引玉。

/**
 *  n皇后问题,找到并返回一个可行的解 
 * @param {number} n number of queens to be placed
 * @returns 
 */
function queenPlacer(n) {
    const root = new _Node(null, n);
    return solve(root, n);

    function solve(node) {
        if (!node)
            return;
        // 路径上节点已满,表示找到了可行方案,直接返回
        if (node.path.length >= n)
            return node.path;
        //以此标记是否继续在孩子节点中找没有冲突的下一个节点
        let continuable = true,
            path = node.path,
            pathLen = path.length,
            children = node.children,
            // 表示当前行的索引值
            curIndex = pathLen;
        // 寻找孩子节点中下一个与路径上不冲突的节点
        // 即: 对路径上任意Pi都有 child != Pi,
        // 并且不在对角线上,即当前路径值加减索引差值不等于该孩子节点的值 
        while (children.length > 0) {
            for (let j = 0; j < pathLen; j++) {
                let diff = curIndex - j;
                // 检测到当前孩子节点的值与路径上的某个节点冲突,则结束对该孩子的检测
                if (path[j] == children[0] || path[j] - diff == children[0] || path[j] + diff == children[0]) {
                    continuable = false;
                    break;
                }
            }
            // 该孩子没有冲突,结束遍历
            if (continuable)
                break;
            // 该孩子有冲突,将其移除队列, 并继续遍历剩下的孩子
            else {
                children.shift();
                continuable = true;
            }
        }
        // 还有孩子,表示孩子头没有冲突,递归搜索此节点
        if (children.length > 0) {
            const child = new _Node(node, n);
            return solve(child);
        } 
        // 没有孩子了,则回溯到父亲节点
        else {
            return solve(node.parent);
        }
    }


    function _Node(parent, n) {
        this.parent = parent || null;
        // 从父亲子节点队列中出列一个值作为本次遍历的节点
        // 当回溯时,由于此节点已经出队,所以可以继续从队头开始遍历其它未访问的节点
        this.value = this.parent ? parent.children.shift() : 0;
        // n皇后问题中每个节点都是一棵n叉完全树的根
        this.children = new Array(n).fill(0).map((_, idx) => idx + 1);
        // 路径继承自父节点,并且将本节点的值加入其中
        this.path = this.parent ? [...parent.path, this.value] : [];
    }
}

测试代码:

let result = queenPlacer(8);
    console.log(result.toString());
    for (let i = 0; i < 8; i++) {
        let info = '';
        for (let j = 0; j < 8; j++) {
            if (result[i] - 1 == j) {
                info += '|+';
            } else {
                info += '| ';
            }
        }
        info += '|';
        console.log(info);
    }

终端显示结果:

1,5,8,6,3,7,2,4
|+| | | | | | | |
| | | | |+| | | |
| | | | | | | |+|
| | | | | |+| | |
| | |+| | | | | |
| | | | | | |+| |
| |+| | | | | | |
| | | |+| | | | |