2-3 N皇后问题(NQueens)
N皇后问题(N-Queens Problem)
N皇后问题是经典的回溯算法问题:在 N×N 的棋盘上放置 N 个皇后,使得任意两个皇后不在同一行、同一列、同一对角线上。
以 N = 4 为例,其中一个有效解为:
. Q . .
. . . Q
Q . . .
. . Q .
其中 Q 表示皇后,. 表示空格。可以验证:没有任何两个皇后在同一行、同一列或同一对角线上。
回溯算法思路
回溯法是解决 N 皇后问题的经典方法。核心思想是逐行放置皇后,每行尝试每一列:
- 放置规则:在当前行的每一列尝试放置皇后。
- 冲突检测:检查该位置是否与已放置的皇后冲突(同列、同主对角线、同副对角线)。
- 递归前进:如果放置有效,递归到下一行继续放置。
- 回溯撤销:如果后续行无法找到有效位置,撤销当前放置,尝试下一列。
冲突检测的关键判断:
- 同列:
col[i] == col[j] - 主对角线(左上到右下):
row - col值相同,即i - col[i] == j - col[j] - 副对角线(右上到左下):
row + col值相同,即i + col[i] == j + col[j]
以 N = 4 为例,回溯过程如下:
尝试第0行: 放在列0
尝试第1行: 列0冲突, 列1对角冲突, 放在列2
尝试第2行: 列0冲突, 列1对角冲突, 列2冲突, 列3对角冲突 → 无解, 回溯
尝试第1行: 放在列3
尝试第2行: 列0对角冲突, 放在列1
尝试第3行: 列0冲突, 列1冲突, 列2对角冲突, 列3冲突 → 无解, 回溯
尝试第2行: 无其他选择, 回溯
尝试第1行: 无其他选择, 回溯
尝试第0行: 放在列1
尝试第1行: 列0对角冲突, 列1冲突, 放在列3
尝试第2行: 放在列0
尝试第3行: 放在列2 → 找到解!
C++ 实现
#include <iostream>
#include <vector>
#include <string>
class NQueens
{
private:
int n;
// stores column index of queen in each row
std::vector<int> queens;
int solutionCount;
// check if placing queen at (row, col) is safe
bool isSafe(int row, int col)
{
for (int i = 0; i < row; i++)
{
// same column
if (queens[i] == col)
return false;
// same main diagonal
if (i - queens[i] == row - col)
return false;
// same anti-diagonal
if (i + queens[i] == row + col)
return false;
}
return true;
}
// recursive backtracking
void solve(int row)
{
// all queens placed, found a solution
if (row == n)
{
solutionCount++;
printBoard();
return;
}
// try each column in current row
for (int col = 0; col < n; col++)
{
if (isSafe(row, col))
{
queens[row] = col;
solve(row + 1);
// backtrack: queens[row] will be overwritten
}
}
}
// print the chessboard
void printBoard()
{
std::cout << "Solution " << solutionCount << ":\n";
for (int i = 0; i < n; i++)
{
std::string line(n, '.');
line[queens[i]] = 'Q';
std::cout << line << "\n";
}
std::cout << "\n";
}
public:
NQueens(int size) : n(size), queens(size, -1), solutionCount(0) {}
void solve()
{
solve(0);
std::cout << "Total solutions for " << n
<< "-Queens: " << solutionCount << "\n";
}
};
int main()
{
NQueens nq(4);
nq.solve();
return 0;
}
运行该程序将输出
Solution 1:
.Q..
...Q
Q...
..Q.
Solution 2:
..Q.
Q...
...Q
.Q..
Total solutions for 4-Queens: 2
C 实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int solutionCount = 0;
// check if placing queen at (row, col) is safe
int isSafe(int queens[], int row, int col)
{
for (int i = 0; i < row; i++)
{
// same column
if (queens[i] == col)
return 0;
// same main diagonal
if (i - queens[i] == row - col)
return 0;
// same anti-diagonal
if (i + queens[i] == row + col)
return 0;
}
return 1;
}
// print the chessboard
void printBoard(int queens[], int n)
{
solutionCount++;
printf("Solution %d:\n", solutionCount);
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (queens[i] == j)
printf("Q ");
else
printf(". ");
}
printf("\n");
}
printf("\n");
}
// recursive backtracking
void solve(int queens[], int n, int row)
{
// all queens placed
if (row == n)
{
printBoard(queens, n);
return;
}
// try each column
for (int col = 0; col < n; col++)
{
if (isSafe(queens, row, col))
{
queens[row] = col;
solve(queens, n, row + 1);
// backtrack: queens[row] will be overwritten
}
}
}
int main()
{
int n = 4;
// queens[i] = column index of queen in row i
int *queens = (int *)malloc(n * sizeof(int));
memset(queens, -1, n * sizeof(int));
solve(queens, n, 0);
printf("Total solutions for %d-Queens: %d\n", n, solutionCount);
free(queens);
return 0;
}
运行该程序将输出
Solution 1:
. Q . .
. . . Q
Q . . .
. . Q .
Solution 2:
. . Q .
Q . . .
. . . Q
. Q . .
Total solutions for 4-Queens: 2
Python 实现
def solve_n_queens(n):
solutions = []
queens = [-1] * n # queens[i] = column index of queen in row i
def is_safe(row, col):
for i in range(row):
# same column
if queens[i] == col:
return False
# same main diagonal
if i - queens[i] == row - col:
return False
# same anti-diagonal
if i + queens[i] == row + col:
return False
return True
def print_board():
board = []
for i in range(n):
line = ['.'] * n
line[queens[i]] = 'Q'
board.append(''.join(line))
return board
def solve(row):
if row == n:
solutions.append(print_board())
return
for col in range(n):
if is_safe(row, col):
queens[row] = col
solve(row + 1)
# backtrack
queens[row] = -1
solve(0)
return solutions
def main():
n = 4
solutions = solve_n_queens(n)
for idx, sol in enumerate(solutions, 1):
print(f"Solution {idx}:")
for row in sol:
print(row)
print()
print(f"Total solutions for {n}-Queens: {len(solutions)}")
if __name__ == "__main__":
main()
运行该程序将输出
Solution 1:
.Q..
...Q
Q...
..Q.
Solution 2:
..Q.
Q...
...Q
.Q..
Total solutions for 4-Queens: 2
Go 实现
package main
import "fmt"
func solveNQueens(n int) [][]string {
solutions := [][]string{}
queens := make([]int, n) // queens[i] = column index of queen in row i
for i := range queens {
queens[i] = -1
}
var isSafe func(row, col int) bool
isSafe = func(row, col int) bool {
for i := 0; i < row; i++ {
// same column
if queens[i] == col {
return false
}
// same main diagonal
if i-queens[i] == row-col {
return false
}
// same anti-diagonal
if i+queens[i] == row+col {
return false
}
}
return true
}
printBoard := func() []string {
board := []string{}
for i := 0; i < n; i++ {
line := make([]byte, n)
for j := 0; j < n; j++ {
line[j] = '.'
}
line[queens[i]] = 'Q'
board = append(board, string(line))
}
return board
}
var solve func(row int)
solve = func(row int) {
if row == n {
solutions = append(solutions, printBoard())
return
}
for col := 0; col < n; col++ {
if isSafe(row, col) {
queens[row] = col
solve(row + 1)
// backtrack
queens[row] = -1
}
}
}
solve(0)
return solutions
}
func main() {
n := 4
solutions := solveNQueens(n)
for idx, sol := range solutions {
fmt.Printf("Solution %d:\n", idx+1)
for _, row := range sol {
fmt.Println(row)
}
fmt.Println()
}
fmt.Printf("Total solutions for %d-Queens: %d\n", n, len(solutions))
}
运行该程序将输出
Solution 1:
.Q..
...Q
Q...
..Q.
Solution 2:
..Q.
Q...
...Q
.Q..
Total solutions for 4-Queens: 2
Go 实现使用切片(slice)存储每行皇后的列索引,通过闭包实现 isSafe 和 solve 的递归回溯逻辑。整体思路与 Python 版本完全一致:逐行尝试放置皇后,检测冲突后递归进入下一行,无法继续时回溯撤销放置。
优化:使用集合加速冲突检测
上面的 isSafe 函数每次都要遍历已放置的皇后,时间复杂度为 O(row)。我们可以用三个集合来维护已被占用的列和对角线,将检测降至 O(1)。
C++ 实现
#include <iostream>
#include <vector>
#include <string>
#include <unordered_set>
class NQueensOptimized
{
private:
int n;
std::vector<int> queens;
int solutionCount;
// track occupied columns and diagonals
std::unordered_set<int> cols;
std::unordered_set<int> diag1; // main diagonal: row - col
std::unordered_set<int> diag2; // anti-diagonal: row + col
void solve(int row)
{
if (row == n)
{
solutionCount++;
printBoard();
return;
}
for (int col = 0; col < n; col++)
{
int d1 = row - col;
int d2 = row + col;
// O(1) conflict check
if (cols.count(col) || diag1.count(d1) || diag2.count(d2))
{
continue;
}
// place queen
queens[row] = col;
cols.insert(col);
diag1.insert(d1);
diag2.insert(d2);
solve(row + 1);
// backtrack
cols.erase(col);
diag1.erase(d1);
diag2.erase(d2);
}
}
void printBoard()
{
std::cout << "Solution " << solutionCount << ":\n";
for (int i = 0; i < n; i++)
{
std::string line(n, '.');
line[queens[i]] = 'Q';
std::cout << line << "\n";
}
std::cout << "\n";
}
public:
NQueensOptimized(int size) : n(size), queens(size, -1), solutionCount(0) {}
void solve()
{
solve(0);
std::cout << "Total solutions for " << n
<< "-Queens: " << solutionCount << "\n";
}
};
int main()
{
NQueensOptimized nq(8);
nq.solve();
return 0;
}
运行该程序将输出(8 皇后共 92 个解,此处仅展示部分)
Solution 1:
Q.......
......Q.
....Q...
.......Q
.Q......
...Q....
.....Q..
..Q.....
... (省略中间解)
Solution 92:
......Q.
.Q......
...Q....
.....Q..
.......Q
..Q....
Q.......
....Q...
Total solutions for 8-Queens: 92
Python 实现
def solve_n_queens_optimized(n):
solutions = []
queens = [-1] * n
cols = set()
diag1 = set() # row - col
diag2 = set() # row + col
def solve(row):
if row == n:
board = []
for i in range(n):
line = ['.'] * n
line[queens[i]] = 'Q'
board.append(''.join(line))
solutions.append(board)
return
for col in range(n):
d1 = row - col
d2 = row + col
if col in cols or d1 in diag1 or d2 in diag2:
continue
queens[row] = col
cols.add(col)
diag1.add(d1)
diag2.add(d2)
solve(row + 1)
# backtrack
cols.remove(col)
diag1.remove(d1)
diag2.remove(d2)
solve(0)
return solutions
solutions = solve_n_queens_optimized(8)
for idx, sol in enumerate(solutions[:3], 1):
print(f"Solution {idx}:")
for row in sol:
print(row)
print()
print(f"... Total solutions for 8-Queens: {len(solutions)}")
运行该程序将输出
Solution 1:
Q.......
......Q.
....Q...
.......Q
.Q......
...Q....
.....Q..
..Q.....
Solution 2:
Q.......
.....Q..
.......Q
..Q.....
......Q.
....Q...
.Q......
...Q....
Solution 3:
Q.......
....Q...
.......Q
.Q......
.....Q..
.......Q (此处略)
..Q.....
...Q....
... Total solutions for 8-Queens: 92
Go 实现
package main
import "fmt"
func solveNQueensOptimized(n int) [][]string {
solutions := [][]string{}
queens := make([]int, n)
for i := range queens {
queens[i] = -1
}
cols := make(map[int]bool)
diag1 := make(map[int]bool) // row - col
diag2 := make(map[int]bool) // row + col
var solve func(row int)
solve = func(row int) {
if row == n {
board := []string{}
for i := 0; i < n; i++ {
line := make([]byte, n)
for j := 0; j < n; j++ {
line[j] = '.'
}
line[queens[i]] = 'Q'
board = append(board, string(line))
}
solutions = append(solutions, board)
return
}
for col := 0; col < n; col++ {
d1 := row - col
d2 := row + col
if cols[col] || diag1[d1] || diag2[d2] {
continue
}
queens[row] = col
cols[col] = true
diag1[d1] = true
diag2[d2] = true
solve(row + 1)
// backtrack
delete(cols, col)
delete(diag1, d1)
delete(diag2, d2)
}
}
solve(0)
return solutions
}
func main() {
solutions := solveNQueensOptimized(8)
for idx := 0; idx < 3 && idx < len(solutions); idx++ {
fmt.Printf("Solution %d:\n", idx+1)
for _, row := range solutions[idx] {
fmt.Println(row)
}
fmt.Println()
}
fmt.Printf("... Total solutions for 8-Queens: %d\n", len(solutions))
}
运行该程序将输出
Solution 1:
Q.......
......Q.
....Q...
.......Q
.Q......
...Q....
.....Q..
..Q.....
Solution 2:
Q.......
.....Q..
.......Q
..Q.....
......Q.
....Q...
.Q......
...Q....
Solution 3:
Q.......
....Q...
.......Q
.Q......
.....Q..
......Q.
..Q.....
...Q....
... Total solutions for 8-Queens: 92
Go 优化版本使用 map[int]bool 代替 Python 的 set,通过 delete 函数在回溯时移除集合元素。冲突检测通过 map 的键查询实现 O(1) 复杂度,与 Python 版本中 in 操作的语义一致。
N皇后解的数量
不同 N 值对应的解的数量:
| N | 解的数量 | 说明 |
|---|---|---|
| 1 | 1 | 只有一个位置 |
| 2 | 0 | 无解 |
| 3 | 0 | 无解 |
| 4 | 2 | 最小的有解情况 |
| 5 | 10 | |
| 6 | 4 | |
| 7 | 40 | |
| 8 | 92 | 经典的八皇后问题 |
| 9 | 352 | |
| 10 | 724 | |
| 12 | 14200 | |
| 16 | 14772512 |
复杂度分析
| 项目 | 基础回溯 | 集合优化 |
|---|---|---|
| 时间复杂度 | O(N!) | O(N!) |
| 冲突检测 | O(N) 每次检测 | O(1) 每次检测 |
| 空间复杂度 | O(N) | O(N) |
| 实际性能 | 较慢 | 快 3-5 倍 |
说明:虽然理论时间复杂度都是 O(N!),但集合优化通过将冲突检测从 O(N) 降至 O(1),在实际运行中有显著提升。回溯算法会通过剪枝(遇到冲突就跳过)大幅减少实际搜索空间,因此实际运行远快于 N! 的上界。

浙公网安备 33010602011771号