八皇后问题优雅解法——位运算

古老的八皇后问题估计大家都不陌生。一个8✖️ 8的棋盘,放置八个皇后(Queen),每个皇后会攻击和自己在同一行(列),同一左(右)对角线上的其他皇后。如何放置这8个皇后,才能使得没有任何皇后会互相攻击?
这个问题最传统的解法就是回溯法(back-tracking),然而这里我要讲的是一种更为优雅的解法,那就是使用位运算。

位运算

位运算就是在0,1bit级别进行的操作,这样的操作比数字的加减乘除不知道要快多少倍,因此一些大公司在面试的时候最喜欢问关于位运算的一些智力题。下面先介绍位运算的几个基本操作。

  • 按位取反

这个比较简单,就是把二进制表示中的1变为0,0变为1。比如32位系统下,0x0F取反就是0xFFFFFFF0。

~0x0F; // => 0xFFFFFFF0 (bitwise negation)
  • 按位与的符号是&,逻辑与的符号是&&,这两者是不同的。
    按位与时,两个1相与得1,其他情况均得0。(有0则0)

0x0F & 0xF0; // => 0x00 (bitwise AND)
  • 按位或的符号是|,逻辑或的符号是||,这两者是不同的。
    按位或时,两个0相或得0,其他情况均得1。(有1则1)

0x0F & 0xF0; // => 0xFF (bitwise OR)
  • 异或

x和y异或时,若x,y不同(一个是0一个是1)则结果为1,若x,y相同(都是0或者都是1)则结果为0。

0x04 ^ 0x0F; // => 0x0B (bitwise XOR)
  • 移位

将数字左移或右移,比如0000 1011左移一位就会变成0001 0110,右移一位就变成了0000 0101。要注意的有两点,第一是,左移一位相当于乘以2,右移一位相当于除以2。第二是移位时空出来的位是用0补充的,因此对于有符号数的移位需要特别小心。

0x01 << 1; // => 0x02 (bitwise left shift (by 1))
0x02 >> 1; // => 0x01 (bitwise right shift (by 1))

八皇后问题与位运算

讲了这几个位运算操作,我们接下来看看如何将它们运用到解决八皇后问题中去。
首先,我们的基本思路与传统一样,每一行只能放并且必须放一个皇后。每次放置一个皇后之后,它就会对后面所有行中,可能放置皇后的位置产生影响。如下图所示,我们已经放置了三个皇后Q1,Q2,Q3。 接着在下面一行放置Q4时,用彩色标注出的区域都是不可行的方案,会与已经放置的三个皇后产生冲突。
这个冲突其实有三种不同的情况,我们用三个变量A,B,C分别来表示这三种不同的冲突。这三个变量都是一个八位的二进制数,这八位中,为1的表示有冲突,为0表示没有冲突。
1. 与已放置的皇后处于同一列中;
我们用B表示这种冲突。例如在图中,第四行会有三个位置因为列攻击而不能再放置皇后(图中大红色方块),因此A = 1000 1001。
2. 与已放置的皇后处于同一左斜(指向右下方)对角线中;
我们用A表示这种冲突。例如在图中,蓝色方块所表示的位置,分别是由于Q1和Q2的左斜对角线上的攻击而不能够放置新的皇后, 因此B = 0001 0010。
3. 与已放置的皇后处于同一右斜(指向右上方)对角线中。
我们用C表示这种冲突。例如图中粉色方块, 是由Q2的右斜对角线的攻击造成的。因此C = 0010 0000。
八皇后问题举例
有了A,B,C,三个变量,我们很容易求出当前这一行中,有哪些位置是可以放置皇后的。这个变量我们用D来表示,我们可以写出D = ~(A|B|C)。D中为1的那些位置则是我们可以放置皇后的位置。在上图的情况下,D = 0100 0100。此时,我们有两个可能的放置皇后的位置,那么要如何取出第一个位置呢?这里有一个技巧。我们用bit表示可能的一个位置,使用bit = D & (-D)即可取出D中最右边的一个1。例如D = 0100 0100,则-D = 10111100(注意计算机中的数都是补码)。而bit = D & (-D) = 0000 0100 取出了最右边的那个1。最后还有一点,如何递归?递归中传入的参数(也即A,B,C)该如何设定?其实也很简单,特别是对于列冲突变量B来说,只要使用B|bit即可表示下一行列冲突的情况,对于A,采用(A|bit)<<1,对于C,采用(C|bit>>1)即可表示下一行中,对角线冲突的情况。

码了这么多文字,或许还是不够清楚,下面还是直接上代码吧。

#include <stdio.h>

int count = 0;

void try(unsigned char A, unsigned char B, unsigned char C){
    if(B==255){
        count ++;
        return;
    }
    unsigned char D = ~(A|B|C);
    while(D){
        unsigned char bit = D & (-D);
        D -= bit;
        try((A|bit)<<1, B|bit, (C|bit)>>1);
    }
}


int main(){
    try(0, 0, 0);
    printf("%d\n", count);
}
  • 14
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
八皇后问题是一个经典的回溯算法问题,但是使用位运算可以使得算法更为高效。具体来说,我们可以使用一个整数变量来表示每一行中哪些位置已经被占用,然后通过位运算来判断某个位置是否可以放置皇后。 具体的算法步骤如下: 1. 定义一个长度为8的数组board,用于存储每一行中皇后所在的列号。 2. 定义三个整数变量:col、ld、rd,分别表示列、左对角线、右对角线上已经被占用的位置。 3. 从第0行开始,依次遍历每一行。 4. 对于当前行i,遍历该行中的每一列j。 5. 判断当前位置是否可以放置皇后,即判断col、ld、rd三个变量中是否有与当前位置冲突的位置。 6. 如果当前位置可以放置皇后,则将该位置的列号存入board数组中,并更新col、ld、rd三个变量。 7. 递归处理下一行。 8. 如果已经处理完了第7行,则说明找到了一组解,输出结果。 9. 回溯到上一行,尝试其他的列。 10. 如果所有的列都尝试完了,仍然没有找到解,则回溯到上一行。 下面是使用位运算实现八皇后问题的Python代码: ```python def solveNQueens(): def dfs(row, col, ld, rd, path, res): if row == n: res.append(path) return for i in range(n): if not (col & 1 << i or ld & 1 << row + i or rd & 1 << row - i + n - 1): dfs(row + 1, col | 1 << i, ld | 1 << row + i, rd | 1 << row - i + n - 1, path + [i], res) n = 8 res = [] dfs(0, 0, 0, 0, [], res) return res print(solveNQueens()) ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值