八皇后问题是大一 C 语言课本上的一个问题,当时没有想过,今日闲来无事,欲解之而后快。
模拟摆放皇后的过程,一次放一个,以列为单位来摆放,依次摆放到第1列,第2列,。。。第8列上面,而且,每次摆放的皇后与之前的皇后都不在一行上面。
如果我们把皇后占的位置标记为1,末占的位置记为 0,则:
1.把所有列构成的二进制进行与操作,则结果一定是0.
2.每列的二进制数据向左,向右移动 1 位,如果与向右数一列的二进制相与等于0才说明是一个合理的摆放,向左,向右移动 2 位与向右数二列的二进制相与等于0说明是一个合理的摆放。。。
如图 1 所示:
图1 八皇后棋盘二进制运算
于是,我们可以用递归思路来解决这个问题,摆放第 i + 1 个皇后时,需要根据前 i 个皇后的摆放状态主动去获得有效的位置,再针对这些位置分别进行递归(分支递归),于是问题便可解。
给出实现的代码。
#define SIZE 8
//typedef unsigned char sint;
typedef int sint;
sint layout [SIZE] = {0};
int ncount = 0;
/************************************************************************/
/*
棋盘中的每列看成一个二进制,从第 0 行到第 8 行分别是最低位到最高位。
0
1
2
3
4
5
6
7
8
ava 函数是针对当前摆放(layout 的值),获得当前递归层(在第 cur 列上摆放皇后)可以摆放的位置。
*/
/************************************************************************/
sint ava(int cur)
{
//横向可用,0 表示可用
sint rowAvaInts = 0;
for (int i = 0;i < SIZE;++ i)
{
rowAvaInts |= *(layout + i);
}
//对角线可用,0 表示可用
for (int i =0;i < SIZE;++ i)
{
rowAvaInts |= ((*(layout + i)) << (cur - i)); //向右下角方向的对角线,与 cur 对应的列的交点是否可以安放
rowAvaInts |= ((*(layout + i)) >> (cur - i)); //向右上角方向的对角线,与 cur 对应的列的交点是否可以安放
}
return ~rowAvaInts; //返回值为 1 的那些位表示是可用的
}
//验证当前摆放局势(layout 的值)的正确性
bool isRight()
{
//检验行
sint rowAvaInts = ~((sint)0);
for (int i = 0;i < SIZE;++ i)
{
rowAvaInts &= *(layout + i);
}
if(0 != rowAvaInts)
{
return false;
}
//检验对角线
for (int i =0;i < SIZE;++ i)
{
for (int j = i + 1;j < SIZE;++ j) //从当前列向后检验对角线
{
if(0 != ((*(layout + j)) & ((*(layout + i)) << (j - i))))
{
return false;
}
if(0 != ((*(layout + j)) & ((*(layout + i)) >> (j - i))))
{
return false;
}
}
}
return true;
}
//求 a 是 2 的几次方
int qrt(sint a)
{
int ncount = -1;
while (true)
{
if(0 == a)
{
break;
}
a = a >> 1;
++ ncount;
}
return ncount;
}
//显示当前摆放局势
void showLay()
{
if(!isRight())
{
printf("catch an error ans --------- \r\n");
return;
}
++ ncount;
for (int i= 0;i < SIZE;++ i)
{
printf("%d ,",qrt(*(layout + i)));
}
printf("\r\n");
}
//在第 cur 列上摆放皇后
void lay(sint cur)
{
if(cur == SIZE)
{
showLay();
return;
}
sint avaInts = ava(cur);
sint t = 1;
for(int i = 0;i < SIZE;++ i) //处理 SIZE 个位置
{
if(avaInts & t)//ava 的第 i 位是1
{
*(layout + cur) |= t; //将第 cur 列上的 t 对应的位置放置皇后
lay(cur + 1);
*(layout + cur) &= (~t); //复位
}
t = t << 1;
}
}
int _tmain(int argc, _TCHAR* argv[])
{
lay(0);
printf("in total : %d\r\n",ncount);
system("PAUSE");
return 0;
}
上面的解法会给出92种解法,这里面有很多重复的解,原解,旋转90度,180度,270度得到的解是一样的。而且这里面还要对半开,每一种旋转之后再进行左右对称或者是上下对称便又缩减一半的解。这里面还可能有某些解开生是对称的。
最终的去重复解为 12 种(11*8+1*4=92)。具体情况可参照:http://bbs.csdn.net/topics/353989。
上面的代码中还对每个解进行了正确性验证。同时,上面的代码直接将 SIZE 变为其它的值就可以扩展为 n 皇后问题的解,不过要注意在机器允许表示的二进制范围内。为了获得最佳的性能,建议对于 n 皇后合理选择 sint 的长度,如对于八皇后时,最好使用 unsigned char 表示 sint,因为这样可以减少位操作。