位运算解决N-皇后问题

位运算的简单操作:

描述:

位运算是定义在整数上的运算。但在做位运算的时候,并不把整数看作整数,而是将它们看做一系列二进制数字,逐位进行运算。位运算有6种,他们的名称,运算符及运算规则如下:

与 (and): 5 & 6 = 4 (101 & 110 = 100)
或 (or): 5 | 6 = 7 (101 | 110 = 111)
异或 (xor): 5 ^ 6 = 3 (101 ^ 110 = 011)
取反 (not / complement): ~5 = -6 (~00000101 = 11111010)
左移 (shift left): 5 << 2 = 20 (101 << 2 = 10100)
右移 (shift right): 5 >> 2 = 1 (101 >> 2 = 1)

说明:

  • 与,或,异或都是二元运算符,逐位进行逻辑运算。

  • 取反是一元运算符,它把一个整数的所有二进制位都取反。负数在计算中用补码表示,求一个数的相反数的操作是 :取反加一,取反本身的效果是将 x变为 -1-x ,无论 x 本身的符号如何。

  • 左移时右侧添0顶位,在左侧不溢出的情况下,左移 k 位相当于乘以2^k(无论正负数);
    -右移时右侧移出的位均舍弃,左侧则有两种选择。如果一律添0顶位,则成为 逻辑右移,若重复原数的符号位,则称为算术右移。算术右移 k 位等价于除以 2^k 再向下取整(无论正负)。在有些语言中,逻辑右移和算术右移有不同的运算符;另外一些语言则只有「>>」一个运算符,可能是逻辑右移,也可能是算术右移。

  • 除取反运算符外,其他运算符均为二元运算符,可以和赋值运算符构成复合运算符。

操作:

设 a 为一个bit array 数组

  • 把第 i 位 变为1 :a |= (1 << i)
  • 把第 i 位变为 0 :a &= ~ (1 << i)
  • 把第 i 位取反 : a ^ = (1 << i)
  • 读取第 i 位的位置: (a >> i )& 1
    1 << i 是一个仅有第 i 位为 1,其他位均为0的 bit array 。它与 a 进行或运算,就可以将 a 的第 i 位置成 1;与 a 进行异或运算,就可以将 a 的第 i 位取反。把 1 << i 取反,就得到一个仅有第 i 位为 0、其它位均为 1 的 bit array;它与 a 进行与运算,就可以将 a 的第 i 位清零,而不影响其它位。读取第 i 位的值更自然的写法是 (a & (1 << i)) >> i —— 用 1 << i 与 a 进行与运算,可以仅保留 a 的第 i 位,然后再把它移到最低位来。容易证明,(a >> i) & 1 这种写法与它等价,但更简洁。
问题的输入:

输入为一个自然数 N ,表示棋盘的大小。
样例:
6

问题的输出:

输出为前三个按照字典序排列的解,最后一行为解的总数。
样例:
2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4

代码:

#include <iostream>
using namespace std;
int  n , ans[1000];
long  lime  = 1, total = 0;		//total为解的总数,lime的目的为生成n个由1组成的二进制数字。
int trans(long num){			//将bit转换成位置。
	int flag = 0;				
	while(num != 0){
		num /= 2;
		flag++;
	}
	return flag;
}
void  test(long a, long  b, long  c,int row){		//a 为竖直方向,b为从左上到右下,c为右上到左												下,row为行号 
 	if(row != n){								//如果row = n
	    long d=lime & ~ (a|b|c);					//d为一个带有可放棋子的标记的n为二进制数字
	    while(d){
	        long bit =  d & (-d);				//取出一个可放棋子的位置
	        ans[row] = trans(bit);				//转换并记录
	        d -= bit;							//取消该位置并递归搜寻下一行
	        test(a|bit , (b|bit)<<1 , (c|bit)>>1,row+1 );
	    }
	}
    else{
    	if(total < 3){							//如果total小于3
    		for(int i=0;i< n;i++){				//输出解
    			cout<<ans[i]<<" ";
    		}
    		cout<<endl;
    	}
    	total++;
    }
}


int main(){
	cout<<"输入棋盘大小:";
	cin>>n;
	lime = (lime << n ) -1;				//对lime左移n位再减一得到n位1组成的二进制数字。
	cout<<"前三个解为:\n";
    test(0, 0, 0,0);
    cout<<"解的总数为:"<<total<<endl;
}

每一行只能放并且必须放一个棋子。每次放置一个棋子之后,它就会对后面所有行中,可能放置棋子的位置产生影响。以下图为例,我们已经放置了三个棋子Q1,Q2,Q3。 接着在下面一行放置Q4时,用彩色标注出的区域都是不可行的方案,会与已经放置的三个棋子产生冲突。
在这里插入图片描述
这个冲突其实有三种不同的情况,我们用三个变量a,b,c分别来表示这三种不同的冲突。这三个变量都是一个八位的二进制数,这八位中,为1的表示有冲突,为0表示没有冲突。这三种冲突分别为:

  1. 与已放置的棋子处于同一列中;
    我们用a表示这种冲突。例如在图中,第四行会有三个位置因为列攻击而不能再放置棋子(图中大红色方块),因此a = 1000 1001。

  2. 与已放置的棋子处于同一左斜对角线中;
    用b表示这种冲突。例如在图中,蓝色方块所表示的位置,分别是由于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。 在下一个递归函数中传入的参数:

    1.对于竖直方向上产生的冲突: a |= bit 。在a中将bit为 1的一位变成1,表示这一列已经放过棋子了。

    2.对于从左上到右下的对角线及其平行线上产生的冲突:(b|bit)<<1。在这次放棋子的位置变为1,且左移一位,来表示下一行的冲突情况。

    3.对于从右上到左下的对角线及其平行线上产生的冲突:(c|bit)>>1。与2同理,标记后右移一位表示下一行的冲突情况。

long lime = 1;
lime = (lime << n ) -1; 生成n个由1组成的二进制数字。

摘抄,侵删
end.

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值