每日一题:解决N皇后问题的深度优先搜索(DFS)算法详解
引言
N皇后问题是经典的算法问题之一,要求在N×N的棋盘上放置N个皇后,使得它们互不攻击。具体来说,任何两个皇后不能在同一行、同一列或同一对角线上。本文将详细介绍如何使用深度优先搜索(DFS)算法来解决N皇后问题,并提供一个完整的C++实现。
1、问题描述
给定一个正整数n,表示一个n×n的棋盘,要求放置n个皇后,使得每行、每列和每条对角线上都只有一个皇后。返回所有可能的摆法数量。
例如,当n=4时,有两种摆法:
4
2
2、算法思路
2.1 深度优先搜索(DFS)
DFS是一种用于遍历或搜索树或图的算法。在N皇后问题中,我们可以将棋盘的状态看作一个树,每个节点代表棋盘的一个状态,边代表放置一个皇后的操作。
2.2 剪枝
为了减少搜索空间,我们需要在DFS过程中进行剪枝。具体来说,当我们放置一个皇后时,需要检查当前列、主对角线和副对角线是否已经被其他皇后占据。如果已经被占据,则跳过该位置。
2.3 回溯
当我们在某一行放置皇后后,继续递归到下一行。如果某一行无法放置皇后,则回溯到上一行,尝试其他位置。
3、代码实现
以下是使用C++实现的N皇后问题的DFS算法:
class Queens {
public:
int cnt = 0, N;
bool col[15], dpos[30], dneg[30];
int nQueens(int n) {
N = n;
dfs(0);
return cnt;
}
void dfs(int i) {
if (i == N && i != 0) { // 到达最后一行,注意0是特殊数据
cnt++; // 计数
return;
}
for (int j = 0; j < N; j++) { // 在这一行的每一列放皇后
if (col[j] || dpos[i + j] || dneg[i - j + N - 1]) continue; // 有别的皇后能攻击到就忽略这个位置
col[j] = dpos[i + j] = dneg[i - j + N - 1] = 1; // 占领这些相应的列和左下对角线和右下对角线
dfs(i + 1); // 到下一行
col[j] = dpos[i + j] = dneg[i - j + N - 1] = 0; // 拿掉这行的皇后,取消相应的占领
}
} // 运行到这说明该行放置皇后失败,不计数返回
};
代码解析
-
变量定义:
cnt
:记录合法的摆法数量。N
:棋盘的大小。col[15]
:标记每一列是否被占据。dpos[30]
:标记主对角线是否被占据。dneg[30]
:标记副对角线是否被占据。
-
nQueens函数:
- 初始化棋盘大小
N
,并调用DFS函数开始搜索。
- 初始化棋盘大小
-
dfs函数:
- 如果当前行
i
等于N
,说明已经成功放置了N个皇后,增加计数器cnt
。 - 遍历当前行的每一列,尝试放置皇后。
- 如果当前位置可以放置皇后(即列、主对角线和副对角线都没有被占据),则标记这些位置为被占据,并递归到下一行。
- 如果递归返回,说明当前行的皇后放置失败,取消标记,尝试下一个位置。
- 如果当前行
4、深入解析:
深入解析N皇后问题中的对角线表示:dpos[i + j]
和 dneg[i - j + N - 1]
在解决N皇后问题时,除了需要确保每行和每列只有一个皇后外,还需要确保每条对角线上也只有一个皇后。为了高效地检查对角线上是否已经存在皇后,我们通常使用两个数组 dpos
和 dneg
来分别表示主对角线和副对角线的状态。本文将详细解释为什么 dpos[i + j]
和 dneg[i - j + N - 1]
可以表示棋盘上的对角线。
4.1 棋盘的对角线
在一个N×N的棋盘上,每个位置 (i, j)
属于两条对角线:
- 主对角线:从左上到右下的对角线。
- 副对角线:从右上到左下的对角线。
我们需要为每条对角线分配一个唯一的标识符,以便快速检查是否有皇后占据了该对角线。
4.2 主对角线的表示:dpos[i + j]
为什么是 i + j
?
- 在同一主对角线上的所有位置
(i, j)
满足i + j
是一个常数。- 例如,在4×4的棋盘上:
- 位置
(0, 0)
的主对角线标识符为0 + 0 = 0
。 - 位置
(1, 1)
的主对角线标识符为1 + 1 = 2
。 - 位置
(2, 2)
的主对角线标识符为2 + 2 = 4
。
- 位置
- 例如,在4×4的棋盘上:
- 因此,
i + j
可以唯一标识一条主对角线。
示例
在4×4的棋盘上,主对角线的标识符如下:
0 | 1 | 2 | 3 | |
---|---|---|---|---|
0 | 0 | 1 | 2 | 3 |
1 | 1 | 2 | 3 | 4 |
2 | 2 | 3 | 4 | 5 |
3 | 3 | 4 | 5 | 6 |
- 可以看到,每条主对角线的
i + j
值都是唯一的。
4.3 副对角线的表示:dneg[i - j + N - 1]
定义
- 副对角线是从右上到左下的对角线。
- 对于棋盘上的任意一个位置
(i, j)
,其副对角线的标识符可以通过i - j + N - 1
计算得到。
为什么是 i - j + N - 1
?
- 在同一副对角线上的所有位置
(i, j)
满足i - j
是一个常数。- 例如,在4×4的棋盘上:
- 位置
(0, 3)
的副对角线标识符为0 - 3 + 3 = 0
。 - 位置
(1, 2)
的副对角线标识符为1 - 2 + 3 = 2
。 - 位置
(2, 1)
的副对角线标识符为2 - 1 + 3 = 4
。
- 位置
- 例如,在4×4的棋盘上:
- 由于
i - j
的值可能为负数,我们通过加上N - 1
来确保标识符始终为非负数。
示例
在4×4的棋盘上,副对角线的标识符如下:
0 | 1 | 2 | 3 | |
---|---|---|---|---|
0 | 3 | 2 | 1 | 0 |
1 | 4 | 3 | 2 | 1 |
2 | 5 | 4 | 3 | 2 |
3 | 6 | 5 | 4 | 3 |
- 可以看到,每条副对角线的
i - j + N - 1
值都是唯一的。
4.4 为什么需要 dpos
和 dneg
?
在N皇后问题中,我们需要快速检查某个位置 (i, j)
是否可以被放置皇后。具体来说,需要满足以下条件:
- 当前列
j
没有被占据(通过col[j]
检查)。 - 当前主对角线
i + j
没有被占据(通过dpos[i + j]
检查)。 - 当前副对角线
i - j + N - 1
没有被占据(通过dneg[i - j + N - 1]
检查)。
通过 dpos
和 dneg
,我们可以高效地标记和检查对角线的状态,从而避免重复计算。
4.5 代码中的具体实现
在代码中,dpos
和 dneg
是两个布尔数组,分别用于标记主对角线和副对角线是否被占据。具体实现如下:
bool col[15], dpos[30], dneg[30]; // 列、主对角线、副对角线的标记数组
void dfs(int i) {
if (i == N) { // 如果已经放置了N个皇后
cnt++; // 增加计数
return;
}
for (int j = 0; j < N; j++) { // 尝试在当前行的每一列放置皇后
if (col[j] || dpos[i + j] || dneg[i - j + N - 1]) continue; // 检查是否被占据
col[j] = dpos[i + j] = dneg[i - j + N - 1] = true; // 标记占据
dfs(i + 1); // 递归到下一行
col[j] = dpos[i + j] = dneg[i - j + N - 1] = false; // 回溯,取消标记
}
}
关键点
dpos[i + j]
:标记主对角线。dneg[i - j + N - 1]
:标记副对角线。- 如果某个位置
(i, j)
的主对角线或副对角线已经被占据,则跳过该位置。
5. 总结
dpos[i + j]
和dneg[i - j + N - 1]
是用于表示棋盘上主对角线和副对角线的唯一标识符。- 通过这两个数组,我们可以高效地检查某个位置是否可以被放置皇后,从而避免冲突。
- 这种表示方法是N皇后问题中常用的优化技巧,能够显著减少计算量。
总结
通过深度优先搜索和剪枝策略,我们可以高效地解决N皇后问题。该算法的时间复杂度为O(N!),但由于剪枝的存在,实际运行时间会大大减少。希望本文能帮助你理解N皇后问题的解法,并掌握DFS算法的应用。
关于深度搜素更多内容,点击:C++二叉树深度探索:DFS函数详解与应用实例
关注专栏:每日一题:C++ LeetCode精讲,和我一起进步
希望本文能帮助你更好地理解N皇后问题中对角线的表示方法!如果有任何疑问,欢迎在评论区留言讨论!
版权声明:本文为博主原创文章,转载请注明出处。点个赞吧