转载请注明出处,谢谢~
上一篇博客里介绍了背景和说明,在此就不多复述了,而上一篇代码使用三个一位数组表示的皇后位置的可用与否,此篇博客就用机器语言0和1表示皇后的放置位置。
首先补充知识:
假设:
int n =2 ; int u = 1;
那么 u = (u<<n)-1;是多少??
首先,1<<2,用二进制是100(不是一百,而是二进制100),它在减去1就是011.所以 u = (u<<n) -1 = 0000 0000 0000 0011;
然后补充原码补码和反码的运算:
首先,数的存储是按照补码的形式存储的。所以二进制的运算要转化成对应补码之后再运算。假设n = 4,那么它的二进制就是 0100 , 那么它的原码反码和补码都是 0100,这是正数,那么-n的原码反码和补码分别是多少呢?-4的原码就是1100,最高位1表示符号,-4的反码是1011,除符号位外其他位取反,-4的补码是1100,就是它的反码加1.这是负数的规则。
补充 |,&,~运算规则:
|运算:
1 | 1 = 1,1 | 0 = 1 ;0 | 1 = 1;0 | 0 = 0;可以这么看,0为假,1为真,|为或者,数学的运算规则,只有 假 或者 假 的时候才为假,其他的都是真。
&运算:
1 & 1 = 1; 1 & 0 = 0 ; 0 & 1 = 0; 0 & 0 = 0;同样可以用数学规则看,就是只有 真 并且 真 的时候才为真。
~运算:
这是取反运算,例如当二进制1011进行~运算后,它就变成了0100.
补充运算技巧,以下全是按照二进制数进行运算:
1.进行|运算可以找出多个数同时为0的位置,如 1001 | 1010 ,那么就是1011,可以知道只有倒数第三位为0.
2.如果一个数 & 一个全为1的二进制数,那么得出的结果还是原数。如1111&1001 = 1001.
3.如果一个正数 & 它的相反数,那么得到的是这个数从右边开始第一个不为0的数,其他位全为0.如6是0110,那么6 &(-6)= 0010
以上有书写不对的地方,请忽略,我就是让你明白个大概意思。。。
下面先把代码列出:
void initQueen(void)
{
int n = 2;
int upperlim = 1;
if ((n < 1) || (n > 32))
{
printf(" 只能计算1-32之间\n");
exit(-1);
}
upperlim = (upperlim << n) -1;
queen(0,0,0,upperlim);
}
void queen(long raw ,long ld,long rd,int upperlim)
{
if (raw != upperlim)
{
long pos = upperlim & ~(raw | ld | rd);
while (pos)
{
long p = pos & -pos;
pos -= p;
queen(raw+p,(ld+p)<<1,(rd+p)>>1);
}
}else{
sum++;
}
}
然后我们逐行解释代码:
if ((n < 1) || (n > 32))
{
printf(" 只能计算1-32之间\n");
exit(-1);
}
这一段是根据int型的数只有32位而做的限制。
upperlim = (upperlim << n) -1;
这就是补充规则里的移位运算,n的值是根据几皇后而定,这里我们为了说的方便用的是2皇后,那么运算结果就是
upperlim = (0001 << 2) – 1 = 0011=11; 用结果中的从右开始的相邻的1的个数来表示n皇后问题所用到的位数。
queen(0,0,0,upperlim);
开始执行程序,raw表示行,ld表示左斜线,rd表示右斜线。
if (raw != upperlim)
raw != upperlim,就是表示当前还未找到问题的解,如果找到了问题的一个解,那么所有的列(不是行)应该是都放满了皇后,那么当前行就应该不能在放置皇后(不考虑斜线的情况下),也就是不在有0的存在。
long pos = upperlim & ~(raw | ld | rd);
这里根据补充的运算技巧1,可以得知raw|ld|rd就是表示当前所有不为1的位置,然后取反,即用1表示所有可以放置皇后的位置,upperlim是全1的二进制,&运算之后就是得到了pos表示一行上所有可以放置皇后的位置。
while (pos)
如果pos为0(假)了,那么就表示这一行上没有可以放置皇后的位置了。
long p = pos & -pos;
根据运算技巧3,它就可以得到从右边开始第一个不为0的二进制,用p表示我们第一次取的放置皇后的位置。
pos -= p;
在pos中减去p(p的位置置为0),即取出放置皇后的位置,回溯时如果pos中有其他位置是1,则表示还有其他位置可以放置皇后,继续循环,如果回溯时pos全为0,即pos=0,那么就表示没有位置可以放置皇后了,就跳出while循环
。
queen(raw+p,(ld+p)<<1,(rd+p)>>1);
继续下一次递归的放置皇后。其中,raw+p,p为当前放置皇后的位置,raw加上它raw的二进制就表示raw如果是0则可以放置皇后,如果是1则不可以。(在这里用0表示可以,所以在循环体前用一个~取反来置换成用1表示)。(ld+p)<<1,这是左斜线,因为左斜线占用的位置是下一行的当前位置的左边,所以这里要左移1位表示,那里不可以放置皇后,同理,右斜线要右移1位。这里都是用1表示不可以放置皇后,用0表示可以。
else{
sum++;
}
当所有的行都放置了皇后,则raw == upperlim(raw每次二进制中都会把一个0变成1,当完成一次解后,它所对应的n个0应该都为1,即等于了upperlim的值),那么else里就+1表示有了一种解法。
与上一篇算法的区别:
上一篇八皇后问题的解法是用三个一维数组的值表示该位置是否可以放置皇后,同样,一维数组里的值也是0和1,而位运算就是把一维数组的值拿出来,不看当前的整形值是多少,只看其二进制,这样就等同于一维数组的效果,其思想都是根据行循环,然后看列和左右斜线看是否能放置皇后。