N皇后问题-位运算

Posted by Marlin on August 16, 2025

N 皇后问题-位运算

N 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

国际象棋中皇后可以攻击同行、同列以及同对角线的棋子。因此,N 皇后问题要求找到一种布局,使得任意两个皇后都不能处于同一行、同一列或同一对角线上。

冲突条件详解

在解决 N 皇后问题时,我们需要判断当前位置是否与已经放置的皇后发生冲突。冲突主要分为三种情况:

  1. 列冲突:两个皇后位于同一列
  2. 主对角线冲突:两个皇后位于同一主对角线(从左上到右下)
  3. 副对角线冲突:两个皇后位于同一副对角线(从右上到左下)

冲突条件的数学表示

对于棋盘上的两个点 (i₁, j₁) 和 (i₂, j₂):

  1. 列冲突:j₁ = j₂
  2. 主对角线冲突:i₁ - j₁ = i₂ - j₂
  3. 副对角线冲突:i₁ + j₁ = i₂ + j₂

这可以简化为一个统一的条件判断:两个皇后在同一条对角线上当且仅当 abs(j₁ - j₂) = abs(i₁ - i₂)

数组版本

数组版本通过直接检查每个已放置皇后的位置来判断冲突:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const int N = 100;
int n;       // 皇后数量
int path[N]; // 第i行的皇后摆在了那一列

// 检查0~i-1行皇后是否与(i, j)皇后冲突
bool check(int i, int j) { // i, j为当前行和列
    // 枚举前i行
    for (int k = 0; k < i; k++) { // k 为之前皇后的编号
        // 检查第k行皇后与当前(i, j)皇后是否冲突
        // j == path[k] 检查列冲突
        // abs(j - path[k]) == abs(i - k) 检查对角线冲突
        if (j == path[k] || abs(j - path[k]) == abs(i - k)) {
            return false;
        }
    }
    return true;
}

int f1(int i, int n) { // i: 当前来到的行 0-base
    // 递归结束条件
    if (i == n) {
        return 1;
    }
    int ans = 0; // 统计解的个数
    // 枚举列
    for (int j = 0; j < n; j++) {
        // 检查是否有冲突的皇后
        if (check(i, j)) {
            path[i] = j;
            ans += f1(i + 1, n);
        }
    }
    return ans;
}

int totalNqueens(int n) { return f1(0, n); }

代码解析

  • 数组记录每一行皇后所在的列位置
  • j == path[k] 检查列冲突
  • abs(j - path[k]) == abs(i - k) 检查对角线冲突,这个条件的含义是:
    • 当两个皇后的位置行差绝对值等于列差绝对值时,它们在同一条对角线上
    • 比如位置(1,2)(3,4),行差为|3-1|=2,列差为|4-2|=2,所以它们在同一条对角线上

位运算版本

位运算版本通过位操作优化冲突检查,提高算法效率:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int n;       // 皇后数量
int col;     // 用整数状态(位信息)表示是否放皇后
int left;
int right;

int f2(int limit, int col, int left,
       int right) { // limit:限制为几皇后问题 col:之前皇后的列影响
                    // left:左对角线影响 right:右对角线影响
    // 递归结束条件
    if (col == limit) {
        return 1;
    }
    int ban = col | left | right;   // 禁止的列
    int candidate = limit & (~ban); // 候选列(能够放皇后的位置)
    int place = 0;                  // 放置的列
    int ans = 0;
    while (candidate) {
        place = candidate & (-candidate); // 获取当前列(提取出最右侧的1)
        candidate ^= place;
        ans += f2(limit, col | place, (left | place) >> 1, (right | place) << 1);
    }
    return ans;
}

int totalNQueens(int n) {
    int limit = (1 << n) - 1;
    return f2(limit, 0, 0, 0);
}

位运算优化原理

  • col:记录已被占用的列(第 i 位为 1 表示第 i 列已被占用)
  • left:记录会被之前皇后攻击的左对角线位置
  • right:记录会被之前皇后攻击的右对角线位置
  • limit:限制棋盘大小,n 位全为 1 的二进制数
  • ban:禁止放置皇后的位置
  • candidate:可以放置皇后的位置

两种方法对比

特性 数组版本 位运算版本
时间复杂度 O(N!) O(N!)
空间复杂度 O(N) O(N)
实现难度 简单 复杂
执行效率 一般
可读性 较差

数组版本更容易理解和实现,而位运算版本通过位操作减少了判断冲突的时间,在实际运行中效率更高。