题目
- 大意:
在国际象棋的棋盘上,按照国际象棋的规则,摆放8个皇后,使得不互相攻击,要求找出所有的解。皇后的攻击范围为同行同列和同对角线。
知识点
- 回溯法
思路
- 在解决这类问题时(是否合法/存在限制条件),我们可以在暴力求解使用递归时同时检查是否已经不满足要求,如果不满足要求则直接递归返回,减少搜索树的大小,就是我们的“回溯法”。
- 在这道题目中,基于皇后的性质,我们必然不可能在一行中有多个皇后,因此我们每一层的遍历可以按照“逐行”或“逐列”搜索,即每一层递归确定一行或一列可以放置的皇后的位置,记录当前的状态(当然这个状态是临时的,因为该层满足条件状态不唯一)然后逐渐向下递归。若条件不满足或到达递归边界后一层一层返回到当前状态,更新下一个可能的状态。如果当前所有可能的状态更新完毕,则返回上一层(去更新可能的状态)
- 这里的主对角线与副对角线的判断方法有一个巧妙之处,我们看到“列-行”与“列+行”可以代表的主对角线和副对角线是唯一的,因此我们可以用这样的方式去判断某一条主对角线或副对角线是否已经被放置皇后:
!vis[0][i] && !vis[1][cur - i + 8] && !vis[2][cur + i]
,这里使用的vis[3][15]
,vis[0]
代表某列,vis[1]
代表某一主对角线,vis[2]
代表某一副对角线。在判断时候我们的主对角先使用了cur - i + 8
是因为主对角线的表示可能为负,如下图所示:
![](https://img-blog.csdnimg.cn/20210201221847930.png)
图1 - 列-行唯一标识了主对角线
代码
# include <iostream>
using namespace std;
int vis[3][15]; // 记录是否已放置
int result[8][8]; // 用于输出结果
void search(int cur) { // cur - 递归的层数,每一层相当于在第cur行放置一个queen
if (cur >= 8) {
// 到达递归边界,输出一个结果(此时到达这里的,必然满足条件,不满足的在前面直接已经返回)
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) cout << result[i][j] << " ";
cout << endl;
}
cout << endl;
return; // 不要忘记返回
}
for (int i = 0; i < 8; i++) { // 在第cur行第i列放置chess的情况
// 第i列、(i-cur) 号主对角线、(cur+i) 号副对角线都没放置,则可以更新为该状态
if (!vis[0][i] && !vis[1][cur - i + 8] && !vis[2][cur + i]) {
// 更新状态,第i列、(i-cur) 号主对角线、(cur+i) 号副对角线设置为已放置
vis[0][i] = vis[1][cur - i + 8] = vis[2][cur + i] = 1;
result[cur][i] = 1; // 记录当前状态
search(cur + 1); // 向下递归
// 递归回来还原之前的状态!!切记
vis[0][i] = vis[1][cur - i + 8] = vis[2][cur + i] = 0;
result[cur][i] = 0; // 记录当前结果
}
}
}
int main() {
memset(vis, 0, sizeof(int[3][8])); // 初始化
search(0);
return 0;
}
过程中遇到的问题 & 解决
- 使用列-行标识唯一主对角线时候,注意可能出现的负数的情况
- 回溯法一定要记得在状态递归返后,还原之前的状态,再去寻找当前递归层下一个可能的状态
- 到达递归边界不要忘记return,不然程序会出现异常
测试
输入:
/
结果:✔️(只列举了两个)
1 0 0 0 0 0 0 0
0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 1
0 0 0 0 0 1 0 0
0 0 1 0 0 0 0 0
0 0 0 0 0 0 1 0
0 1 0 0 0 0 0 0
0 0 0 1 0 0 0 0
1 0 0 0 0 0 0 0
0 0 0 0 0 1 0 0
0 0 0 0 0 0 0 1
0 0 1 0 0 0 0 0
0 0 0 0 0 0 1 0
0 0 0 1 0 0 0 0
0 1 0 0 0 0 0 0
0 0 0 0 1 0 0 0
...