位运算“秒解”八皇后:回溯算法的性能飞升实战

八皇后问题是回溯算法的经典案例,但普通解法在冲突判断时会做大量冗余计算。本文分享一种位运算优化的回溯解法:用二进制掩码替代数组判断冲突,代码更简洁、效率直接起飞,还能直观打印所有解的棋盘!

一、八皇后问题的核心约束

要在8×8棋盘放8个皇后,需满足3条规则:

    1.    同列不冲突:每列只能有1个皇后;

    2.    左对角线不冲突:行差的绝对值 ≠ 列差的绝对值;

    3.    右对角线不冲突:同理左对角线。

二、位运算优化的核心思路

位运算的精髓是用二进制数的“位”表示“列/对角线的占用状态”,通过位操作快速判断冲突、选择可用列。

以8皇后为例,我们用3个整数(二进制掩码)记录状态:

    •    colMask:列掩码,某一位为1表示该列已被占用;

    •    leftDiagMask:左对角线掩码,某一位为1表示该左对角线已被占用;

    •    rightDiagMask:右对角线掩码,某一位为1表示该右对角线已被占用。

三、完整代码实现(C++)
#include <iostream>
using namespace std;

const int N = 8;        // 棋盘大小
int solutionCount = 0;  // 解的数量
int queens[N];          // 记录皇后位置(queens[i] = j 表示第i行第j列)

/**
 * 位运算回溯+记录皇后位置
 * @param row 当前行
 * @param colMask 列掩码
 * @param leftDiagMask 左对角线掩码
 * @param rightDiagMask 右对角线掩码
 */
void backtrack(int row, int colMask, int leftDiagMask, int rightDiagMask) {
    // 递归终止:所有行都放好皇后,打印当前解
    if (row == N) {
        cout << "第 " << ++solutionCount << " 种解:\n";
        for (int i = 0; i < N; ++i) {
            for (int j = 0; j < N; ++j) {
                cout << (queens[i] == j ? "Q " : ". ");
            }
            cout << endl;
        }
        cout << endl;
        return;
    }

    // 计算当前可用列:取反后与“全1掩码”相交,得到可用位为1的数
    int availableCols = ~(colMask | leftDiagMask | rightDiagMask) & ((1 << N) - 1);
    
    while (availableCols) {
        // 提取最右边的可用列(二进制位)
        int colBit = availableCols & -availableCols;
        // 把二进制位转换为列号(比如0b100对应第2列)
        int col = __builtin_ctz(colBit);
        
        queens[row] = col;  // 记录当前行皇后的列号
        
        // 递归下一行:更新掩码
        backtrack(
            row + 1,
            colMask | colBit,          // 列掩码:标记当前列已占用
            (leftDiagMask | colBit) << 1,  // 左对角线掩码:左移1位
            (rightDiagMask | colBit) >> 1  // 右对角线掩码:右移1位
        );
        
        // 清除当前尝试的列(继续尝试其他可用列)
        availableCols &= availableCols - 1;
    }
}

int main() {
    backtrack(0, 0, 0, 0);
    cout << "八皇后问题共有 " << solutionCount << " 种解\n";
    return 0;
}
四、关键代码解析

1. 可用列计算
int availableCols = ~(colMask | leftDiagMask | rightDiagMask) & ((1 << N) - 1);
    •    把“列/左对角线/右对角线”的占用位合并(colMask | leftDiagMask | rightDiagMask);

    •    取反后,用(1 << N) - 1(8位全1,即0b11111111)“截取”低8位,得到当前行可放皇后的列(对应位为1)。

2. 提取最右可用列
int colBit = availableCols & -availableCols;
利用补码特性,快速提取availableCols最右边的1(比如availableCols=0b1010,结果为0b0010)。

3. 列号转换
int col = __builtin_ctz(colBit);
__builtin_ctz是GCC内置函数,返回“二进制数末尾0的个数”:

    •    比如colBit=0b100(十进制4),末尾有2个0,对应第2列(从0开始计数)。

4. 掩码更新

递归下一行时,需要更新3个掩码:

    •    列掩码:colMask | colBit → 标记当前列已占用;

    •    左对角线掩码:(leftDiagMask | colBit) << 1 → 下一行的左对角线是当前左对角线左移1位;

    •    右对角线掩码:(rightDiagMask | colBit) >> 1 → 下一行的右对角线是当前右对角线右移1位。

五、运行效果

程序会输出8皇后的所有92种解,每种解以棋盘形式展示(Q表示皇后,.表示空位),例如其中一种解:
第 1 种解:
Q . . . . . . . 
. . . . Q . . . 
. . . . . . . Q 
. . . . . Q . . 
. . Q . . . . . 
. . . . . . Q . 
. Q . . . . . . 
. . . Q . . . . 
六、为什么位运算更快?

普通回溯法需要用数组遍历判断冲突(时间复杂度O(n)),而位运算通过二进制位操作(与、或、非、移位)实现冲突判断,操作是O(1)时间,且CPU对二进制运算的支持极高效,能大幅减少计算量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值