题目:
在 𝑛×𝑛 格( 𝑛≤8 )的国际象棋棋盘上摆放 𝑛 个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
输入:
一行一个正整数,表示 𝑛
输出数据:
输出一个正整数,表示 𝑛 皇后摆放的个数
样例:
输入:
8
输出:
92
解题思路:
首先我们看一个完全暴力的方法:从 8×8 的格子里选8个格子,放皇后,然后测试是否满足条件,若满足则结果加1,否则换8个格子继续试。很显然,64 选 8 的方案数为 4426165368,并不是个小数字。
稍加分析,我们可以得到另一个不那么暴力的方法:显然,每行每列最多只能有一位皇后,如果基于这个事实再进行暴力破解,那结果会好很多。安排皇后时,第一行有8种选法,一旦第一行选定,假设选为 (1,𝑖) ,那么第二行只能选 (2,𝑗) ,其中, 𝑗≠𝑖 ,所以有7种选法。以此类推,需要穷举的情况有 8!=40320 种,比十亿级别的小很多了。
之后我们尝试用回溯的方法来解决这个问题。
递归回溯本质上是一种枚举法。这种方法从棋盘的第一行开始尝试摆放第一个皇后,摆放成功后,递归一层,再遵循规则在棋盘第二行来摆放第二个皇后。如果当前位置无法摆放,则向右移动一格再次尝试,如果摆放成功,则继续递归一层,摆放第三个皇后......
如果某一层看遍了所有格子,都无法成功摆放,则回溯到上一个皇后,让上一个皇后右移一格,再进行递归。如果八个皇后都摆放完毕且符合规则,那么就得到了其中一种正确的解法。说起来有些抽象,我们来看一看递归回溯的详细过程。
第二层递归,尝试在第二行摆放第二个皇后(前两格被第一个皇后封锁,只能落在第三格)
第二层递归,尝试在第二行摆放第二个皇后(前两格被第一个皇后封锁,只能落在第三格)
第三层递归,尝试在第三行摆放第三个皇后(前四格被第一第二个皇后封锁,只能落在第五格)
第四层递归,尝试在第四行摆放第四个皇后(第一格被第二个皇后封锁,只能落在第二格)
第五层递归,尝试在第五行摆放第五个皇后(前三格被前面的皇后封锁,只能落在第四格)
由于所有格子都“绿了”,第六行已经没办法摆放皇后,于是进行回溯,重新摆放第五个皇后到第八格。
第六行仍然没有办法摆放皇后,第五行也已经尝试遍了,于是回溯到第四行,重新摆放第四个皇后到第七格。
继续摆放第五个皇后,以此类推......
以上就是 8 皇后问题的经典解法。
我们来思考一下如何将这个过程转为 𝑐++ 程序。
首先我们采用一个二维数组来记录棋盘的情况,如果 𝑚𝑝[𝑖][𝑗]==1 则表示该位置有一个皇后,如果 𝑚𝑝[𝑖][𝑗]==0 则表示该位置是空的。
我们利用递归回溯来解决该问题。递归的过程是逐行试探,从第一行到第 𝑛 行。因此递归的深度是 𝑛。每段递归的过程(处理第 𝑖 行),试着向 𝑚𝑝[𝑖][1]−>𝑚𝑝[𝑖][𝑛] 放一个皇后。
由于是逐行枚举,因此不存在同一行放置多个皇后的情况。但每放置一个皇后,需要检查这一列上有没有放置其他皇后,2 个对角线也是如此。
可以使用 3 个循环来进行枚举。
代码:
#include <bits/stdc++.h>
using namespace std;
int n, ans = 0;
bool mp[15][15];
bool Check(int i, int j)
{
for(int v = 1; v <= n; v++) {
if(mp[v][j])
return false; //检测之前放置的列
}
for(int v = 1; v < i && v < j; v++) {
if(mp[i - v][j - v])
return false; //检测左上到右下的斜线
}
for(int v = 1;v < i && j + v <= n; v++) {
if(mp[i - v][j + v])
return false;
}
return true;
}
void Queen(int i)
{
if (i == n + 1)
{
ans++;
return;
}
for (int j = 1; j <= n; j++)
{
if (Check(i, j))
{
mp[i][j] = true;
Queen(i + 1);
mp[i][j] = false;
}
}
}
int main()
{
cin >> n;
Queen(1);
cout << ans << endl;
return 0;
}
已AC。
注:本文来自51NOD,为小罐头甜编辑。