What ! N皇后!!QNMD!!!
别…别怕,就…就是一道回…
回溯
而已(确信)
题目描述
在
n
×
n
n×n
n×n格的国际象棋上摆放
n
n
n个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,如下图
(
a
)
(a)
(a)所以示
( b ) (b) (b)是一个可行解,用序列2 4 6 1 3 5来表示,第 i i i 个数表示在第 i i i 行的相应位置有一个棋子。这只是 6 6 6 皇后问题的一个解。请编一个程序找出 n n n 皇后的所有解。
输入格式
一行一个整数 n。
输出格式
按题目所说的序列方法输出,解按字典顺序排列。请输出前 3 个解(不足 3 个就全部输出)。最后一行是解的总个数。
样例
输入样例
6
输出样例
2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4
数据范围与提示
6 ≤ n ≤ 13 6\le n \le 13 6≤n≤13
思路分析
因为每个皇后都必须分别占据一行,我们需要做的不过是棋盘上的每个皇后分配一列。下面我们用4皇后的求解过程来讲解算法思路:从空棋盘开始,然后把皇后1 放到它所在行的第-一个可能位置上,也就是第一-行第一列。对于皇后2,在经过第-列和第二列的失败尝试之后,我们把它放在第一个可能的位置,就是格子(2, 3),位于第二行第三列的格子。这被证明是一个死胡同,因为皇后3将没有位置可放。所以,该算法进行回溯,把皇后2放在下一个可能位置(2,4)上。这样皇后3就可以放在(3, 2),这被证明是另一个死胡同。该算法然后就回溯到底,把皇后1移到(1,2)。 接着皇后2到(2,4), 皇后3到(3,1), 而皇后4到(4, 3), 这就是该问题的一个解。
整个过程实际上就是一个状态树的遍历
过程
下图为状态树
就我个人对于回溯法的理解,我认为回溯法本质就是一种深度优先搜索算法(DFS)
。回溯法可以被有效地应用在具有树状搜索空间的搜索问题中,主要用于解决搜索空间非常大,但是可行解非常有限的问题。而可以进行回溯的条件则是一个待搜索的结果在搜索结果完全展现之前便可以提前作出判断,判定该结果是否为合法结果,从而减少没必要的搜索,以此来实现高效率的深度优先搜索。对于回溯法的直观理解,可以认为你的搜索路径在一棵树中进行,每次进一步搜索则会进入到下一层的节点中,而子节点则根据从左到右的顺序逐个搜索。如果发现从某个节点开始搜索结果不可行,则他的所有子节点都不需要进行搜索,下一层的搜索问题则回溯到这一层中,因此称之为回溯法。回溯法里找到有效的减少冗余搜索的方法很重要,可以大大地减少需要搜索的空间。回到这个题目,要将n个皇后放在
n
×
n
n×n
n×n的棋盘上,由于皇后不能在同一行同一列中,因此解必然是每行每列中有一个皇后,因此求所有的解法就是找每一列中皇后可以放在哪一行上的所有摆法。而这里用来减少冗余搜索的判断当然就是皇后之间能否互相攻击,即是否有皇后在同一行同一列或者同一斜线上。
WA AC Code
#include <iostream>
using namespace std;
int n, m = 0;
int ans[15];
int arr[15]; // arr[15] -> 标记列
int brr[100]; // brr[25] -> 正对角线
int crr[100]; // crr[25] -> 反对角线
void dfs(int x){
if(x > n) { // 边界
m++;
if(m <= 3) {
for(int i = 1; i <= n; i++) {
cout << ans[i] << " ";
}
cout << endl;
}
return ;
}
for(int i = 1; i <= n; i++) {
if(arr[i] == 0 && brr[x - i + n - 1] == 0 && crr[x + i + n - 1] == 0) {// 检查标记
ans[x] = i;
arr[i] = 1;
brr[x - i + n - 1] = 1;//根据规律(2,1),(3,2),(4,3)...
crr[x + i + n - 1] = 1;//根据规律(6,1),(5,2),(4,3)...
dfs(x + 1);// 递归
arr[i] = 0;
brr[x - i + n - 1] = 0;
crr[x + i + n - 1] = 0; // 回溯
}
}
}
int main() {
cin >> n;
dfs(1);
cout << m;
return 0;
}