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)存储每行皇后的列索引,通过闭包实现 isSafesolve 的递归回溯逻辑。整体思路与 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! 的上界。

posted @ 2026-04-16 10:39  游翔  阅读(48)  评论(0)    收藏  举报