马上注册,结交更多好友,享用更多功能^_^
您需要 登录 才可以下载或查看,没有账号?立即注册
x
本帖最后由 zhangjinxuan 于 2023-1-9 14:26 编辑
鱼油们好,今天我来分享一下N皇后的解法
题目简介:
在一个 n×n 的棋盘中一行一行地放入 n 个皇后,要求皇后之间不能互相攻击,请按字典序从小到大的顺序输出所有的方案。
一个皇后能攻击和她同一行、同一列、同一斜线上的所有其他格子上的棋子。
输入格式:
一行一个整数 n。
输出格式:
按字典序从小到大的顺序一行一行输出所有方案,每行一组方案,输出 n 个整数,依次表示第 1,2,…,n 行的皇后在第几列。
样例输入:样例输出:2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
5 3 1 6 4 2
数据范围:3 <= n <= 13
这是一道经典的深搜(DFS)题,USACO可是考过的,所以一定要学会!
思路和代码:
1:我们先把框架写好,一些头文件、命名空间、main先写好,这步很简单,不多讲:#include <cstdio>
using namespace std;
int main() {
return 0;
}
2:定义所需变量,首先,n要定义,以及棋盘布局queen二维数组,以及一些深搜要用的数组,另外,可以用个宏变量MAXS方便以后调试程序:#define MAXS 13
int n;
bool queen[MAXS + 1][MAXS + 1], col[MAXS + 1], left[MAXS * 2 + 2], right[MAXS * 2 + 2];
有人说,这那么多bool数组用来干啥的?
其实queen是来对之后的输出做准备的,而col是来查看第i列有没有皇后,left,right表示第i个斜线有没有皇后,left表示左斜线,right表示右斜线,一个矩阵是有n*2-1条斜线,具体怎么表示就详细看后面
Q:为什么只用记列号,而不记行号呢?
A:因为我们是一行一行放置皇后的(题面说了的),放完第一行放下一行,放完第i行放i+1行,第i行的皇后不可能和其它行的皇后有行的冲突吧,所以不记行号,还是不理解可以在后面的代码中体会到的
3.1:声明深搜函数:先声明,无需返回值,写void,后面的row代表行号,表示当前要在第row行放置皇后
3.2:定义函数,并且判断边界条件:
如果r>n,说明所有皇后放置完毕,打印结果void dfs(int r) {
if (r > n) {
for (int i = 1; i <= n; ++i) //遍历queen布尔数组
for (int j = 1; j <= n; ++j)
if (queen[i][j]) { //如果当前位置有皇后
printf("%d ", j); //打印第i行皇后的列号 注,第二位才是列号
break; //可以break了
}
puts("");//打印换行
return; //必须返回,否则会陷入死递归
}
}
注释里有讲,这里不赘述,关于queen布尔数组看3.3步
3.3检查第i行j列的皇后(难点之难点):
检查是否可以放置皇后:for (int c = 1; c <= n; ++c) { //枚举列号
if (!col[c] && !left[c + r] && !right[c + n + 1 - r]) { //判断是否可以放置
//...
}
}
Q:这些col[c]、left[c + r]和right[c + n + 1 - r]是什么意思?干嘛要写我不认识他,他又不认识我的判断呢?
A:正是我要讲的,也是难点之一,首先col[c]不用讲,就是判断该列有没有皇后,而left,right是判断斜行,这个只能找规律,规律如下:
首先,我们画一个矩阵(简单起见,画4*4):
[ ][ ][ ][ ]
[ ][ ][ ][ ]
[ ][ ][ ][ ]
[ ][ ][ ][ ]
标上行号列号:
1 2 3 4
1 [ ][ ][ ][ ]
2 [ ][ ][ ][ ]
3 [ ][ ][ ][ ]
4 [ ][ ][ ][ ]
我们可以发现,在同一个左斜行,每个点的行号列号之和是相等的,我们来看看:
1 2 3 4
1 [ ] [ ] [Q1] [ ]
2 [ ] [Q2] [ ] [ ]
3 [Q3][ ] [ ] [ ]
4 [ ] [ ] [ ] [ ]
Q1行号列号和是4,Q2也是4,Q3也是4
所以我们可以通过这个特性来得出left[c + r]的代码
那么,对于右斜行,我们可以把行号数字,或者列号数字翻转过来(n+1-号,且只能反转一个,都是一样的):
4 3 2 1
1 [ ][ ][ ][ ]
2 [ ][ ][ ][ ]
3 [ ][ ][ ][ ]
4 [ ][ ][ ][ ]
再来看看右斜行列行号之和是否相等:
4 3 2 1
1 [Q1][ ] [ ] [ ]
2 [ ] [Q2] [ ] [ ]
3 [ ] [ ] [Q3] [ ]
4 [ ] [ ] [ ] [Q4]
可以发现,他们的和都是5,所以我们也得出了right[c + n + 1 - r]的代码
Q:那为什么中间要加一呢?
A:主要是方便大家理解,不然矩阵就长这样了:
3 2 1 0
1 [ ][ ][ ][ ]
2 [ ][ ][ ][ ]
3 [ ][ ][ ][ ]
4 [ ][ ][ ][ ]
这样理解肯定不方便
如果这步不理解,可以自己定义一个check函数,检查列号斜行(就像小甲鱼那样)
3.4放置递归并回溯(重点之重点):
先设置好该位置存在皇后:queen[r][c] = col[c] = left[c + r] = right[c + n + 1 - r] = true;
再递归下一行:回溯,清除状态:queen[r][c] = col[c] = left[c + r] = right[c + n + 1 - r] = false;
好了,至此,最难的部分已经完成了,接下来就是输入,调用部分啦
4.输入并调用:
这里一定要传入第一行,传入其他数字会发生奇奇怪怪的结果
全部代码:#include <cstdio>
using namespace std;
#define MAXS 13
int n, cnt;
bool queen[MAXS + 1][MAXS + 1], col[MAXS + 1], left[MAXS * 2 + 2], right[MAXS * 2 + 2];
void dfs(int r) {
if (r > n) {
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
if (queen[i][j]) {
printf("%d ", j);
break;
}
puts("");
++cnt;
return;
}
for (int c = 1; c <= n; ++c) {
if (!col[c] && !left[c + r] && !right[c + n + 1 - r]) {
queen[r][c] = col[c] = left[c + r] = right[c + n + 1 - r] = true;
dfs(r + 1);
queen[r][c] = col[c] = left[c + r] = right[c + n + 1 - r] = false;
}
}
}
int main() {
scanf("%d", &n);
dfs(1);
//printf("%d", cnt);
return 0;
}
总结:还是比较简单,之后可能有变种之类的,大家尽量学会!
[如果有帮助,别忘了小小的评分,一个荣誉也是爱~]
说明:本文为本萌新原创,如有雷同,纯属巧合! |