最快的八皇后解法 – 位运算
目前最快的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变为0,0变为1。比如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))