问题简介:
在棋盘上放置8个皇后,使它们不同行,不同列,不同对角线。问有多少种合法的情况?
N皇后问题是8皇后问题的扩展。
![一种可能的情况](https://i-blog.csdnimg.cn/blog_migrate/a5f86b36c6bdc27846a0b6c7b4cbb956.png)
![过程示意图](https://i-blog.csdnimg.cn/blog_migrate/20110d14757f22214f8826b548412464.png)
其实n皇后问题就是对所有的皇后进行枚举,我们采用递归的写法,其中有两个技巧就是回溯和剪枝。当我们发现当前情况已经不满足时,可以将这棵搜索树直接在此处剪短,然后回溯到上一个结点,继续进行枚举。
Key1
如何递归?
每一行放置一个皇后
void dfs(int r) // 一行一行的放置皇后,本次在第r行开始
// r是从0开始计数的
{
if (r == n) { // 所有皇后都放置好了之后, 递归返回
ans++; // ans记录合法的棋局个数
return ;
}
for (int i = 0; i < n; i++) {
queen[r] = i; // 在第r行的c列放置皇后
if (check(r, queen[r])) { // 检查是否合法
dfs(r + 1); // 继续放下一行皇后
}
}
}
Key2
如何剪枝?
- 设左上角是原点(0, 0)
- 已经防止好的皇后的坐标是(i, j)
- 新皇后的坐标是(r, c)
原皇后(i, j), 新皇后(r, c)
(1) 横向,不同行: i != r
(2) 纵向,不同列: j != c
(3)斜对角,从(i, j)向斜对角走a步,走到新坐标(r, c)有以下四种情况。
- 左上角: (i - a, j - a)
- 右上角: (i + a, j - a)
- 左下角: (i - a, j + 1)
- 右下角: (i + a, j + a)
bool check(int r, int c)
{
for (int i = 0; i < r; i++) {
if (queen[i] == c || abs(queen[i] - c) == abs(i - r))
// 检查同列, 同一对角线是否冲突
return false;
}
return true;
}
复杂度分析dfs()一行行的放置皇后,复杂度O(n!)
check()冲突检查O(n)
总的复杂度O(n x n!)
HDU–2553代码如下:
#include <bits/stdc++.h>
using namespace std;
const int maxr = 10;
int n, ans;
int queen[maxr];
bool check(int r, int c)
{
for (int i = 0; i < r; i++) {
if (queen[i] == c || abs(queen[i] - c) == abs(i - r))
return false;
}
return true;
}
void dfs(int r)
{
if (r == n) {
ans++;
return ;
}
for (int i = 0; i < n; i++) {
queen[r] = i;
if (check(r, queen[r])) {
dfs(r + 1);
}
}
}
int main()
{
int res[maxr];
for (int i = 0; i < maxr; i++) {
memset(queen, 0, sizeof queen);
ans = 0;
n = i + 1;
dfs(0);
res[i] = ans;
}
while (cin >> n) {
if (0 == n) break;
cout << res[n - 1] << endl;
}
return 0;
}
采用另一种数据结构Dancing links可以解决15皇后的规模,详细介绍见以下博客: