屈老师关于使用回溯来解决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
|+| | | | | | | |
| | | | |+| | | |
| | | | | | | |+|
| | | | | |+| | |
| | |+| | | | | |
| | | | | | |+| |
| |+| | | | | | |
| | | |+| | | | |