De Front-end Dev Engineer

Java 位运算解八皇后算法

2018-05-14

最快的八皇后解法 – 位运算

目前最快的N皇后递归解决方法* N Queens Problem* 试探-回溯算法,递归实现:

    public class Queens {
        // sum用来记录皇后放置成功的不同布局数;upperlim用来标记所有列都已经放置好了皇后。
        private static long sum = 0, upperlim = (1 << 8) - 1;// 这里是8皇后

        // 试探算法从最右边的列开始。
        public static void test(long row, long ld, long rd) {
            if (row != upperlim) {
                // row,ld,rd进行“或”运算,求得所有可以放置皇后的列,对应位为0,
                // 然后再取反后“与”上全1的数,来求得当前所有可以放置皇后的位置,对应列改为1
                // 也就是求取当前哪些列可以放置皇后
                long pos = upperlim & ~(row | ld | rd);// 所有可以放的位置
                while (pos != 0) { // 0 -- 皇后没有地方可放,回溯
                    // 拷贝pos最右边为1的bit,其余bit置0
                    // 也就是取得可以放皇后的最右边的列
                    long p = pos & -pos;// 最右边的位置

                    // row + p,将当前列置1,表示记录这次皇后放置的列。
                    // (ld + p) << 1,标记当前皇后左边相邻的列不允许下一个皇后放置。
                    // (ld + p) >> 1,标记当前皇后右边相邻的列不允许下一个皇后放置。
                    // 此处的移位操作实际上是记录对角线上的限制,只是因为问题都化归
                    // 到一行网格上来解决,所以表示为列的限制就可以了。显然,随着移位
                    // 在每次选择列之前进行,原来N×N网格中某个已放置的皇后针对其对角线
                    // 上产生的限制都被记录下来了
                    test(row + p, (ld + p) << 1, (rd + p) >> 1);
                    // 将pos最右边为1的bit清零
                    // 也就是为获取下一次的最右可用列使用做准备,
                    // 程序将来会回溯到这个位置继续试探
                    pos -= p;
                }
            } else {
                // row的所有位都为1,即找到了一个成功的布局,回溯
                sum++;
            }
        }

        public static void main(String[] args) {
            test(0, 0, 0);
            System.out.println(sum);
        }

    }

位运算的几个基本操作:

  1. 按位取反
    这个比较简单,就是把二进制表示中的1变为0,0变为1。比如0x0F取反就是0xFFFFFFF0。
    ~0x0F; // => 0xFFFFFFF0 (bitwise negation)
    

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

  3. 按位或的符号是|,逻辑或的符号是||,这两者是不同的。
    按位或时,两个0相或得0,其他情况均得1。(有1则1)
    0x0F & 0xF0; // => 0xFF (bitwise OR)
    
  4. 异或
    x和y异或时,若x,y不同(一个是0一个是1)则结果为1,若x,y相同(都是0或者都是1)则结果为0。
    0x04 ^ 0x0F; // => 0x0B (bitwise XOR)
    
  5. 移位
    将数字左移或右移,比如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))
    

Similar Posts

Comments