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

来源:互联网 发布:myeye cms监控软件 编辑:程序博客网 时间:2024/06/05 08:44

古老的八皇后问题估计大家都不陌生。一个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);}
1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 睫毛胶水瓶盖打不开怎么办 玻璃杯盖子滑丝怎么办 瓶盖滑扣了怎么办 胶水瓶口被塞住怎么办 美林盖子打不开怎么办 美林瓶盖打不开怎么办 泰诺瓶盖打不开怎么办 玻璃罐头瓶盖打不开怎么办 塑料罐头瓶盖打不开怎么办 喷笔壶盖打不开怎么办 陶瓷壶盖卡住了怎么办 贝德玛瓶盖摔坏怎么办 塑料盖子错位拧不开怎么办 安全瓶盖坏了怎么办 刚买面霜打不开怎么办 可乐瓶盖鼓起来怎么办 暖壶塞子吸住了怎么办 茶兀瓶盖打不开怎么办 水杯盖太紧了拧不开怎么办 矿泉水瓶盖拧不开了怎么办 弩弦用手拉不上怎么办 茅台酒瓶口漏酒怎么办 化妆品盖子丢了怎么办 化妆品盖子碎了怎么办 自制水泵压力小怎么办 大学数学不会做怎么办 下雪了怎么办教案幼儿园小班 下水道被混凝土堵塞怎么办 日本足贴丢了胶布怎么办 牙齿被可乐腐蚀怎么办 三十岁满嘴无牙怎么办 水乳盖子打不开怎么办 蜂蜜罐子打不开了怎么办 蜂蜜盖子第二次拧不开怎么办 玻璃杯子拧不开盖子怎么办 玻璃杯水杯盖子拧不开怎么办 鞋子蝴蝶结掉了怎么办 蝴蝶翅膀受伤了怎么办 手被割了个口子怎么办 致炫方向盘重怎么办 黑檀7打不透怎么办