#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
// 检查是否可以在 (row, col) 放置皇后
bool isSafe(const vector<int>& board, int row, int col) {
for (int i = 0; i < row; ++i) {
// 检查同一列和对角线冲突
if (board[i] == col || abs(board[i] - col) == abs(i - row)) {
return false;
}
}
return true;
}
// 递归解决八皇后问题
void solveNQueens(vector<int>& board, int row, vector<vector<int>>& solutions) {
int n = board.size();
if (row == n) {
solutions.push_back(board); // 找到一个解
return;
}
for (int col = 0; col < n; ++col) {
if (isSafe(board, row, col)) {
board[row] = col; // 放置皇后
solveNQueens(board, row + 1, solutions); // 递归处理下一行
board[row] = -1; // 撤销选择(回溯)
}
}
}
// 打印棋盘
void printSolution(const vector<int>& board) {
int n = board.size();
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
if (board[i] == j) {
cout << "Q ";
} else {
cout << ". ";
}
}
cout << endl;
}
cout << endl;
}
int main() {
int n = 8; // 棋盘大小,八皇后问题固定为 8
vector<int> board(n, -1); // 初始化棋盘,每行初始为空
vector<vector<int>> solutions; // 存储所有解
solveNQueens(board, 0, solutions);
cout << "Total solutions: " << solutions.size() << endl;
for (const auto& solution : solutions) {
printSolution(solution);
}
return 0;
}
代码说明
isSafe
函数:检查当前行是否可以安全地放置皇后,防止同列和对角线冲突。solveNQueens
函数:通过递归和回溯生成所有合法的棋盘布局。printSolution
函数:打印棋盘,用Q
表示皇后,用.
表示空位。- 主函数:设置棋盘大小为 8,调用解决函数,并打印解的数量和具体布局。
运行结果
程序运行后会输出所有八皇后的解以及解的总数(92 个)。
回溯算法的特点
- 递归+回溯:
- 用递归的方式遍历所有可能的棋盘布局。
- 当发现不符合条件时,通过回溯撤销选择,继续尝试其他可能性。
- 剪枝优化:
- 在放置皇后时,通过
isSafe
提前判断是否满足条件,减少无效尝试。
- 在放置皇后时,通过
这段代码结构清晰,适用于任何 n
皇后问题,只需调整 n
的值即可。
有的人会问:棋盘是二维的的,为什么isSafe函数的board是个一维数组?
在这个实现中,使用一维数组 board
来表示二维棋盘,这是一种常见的优化方式。下面解释这种表示法的意义和好处:
1. 棋盘的表示方法
- 二维棋盘通常用
n x n
的数组表示,每个位置可以是0
(空)或1
(有皇后)。
int board[8][8]; // 传统二维数组表示法
但是,这里选择了一维数组 board
,其含义如下:
- 数组的下标表示行号 (
row
)。 - 数组的值表示对应行的皇后放在哪一列 (
col
)。
vector<int> board(n, -1);
举例: 假设 board = [1, 3, 0, 2]
:
board[0] = 1
表示第 0 行的皇后放在第 1 列。board[1] = 3
表示第 1 行的皇后放在第 3 列。board[2] = 0
表示第 2 行的皇后放在第 0 列。board[3] = 2
表示第 3 行的皇后放在第 2 列。
相应的棋盘布局是:
. Q . .
. . . Q
Q . . .
. . Q .
2. 为什么用一维数组
-
简化逻辑:
- 每行只能放置一个皇后,因此不需要检查同一行是否冲突,只需记录每行皇后的位置。
- 这样可以用
board[row]
存储第row
行皇后的位置,而无需维护整个二维数组。
-
节省空间:
- 二维数组的空间复杂度是 O(n2)O(n^2)O(n2),而一维数组的空间复杂度是 O(n)O(n)O(n)。
- 对于较大的棋盘(如 n>8n > 8n>8),这种优化非常重要。
-
易于实现对角线检查:
- 检查对角线冲突时,用
abs(board[i] - col) == abs(i - row)
就可以判断两皇后是否在同一对角线上。 - 如果用二维数组实现,需要通过复杂的循环和索引来检查对角线。
- 检查对角线冲突时,用
3. isSafe
函数工作原理
bool isSafe(const vector<int>& board, int row, int col) {
for (int i = 0; i < row; ++i) {
if (board[i] == col || abs(board[i] - col) == abs(i - row)) {
return false;
}
}
return true;
}
- 遍历
board
中之前所有行i
的皇后位置:- 列冲突: 检查
board[i] == col
,即前面某一行的皇后是否在当前列。 - 对角线冲突: 检查
abs(board[i] - col) == abs(i - row)
,即前面某一行的皇后是否在当前对角线上。
- 列冲突: 检查
- 由于一维数组记录了每行皇后的位置,不需要额外的逻辑去检查行冲突。
4. 总结
使用一维数组代替二维棋盘的关键原因是:
- 空间节省:从 O(n2)O(n^2)O(n2) 降到 O(n)O(n)O(n)。
- 逻辑简化:每行最多只有一个皇后,使用
board[row]
自然表达。 - 易于判断对角线冲突:通过简单的数学运算解决。
这种优化方式在处理类似问题(如数独、n 皇后等)中非常常见。