前言
今天分享前端面试算法的经典问题--八皇后问题
八皇后问题是一个经典的回溯算法问题。该问题是在一个 8x8 的棋盘上放置 8 个皇后,使得它们彼此不互相攻击,即任意两个皇后都不在同一行、同一列和同一对角线上。这个问题可以用回溯算法解决,即尝试在棋盘上放置皇后,如果发现某位置不合法,就撤销该位置的皇后,并继续尝试下一个位置。
就像下面这样
这张图中的每个棋子就符合八皇后的排列
手动摆貌似可以解决这个问题,但是需要穷尽所有的排列可能,就非人力可为了
解题思路
什么是回溯算法呢?回溯算法是一种解决组合问题(如组合、排列等)的通用算法。它通过不断地尝试和撤销,从问题的初始状态出发,搜索问题的解空间,直到找到一个符合条件的解为止。也就是说 回溯算法的核心思想是"选择-检查-撤销",即在尝试解决一个问题时,先从当前的选择中选择一个方案,然后检查该方案是否满足问题的要求,如果满足,则继续尝试下一个方案;如果不满足,则撤销该方案,回到上一个选择,尝试其他方案。
回到话题,那八皇后可以描述为以下步骤:
- 初始化一个 8x8 的棋盘,所有位置都没有放置皇后。
- 选择一个位置放置第一个皇后。
- 检查该位置是否合法,即是否满足任意两个皇后都不在同一行、同一列和同一对角线上。
- 如果该位置合法,则继续尝试下一个位置放置第二个皇后。
- 重复步骤 3 和 4,直到放置完所有 8 个皇后。
- 如果成功放置了所有 8 个皇后,则输出解决方案。
是不是很简单,对,就是这么简单
代码实现
javascript
const eightQueue = (array, line) => {
// 如果当前行数已经超过数组长度,说明已经找到了一个解决方案
if (line >= array.length) {
printQueue(array);
console.log("------");
return;
}
for (let position = 0; position < array.length; position++) {
// 如果当前位置可以放置皇后,则将其放置到当前行的位置上
if (isOK(array, line, position)) {
array[line] = position;
eightQueue(array, line + 1);
}
}
};
const isOK = (array, row, position) => {
let left = position - 1,
right = position + 1;
// 从上一行的位置开始,向前遍历每一行
for (let i = row - 1; i >= 0; i--) {
// 如果当前位置与当前位置冲突,或者与当前位置的左侧或右侧冲突,则返回 false
if (array[i] == position) return false;
if (left >= 0 && array[i] == left) return false;
if (right < array.length && array[i] == right) return false;
// 更新左右两侧皇后的位置
left--;
right++;
}
// 如果没有冲突,则返回 true
return true;
};
const printQueue = (array, line = 0) => {
// 如果当前行数已经超过数组长度,则不需要打印棋盘
if (line >= array.length) return;
let res = "";
// 打印棋盘
for (let i = 0; i < array.length; i++) {
if (array[line] == i) {
res += "# ";
} else {
res += "- ";
}
}
console.log(res);
// 递归打印下一行的棋盘
printQueue(array, line + 1);
};
代码中定义了三个函数
- eightQueue 函数:定义了一个递归函数,用于解决八皇后问题。输入参数是一个数组 array 和当前处理到的行数 line。
- isOK 函数:定义了一个检查当前位置是否可以放置皇后的函数。输入参数是一个数组 array、当前处理到的行数 row 和当前位置 position。
- printQueue 函数:定义了一个打印棋盘的函数。输入参数是一个数组 array 和当前处理到的行数 line。
对于每个可以放置皇后的位置,调用 isOK 函数检查该位置是否满足条件。如果满足条件,将该位置放置到当前行的位置上,并递归调用 eightQueue 函数处理下一行。
在 isOK 函数中,首先定义了左右两个皇后位置的索引 left 和 right,初始值分别为 position - 1 和 position + 1。然后,从上一行的位置开始,向前遍历每一行,检查列是否冲突,左上有没有冲突,右上有没有冲突。如果冲突,则返回 false。最后,如果没有冲突,则返回 true
如果当前行数已经超过数组的长度,说明已经找到了一个解决方案,调用 printQueue 函数打印棋盘,并输出一个分隔符 "------"。
测试代码
测试代码看看效果吧
javascript
eightQueue(Array(8).fill(-1), 0);
现将所有的数组内容初始化为-1,然后从第 0 行开始调用
这是打印截图。统计了下,共有 92 种排列方式,只能提供一部分截图咯
总结
这篇文章分享了前面面试的经典问题--八皇后。解决问题的思路是不断地"选择-检查-撤销",即在尝试解决一个问题时,先从当前的选择中选择一个方案,然后检查该方案是否满足问题的要求,如果满足,则继续尝试下一个方案;如果不满足,则撤销该方案,回到上一个选择,尝试其他方案。对于八皇后,就是不断地尝试每一个位置,看看可不可行,如果不可行,就放到下一个位置。
你觉得这篇文章怎么样?我每天都会分享一篇算法小练习,喜欢就点赞+关注吧