<think>我们被要求提供N皇后问题的C++解决方案代码。根据引用内容,有几种不同的实现方法,包括回溯法和优化版本(使用位运算优化,适用于皇后数量不超过32的情况)。用户要求使用C++编写,因此我们将提供两种常见的实现方式:基础回溯法和位运算优化法。
基础回溯法思路:
1. 使用一个数组`col`(或类似结构)来记录每一行皇后所在的列。
2. 在放置第i行的皇后时,检查当前列是否与之前放置的皇后冲突(同一列,同一对角线)。
3. 如果冲突则跳过,否则放置并递归处理下一行。
4. 当所有行都放置了皇后,则得到一个解。
位运算优化法思路(适用于n<=32):
1. 使用三个整数(colLim, leftDiaLim, rightDiaLim)的二进制位来表示列和两个方向对角线的占用情况。
2. 通过位运算快速获取当前行可以放置皇后的位置。
根据引用[2]和引用[3],我们分别提供两种代码。
注意:引用[2]中的代码使用了二维数组来记录棋盘,并且在搜索时同时考虑了放与不放两种情况,同时使用四个布尔数组(行、列、两个对角线)来记录冲突。但这种方法在搜索过程中是按每个格子(x,y)来推进的,而不是按行推进。引用[3]的代码是按行递归。
我们将提供两种常见写法:
写法一:按行递归(引用[3]风格,但稍作修改,使其输出棋盘)
写法二:位运算优化(引用[1]中提到的方法,但引用[1]描述的是经典回溯,引用[2]和引用[3]没有位运算,因此我们根据位运算的经典写法补充)
由于用户要求代码,我们重点提供可运行的代码,并简要说明。
注意:输出格式要求,每个解输出n行字符串,每行n个字符,'.'表示空,'Q'表示皇后,解之间输出空行。
我们先提供按行递归的基础回溯法(输出所有解的棋盘):
这里我们参考引用[3]并修改,使其输出棋盘。引用[3]只统计解的数量,我们修改为输出所有解。
写法一:基础回溯法(输出所有解)
步骤:
1. 定义全局变量:棋盘大小n,一个数组col(记录每行皇后的列位置),一个容器(存储所有解,或者直接输出)。
2. 编写检查函数Place:判断当前行row的col列是否可以放置(检查与前面行的冲突)。
3. 编写回溯函数:从第0行(或第1行)开始递归,如果当前行是第n行,则输出棋盘(或保存解);否则,尝试在当前行的每一列放置,如果合法则递归下一行。
4. 注意回溯。
但是,输出格式要求输出棋盘,因此我们使用一个二维字符数组(或字符串数组)来构建每个解的棋盘。
然而,在递归过程中,我们不需要保存整个棋盘状态,只需要记录每行皇后的列位置,然后在得到一个解时,根据这个位置数组构建棋盘并输出。
因此,我们可以这样:
全局变量:
vector<vector<string>> solutions; // 存储所有解(每个解是一个棋盘,棋盘由vector<string>表示,每行是一个字符串)
vector<int> col_placement; // 当前每行皇后的列位置(索引为行号)
但是,为了节省空间,我们也可以每次得到一个解就输出一个解。
我们选择在得到解时立即输出(因为n皇后解的数量可能很大,全部存储可能占用大量内存)。
代码结构:
void backtrack(int row, int n) {
if (row == n) {
// 输出当前棋盘
print_board(col_placement, n);
return;
}
for (int c = 0; c < n; c++) {
if (is_valid(row, c, col_placement)) {
col_placement[row] = c;
backtrack(row+1, n);
// 无需显式回溯,因为col_placement[row]会被覆盖
}
}
}
检查函数is_valid:
bool is_valid(int row, int col, const vector<int>& col_placement) {
for (int r=0; r<row; r++) {
int c = col_placement[r];
if (c == col || abs(row - r) == abs(col - c)) {
return false;
}
}
return true;
}
输出函数print_board:
void print_board(vector<int>& col_placement, int n) {
for (int i=0; i<n; i++) {
string s(n, '.'); // 创建一行,初始为n个点
s[col_placement[i]] = 'Q';
cout << s << endl;
}
cout << endl; // 每个解后输出空行
}
但是,注意:col_placement数组的大小应为n,并且我们在递归过程中使用它。
但是,这种方法在n较大时效率较低,因为检查冲突需要O(n)时间。
写法二:位运算优化(不输出所有解,但可以输出,这里我们只提供计算解数量的位运算方法,但也可以输出解,不过输出所有解时,位运算优化并不能减少解的数量,所以输出部分仍然需要时间,因此我们这里提供位运算优化求所有解并输出棋盘的方法?)
位运算优化通常用于统计解的数量,输出所有解时,位运算优化可以加速搜索过程,但输出棋盘需要额外操作。
位运算优化方法(参考经典实现):
使用三个整数:col, ld, rd,分别表示列、左斜线、右斜线的占用情况(二进制位1表示被占用)。
当前行可用的位置:pos = ~(col | ld | rd) & ((1<<n)-1) (取低n位)
然后循环取可用的位置(取最低位的1:p = pos & -pos,然后从pos中移除该位置:pos -= p),并递归下一行。
但是,在递归过程中,我们如何记录每个皇后的位置?因为输出棋盘需要知道每行皇后的列。
我们可以用数组记录每行放置的列(通过计算p中1所在的位置)。
因此,我们同样需要记录每行放置的列(或者用栈),在到达最后一行时,根据记录输出棋盘。
位运算优化代码结构:
void backtrack(int row, int col_mask, int left_diag, int right_diag, vector<int>& placement, int n) {
if (row == n) {
print_board(placement, n);
return;
}
int available = ((1 << n) - 1) & ~(col_mask | left_diag | right_diag);
while (available) {
int pos = available & -available; // 取最低位的1
available -= pos;
// 计算当前列号:pos的二进制中1的位置(从0开始)
int col_index = __builtin_ctz(pos); // 或者用log2,但__builtin_ctz是GCC内置函数(计算末尾0的个数,即最低位1的位置)
// 记录当前行放置的列
placement[row] = col_index;
// 递归下一行,注意更新三个掩码:
// 列掩码:col_mask | pos
// 左斜线:(left_diag | pos) << 1 (因为下一行的左斜线影响是当前左斜线左移1位)
// 右斜线:(right_diag | pos) >> 1
backtrack(row+1, col_mask | pos, (left_diag | pos) << 1, (right_diag | pos) >> 1, placement, n);
}
}
注意:左斜线和右斜线的更新,在递归下一行时,左斜线要左移1,右斜线要右移1(因为斜线的影响会传递到下一行)。
但是,由于左移和右移可能会超出n位,所以我们在递归时用掩码限制低位n位:通过和((1<<n)-1)相与,但这里我们在递归参数中已经用available进行了限制(因为一开始available就限制在低n位),所以移位后超出部分会被available的掩码操作过滤掉。
但是,在递归调用时,我们传入的left_diag和right_diag在下一层函数中会被available的掩码操作限制(即:available = ((1<<n)-1) & ~(col_mask | left_diag | right_diag)),所以移位后超出n位的部分会被自动忽略。
因此,我们可以这样写。
输出函数print_board同上。
但是,位运算优化只适用于n<=32(因为整数的位数限制,通常int是32位),所以当n>32时,需要改用其他方法。
由于用户没有指定n的大小,我们提供两种方法,并说明适用情况。
我们决定提供两种代码:
代码1:基础回溯法(按行递归,输出所有解)
代码2:位运算优化(输出所有解,但仅适用于n<=32)
但是,用户可能只需要一种,或者更关注通用性。考虑到基础回溯法简单易懂,且n一般不会太大(通常n=8,12等),基础回溯法已经足够。
我们根据引用[4]的要求输出格式:每个方案输出n行字符串,每行n个字符,'.'和'Q',每个方案后输出空行。
我们选择基础回溯法实现,因为引用[2]和[3]都提供了基础回溯,并且引用[2]的代码已经可以输出棋盘(但引用[2]的代码是按格子递归,我们这里按行递归更高效)。
因此,我们提供按行递归的基础回溯法代码(输出所有解的棋盘)。
注意:引用[2]的代码中使用了四个布尔数组(行、列、两个对角线)来记录冲突,这样可以将检查冲突的时间降到O(1),但需要额外空间。我们也可以采用这种方法来优化基础回溯法。
方法:使用四个数组(或三个,因为行是按行递归的,所以不会出现行冲突,只需要列、主对角线、副对角线)
列数组:bool col_used[n] -> 记录每列是否被占用
主对角线:bool diag1[2*n-1] -> 主对角线:行号+列号 = 常数,范围0~2n-2
副对角线:bool diag2[2*n-1] -> 副对角线:行号-列号+n-1 = 常数,范围0~2n-2
这样,在放置(row, col)时,检查:
if (!col_used[col] && !diag1[row+col] && !diag2[row-col+n-1])
则合法。
然后标记:
col_used[col] = diag1[row+col] = diag2[row-col+n-1] = true;
递归下一行
递归返回后,回溯:col_used[col] = diag1[row+col] = diag2[row-col+n-1] = false;
这种方法比显式循环检查冲突更快。
因此,我们采用这种优化。
代码结构:
全局(或作为参数传递):
vector<bool> col_used;
vector<bool> diag1; // 主对角线
vector<bool> diag2; // 副对角线
vector<int> queens; // 每行皇后放在哪一列,用于最后输出棋盘(也可以不用,因为递归时我们按行放置,可以直接用栈)
函数:
void solve(int row, int n, vector<string>& current, vector<vector<string>>& solutions)
或者直接输出,不存储(这里我们直接输出)
但是,输出所有解时,如果解的数量很大,直接存储可能内存不够,所以选择直接输出。
因此,我们这样:
void backtrack(int row, int n, vector<int>& placement, vector<bool>& col_used, vector<bool>& diag1, vector<bool>& diag2) {
if (row == n) {
print_board(placement, n);
return;
}
for (int col=0; col<n; col++) {
int d1 = row + col;
int d2 = row - col + n - 1;
if (!col_used[col] && !diag1[d1] && !diag2[d2]) {
placement[row] = col;
col_used[col] = diag1[d1] = diag2[d2] = true;
backtrack(row+1, n, placement, col_used, diag1, diag2);
col_used[col] = diag1[d1] = diag2[d2] = false; // 回溯
}
}
}
初始化:
vector<int> placement(n, -1); // 每行皇后的列位置
vector<bool> col_used(n, false);
vector<bool> diag1(2*n-1, false);
vector<bool> diag2(2*n-1, false);
然后调用:backtrack(0, n, placement, col_used, diag1, diag2);
输出函数print_board(placement, n)同上。
考虑到引用[2]的代码中使用了二维棋盘,并且是逐格递归,我们这里按行递归效率更高(因为每行只放一个皇后)。
因此,我们最终选择这种优化后的基础回溯法(使用三个布尔数组避免显式检查冲突)。
下面提供完整代码(注意:输出格式,每行末尾不能有空格,每个解后有空行)。
代码:
注意:我们直接输出每个解,不存储。
但是,如果用户需要存储(比如返回所有解),可以修改,但题目要求输出棋盘。
我们按照要求输出。
注意:皇后数量n由用户输入。
完整代码:
#include <iostream>
#include <vector>
#include <cmath>
#include <cstdlib>
using namespace std;
void print_board(vector<int>& placement, int n) {
for (int i=0; i<n; i++) {
string s(n, '.');
s[placement[i]] = 'Q';
cout << s << endl;
}
cout << endl; // 每个解后输出空行
}
void backtrack(int row, int n, vector<int>& placement, vector<bool>& col_used, vector<bool>& diag1, vector<bool>& diag2) {
if (row == n) {
print_board(placement, n);
return;
}
for (int col=0; col<n; col++) {
int d1 = row + col;
int d2 = row - col + n - 1; // 防止负数,所以加n-1
if (!col_used[col] && !diag1[d1] && !diag2[d2]) {
placement[row] = col;
col_used[col] = true;
diag1[d1] = true;
diag2[d2] = true;
backtrack(row+1, n, placement, col_used, diag1, diag2);
col_used[col] = false;
diag1[d1] = false;
diag2[d2] = false;
}
}
}
int main() {
int n;
cout << "请输入皇后的数量: ";
cin >> n;
vector<int> placement(n, -1); // 记录每行的皇后所在列
vector<bool> col_used(n, false);
vector<bool> diag1(2*n-1, false);
vector<bool> diag2(2*n-1, false);
backtrack(0, n, placement, col_used, diag1, diag2);
return 0;
}
但是,注意:这个代码没有考虑没有任何解的情况,但也会正常输出(不输出任何棋盘,因为backtrack返回后程序结束)。这是符合要求的。
然而,引用[2]的代码中,在放置皇后时,不仅考虑放,还考虑不放?实际上按行递归不需要考虑不放,因为每行必须放一个皇后。所以我们的循环中,如果找到合法位置就放,否则循环结束就回溯到上一行。
另外,我们也可以像引用[2]那样,在递归函数中同时处理不放的情况?不需要,因为每行必须放一个皇后,所以我们在当前行找到合法列放置即可,没有不放的选项。
因此,我们的代码是合理的。
但是,我们也可以使用非递归方式,但这里使用递归更清晰。
最后,我们提供这个代码。
另外,位运算优化的代码,如果需要,我们也可以提供,但考虑到通用性,我们先提供基础回溯法。
如果用户需要位运算优化,我们可以补充。
因此,我们回答:
我们提供两种C++实现方法,第一种是基础回溯法(使用布尔数组优化冲突检查),第二种是位运算优化(适用于n<=32)。
由于用户要求代码,我们分别提供。
方法一:基础回溯法(使用布尔数组优化)
方法二:位运算优化回溯
我们分别提供代码。
方法一代码(已提供,见上)但为了完整,我们再次整理。
方法二:位运算优化(输出所有解)
注意:位运算优化在输出所有解时,也需要记录每行皇后的列位置,以便输出棋盘。
代码:
#include <iostream>
#include <vector>
using namespace std;
void print_board(vector<int>& placement, int n) {
for (int i=0; i<n; i++) {
string s(n, '.');
s[placement[i]] = 'Q';
cout << s << endl;
}
cout << endl;
}
void backtrack(int row, int col_mask, int left_diag, int right_diag, vector<int>& placement, int n) {
if (row == n) {
print_board(placement, n);
return;
}
// 计算可用的位置:col_mask, left_diag, right_diag 的二进制位1表示不可用
int available = ((1 << n) - 1) & ~(col_mask | left_diag | right_diag);
while (available) {
int pos = available & -available; // 取最低位的1
available ^= pos; // 或者 available -= pos; 清除最低位的1
// 计算列索引:从0开始,计算pos的二进制中1的位置(即末尾0的个数)
int col_index = __builtin_ctz(pos); // 使用GCC内置函数,注意:如果使用其他编译器,可能需要替换为其他函数
// 记录当前行放置的列
placement[row] = col_index;
// 递归下一行,更新三个掩码:
// 列掩码:col_mask | pos
// 左斜线:(left_diag | pos) << 1
// 右斜线:(right_diag | pos) >> 1
backtrack(row+1,
col_mask | pos,
(left_diag | pos) << 1,
(right_diag | pos) >> 1,
placement,
n);
}
}
int main() {
int n;
cout << "请输入皇后的数量: ";
cin >> n;
vector<int> placement(n, -1); // 记录每行皇后的列位置
// 初始调用:从第0行开始,列掩码、左斜线掩码、右斜线掩码都为0
backtrack(0, 0, 0, 0, placement, n);
return 0;
}
注意:位运算优化代码使用了GCC内置函数__builtin_ctz(返回二进制中末尾0的个数,即从最低位开始的1的位置)。在非GCC编译器下可能无法编译,可以替换为:
int col_index = 0;
int temp = pos;
while (temp >>= 1) col_index++;
// 但是这样计算的是最高位?不对,应该计算最低位的1的位置。
// 另一种方法:使用log2(pos)(但pos必须是2的幂次)
if (pos > 0) {
col_index = log2(pos); // 这样得到的是指数,即位置(从0开始)
}
但是,log2函数在cmath中,且可能有浮点误差,但pos是2的幂,所以可以。
因此,可以替换为:
int col_index = log2(pos); // 需要#include <cmath>
但是,注意:log2函数在pos很大时(比如超过2^30)可能会有精度问题,但n<=32,所以pos最大为2^31(在32位系统上,n=32时,pos可以是2^31,但log2(2^31)=31,没问题)。
修改后的代码(可移植):
int col_index = (int)log2(pos); // 强制转换为int
或者,使用循环(效率较低,但n小没关系):
int col_index = 0;
int p = 1;
while ((pos & p) == 0) {
col_index++;
p <<= 1;
}
// 这样col_index就是最低位1的位置?不对,这样得到的是从最低位开始数0的个数,但循环中我们每次左移p,当p与pos匹配时,col_index就是0的个数,也就是位置。
// 但这样写有点麻烦,不如用log2。
因此,我们使用log2。
修改位运算优化代码中的行:
int col_index = (int)log2(pos); // 注意:pos是2的幂,所以log2(pos)是整数
但是,注意:log2(pos)返回double,我们转换为int,并且pos必须大于0。
完整位运算优化代码(使用log2):
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
void print_board(vector<int>& placement, int n) {
for (int i=0; i<n; i++) {
string s(n, '.');
s[placement[i]] = 'Q';
cout << s << endl;
}
cout << endl;
}
void backtrack(int row, int col_mask, int left_diag, int right_diag, vector<int>& placement, int n) {
if (row == n) {
print_board(placement, n);
return;
}
int available = ((1 << n) - 1) & ~(col_mask | left_diag | right_diag);
while (available) {
int pos = available & -available;
available ^= pos; // 或者 available -= pos;
// 计算列索引:log2(pos)
int col_index = (int)log2(pos); // 注意:pos必须是2的幂,且大于0
placement[row] = col_index;
// 递归
backtrack(row+1,
col_mask | pos,
(left_diag | pos) << 1,
(right_diag | pos) >> 1,
placement,
n);
}
}
int main() {
int n;
cout << "请输入皇后的数量: ";
cin >> n;
if (n > 32 || n < 1) {
cout << "位运算优化仅适用于1<=n<=32" << endl;
return 0;
}
vector<int> placement(n, -1);
backtrack(0, 0, 0, 0, placement, n);
return 0;
}
注意:位运算优化代码中,我们限制了n的范围(1~32),因为当n>32时,整数位不够(我们使用int,通常32位)。如果n=0,则没有意义。
最后,我们提供两种代码,用户可以根据需要选择。
但是,用户可能只需要一种。我们根据常见情况,先提供基础回溯法(方法一),因为它更通用(对n没有限制,但n太大时解的数量指数级增长,输出会非常慢,但代码可以运行)。
因此,我们最终提供基础回溯法(布尔数组优化)的代码作为主要答案,位运算优化作为补充。
另外,引用[2]的代码是逐格递归,我们这里按行递归效率更高,所以不采用逐格递归。
最终答案:
我们提供使用C++解决N皇后问题的代码,采用回溯法并使用布尔数组优化冲突检查,直接输出所有解的棋盘。
代码如下:</think>以下是使用C++实现的N皇后问题解决方案代码,采用回溯法并优化了冲突检查(使用布尔数组记录列和对角线占用情况),能输出所有解的棋盘布局:
```cpp
#include <iostream>
#include <vector>
using namespace std;
// 打印棋盘布局
void print_board(vector<int>& queens, int n) {
for (int i = 0; i < n; i++) {
string row(n, '.'); // 初始化一行,全为'.'
row[queens[i]] = 'Q'; // 在皇后位置标记'Q'
cout << row << endl;
}
cout << endl; // 解之间用空行分隔
}
// 回溯法求解N皇后
void backtrack(int row, int n, vector<int>& queens,
vector<bool>& col_used,
vector<bool>& diag1,
vector<bool>& diag2) {
if (row == n) {
print_board(queens, n); // 找到解,打印棋盘
return;
}
for (int col = 0; col < n; col++) {
int d1 = row + col; // 主对角线索引
int d2 = row - col + n - 1; // 副对角线索引(防负数)
// 检查当前位置是否安全
if (!col_used[col] && !diag1[d1] && !diag2[d2]) {
queens[row] = col; // 记录皇后位置
col_used[col] = diag1[d1] = diag2[d2] = true; // 标记占用
// 递归处理下一行
backtrack(row + 1, n, queens, col_used, diag1, diag2);
// 回溯:撤销标记
col_used[col] = diag1[d1] = diag2[d2] = false;
}
}
}
int main() {
int n;
cout << "请输入皇后的数量: ";
cin >> n;
vector<int> queens(n, -1); // 记录每行皇后的列位置
vector<bool> col_used(n, false); // 列占用标记
vector<bool> diag1(2 * n - 1, false); // 主对角线占用标记
vector<bool> diag2(2 * n - 1, false); // 副对角线占用标记
backtrack(0, n, queens, col_used, diag1, diag2);
return 0;
}
```
### 代码说明:
1. **冲突检查优化**:
- **列冲突**:`col_used`数组标记已被占用的列。
- **主对角线**:`diag1`数组标记行号+列号相同的对角线(索引范围:$0$ 到 $2n-2$)。
- **副对角线**:`diag2`数组标记行号-列号相同的对角线(索引范围:$0$ 到 $2n-2$),通过 `row-col+n-1` 避免负数索引。
2. **输出格式**:
- 每个解输出 $n$ 行字符串(如 `..Q.`)。
- 解之间用空行分隔,符合题目要求[^4]。
3. **时间复杂度**:
- 最坏情况 $O(n!)$,但优化后实际运行效率显著高于朴素回溯法。
### 使用示例:
输入 `4` 将输出:
```
.Q..
...Q
Q...
..Q.
..Q.
Q...
...Q
.Q..
```
### 相关问题:
1. 如何优化N皇后问题的时间复杂度?
2. 位运算在N皇后问题中如何应用?
3. N皇后问题有哪些实际应用场景?
4. 当皇后数量极大(如 $n>30$)时,如何高效统计解的数量?
[^1]: 回溯法通过递归尝试所有可能性,当冲突发生时撤销操作(回溯)。
[^2]: 布尔数组将冲突检查复杂度从 $O(n)$ 降至 $O(1)$。
[^3]: 对角线索引公式确保唯一标识每条对角线。
[^4]: 输出格式要求每个解独立显示,解间有空行。