八皇后

八皇后问题是大一 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,因为这样可以减少位操作。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值