第一次接触八皇后问题时,我被这个看似简单却暗藏玄机的问题深深吸引。想象一下,在一个标准国际象棋棋盘上摆放8个皇后,要求她们互不攻击——这意味着任何两个皇后不能在同一行、同一列或同一对角线上。这个1848年提出的问题,至今仍是算法学习的经典案例。
但真正让我着迷的是它的扩展:当棋盘大小变为N×N,皇后数量变为N时,问题会怎样变化?这就是N皇后问题的由来。从算法角度看,八皇后只是N皇后在N=8时的特例。理解这一点后,我突然意识到,掌握N皇后的通用解法,比单纯解决八皇后要有价值得多。
**深度优先搜索(DFS)**在这里展现出惊人的适应性。它的核心思想就像走迷宫:沿着一条路走到底,走不通就退回上一个岔路口。对于N皇后问题,我们可以逐行放置皇后,在每行尝试所有可能的位置,遇到冲突就回溯。这种"试探-回退"的机制,正是DFS回溯算法的精髓所在。
要实现通用的N皇后解法,首先需要设计合适的数据结构。与固定大小的八皇后不同,N皇后需要动态处理棋盘尺寸。我通常会定义以下变量:
cpp复制int N; // 棋盘大小
vector<int> queens; // 记录每行皇后所在的列
vector<bool> cols; // 标记列是否被占用
vector<bool> diag1; // 主对角线(左上到右下)
vector<bool> diag2; // 副对角线(右上到左下)
这里有个关键技巧:如何高效判断对角线冲突?通过观察发现,主对角线上所有点的行-列值相同,副对角线上行+列值相同。因此:
(0 - (N-1))到((N-1) - 0),所以需要偏移N-10+0到(N-1)+(N-1)完整的DFS回溯框架如下:
cpp复制void dfs(int row) {
if (row == N) {
// 找到一个解,处理结果
return;
}
for (int col = 0; col < N; ++col) {
int d1 = row - col + N - 1; // 主对角线索引
int d2 = row + col; // 副对角线索引
if (!cols[col] && !diag1[d1] && !diag2[d2]) {
queens[row] = col;
cols[col] = diag1[d1] = diag2[d2] = true;
dfs(row + 1); // 递归下一行
// 回溯
cols[col] = diag1[d1] = diag2[d2] = false;
}
}
}
这个框架的美妙之处在于它的通用性——只需改变N的值,就能解决任意尺寸的N皇后问题。我在实际编码中发现,清晰的变量命名和注释对理解回溯过程至关重要。
N皇后问题的时间复杂度是指数级的O(N!)。这是因为:
但实际运行时会提前剪枝,所以比纯阶乘要好。我测试过不同N值的运行时间:
对于追求极致性能的场景,可以使用位运算优化。核心思想是用整数的二进制位表示列和对角线的占用情况:
cpp复制void dfs(int row, int cols, int diag1, int diag2) {
if (row == N) {
// 找到解
return;
}
int available = ((1 << N) - 1) & ~(cols | diag1 | diag2);
while (available) {
int pos = available & -available; // 获取最低位的1
available ^= pos; // 清除该位
dfs(row + 1,
cols | pos,
(diag1 | pos) << 1,
(diag2 | pos) >> 1);
}
}
这种优化可以显著减少内存访问,提升缓存命中率。在我的测试中,N=15时,位运算版本比数组版本快3-5倍。
结合上述思路,下面是一个完整的N皇后求解器实现:
cpp复制#include <iostream>
#include <vector>
using namespace std;
class NQueens {
private:
int N;
vector<vector<string>> solutions;
void dfs(int row, vector<int>& queens,
vector<bool>& cols,
vector<bool>& diag1,
vector<bool>& diag2) {
if (row == N) {
addSolution(queens);
return;
}
for (int col = 0; col < N; ++col) {
int d1 = row - col + N - 1;
int d2 = row + col;
if (!cols[col] && !diag1[d1] && !diag2[d2]) {
queens[row] = col;
cols[col] = diag1[d1] = diag2[d2] = true;
dfs(row + 1, queens, cols, diag1, diag2);
cols[col] = diag1[d1] = diag2[d2] = false;
}
}
}
void addSolution(const vector<int>& queens) {
vector<string> solution(N, string(N, '.'));
for (int i = 0; i < N; ++i) {
solution[i][queens[i]] = 'Q';
}
solutions.push_back(solution);
}
public:
vector<vector<string>> solveNQueens(int n) {
N = n;
solutions.clear();
vector<int> queens(N, -1);
vector<bool> cols(N, false);
vector<bool> diag1(2 * N - 1, false);
vector<bool> diag2(2 * N - 1, false);
dfs(0, queens, cols, diag1, diag2);
return solutions;
}
};
int main() {
int n;
cout << "输入皇后数量N: ";
cin >> n;
NQueens solver;
auto solutions = solver.solveNQueens(n);
cout << "共找到 " << solutions.size() << " 种解法\n";
// 打印前3种解法示例
for (int i = 0; i < min(3, (int)solutions.size()); ++i) {
cout << "解法 " << i+1 << ":\n";
for (const auto& row : solutions[i]) {
cout << row << "\n";
}
cout << "\n";
}
return 0;
}
测试不同规模的N皇后问题时,我发现几个有趣现象:
对于大规模N值(如N>15),即使使用优化算法,计算所有解仍然非常耗时。这时可以考虑随机算法或启发式方法寻找部分解。