八皇后详解

 

 

    还是先简单介绍下八皇后问题:

    八皇后问题是一个古老而著名的问题,是回溯算法的典型例题。该问题是十九世纪著名的数学家高斯1850年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

 

    解这题需要用到回溯法,先简单介绍递归和回溯。回溯与递归很像。它们的本质区别在于递归每次都能成功,沿着递归函数进去肯定能进入极值。举个求和的简单例子:

    //求1至n的和

  

 int sum(int n){
	if(1==n)
    		return 1;
	else
    		return n+sum(n-1);
 }


    所有数的和等于n加上前(n-1)个数的和,而前(n-1)个数又等于n-1加上前(n-2)个数的和,如此下去,前2个数的和等于2加上前1个数之和,前1个数的和就是1,程序运行至此就一直返回。

    在这个递归的例子中,每进入一个新函数,返回值最终都能成功。也就是说沿着函数一直进去肯定能找到n==1的情况。

 

    虽然回溯要想解决问题还得借助递归思想,但是回溯进入新的函数却不一定能找到答案,比如要从这张二叉树图片

中的根节点(0号)开始找到一个存放在11号叶结点的东西(之前你只知道东西放在某个叶结点而已),在没有任何帮助条件下,为了找到目标,你得挨个找,假设从左往右找。0号->1号->3号->7号,进到7号后你发现没有,这时你必须得返回至3号,然后往右移,进入8号,也没有,返回至3号,然后发现7号和8号找过了,于是从3号返回至1号,接着1号->4号->9号……就这么进去找,找不到就返回前一个再向右移,最后会进入到11号找到东西。

 

    这就是回溯法的思想,找不到就返回前一个,接着找,直到找到为止。

 

    八皇后的思路大致是这样的,挨行或者挨列放置皇后(这里先使用挨列),使其都不冲突。

    先在第一列放置皇后,记录放置的行号(行号从小到大挨个试,下同),再放置第二列(前提不与前面的皇后冲突),同样也记录行号,然后放置第三、第四列(前提也是不与前面的皇后冲突),这里注意一下,正在尝试第N列皇后的放置时说明前(N-1)列皇后放置是不冲突的。当发现某列无法放置皇后时,就返回前一列,行号加一(就跟上面二叉树例子一样,返回一步后,向右一个试探,这里按行号从小到大试探),继续试探。如果放置好了第八列的皇后就得到了一种放置的方法。接着行号加一或者返回前一列,继续试探,直到把所有的情况都试完。

 

现在把代码贴上来:注释足够多,讲得应该很清楚了。

 
//#include <iostream.h>
#include <stdio.h>
 
#define N 8
 
int site[N];//保存各列皇后放置的行数,下标表示列号(0-7),site[i]保存放置行号(1-8)
int iCount=0;//表示可行解的数目
 
void Queen(int n);//执行并放置第n列皇后的回溯函数,参数n表示第n列(从0开始)
bool isValid(int n);//判断第n列放置的皇后是否与前面的冲突,冲突返回false,有效返回true
void outPut();//找到一个可行解后,把可行解输出来
 
int main(){
	//从第0列开始放置皇后
	Queen(0);
	//任意输入结束程序
	//getchar();
	return 0;
}
void Queen(int n){
	int i;
	//N==n时,表示前面的8列(列号为0-7)都已经放好,把一组解输出
	if (N==n)
	{
		outPut();
		return;
	}
	//i表示行号,第n列皇后将放置的行号
	for (i=1;i<=N;i++)
	{
		site[n]=i;//这条语句保证了每一列皇后的放置都会从第1行试探到第N行
		//如果放置第n列成功,则继续尝试第n+1列
		if (isValid(n))
			Queen(n+1);
	}
}
bool isValid(int n){
	int j;
	for (j=0;j<n;j++)
	{
		if (site[n]==site[j]||(site[n]-site[j])==(n-j)||(site[n]-site[j])==(j-n))
			return false;
		//site[n]==site[j]表示放置的行号相同,冲突
		//因为是逐列操作,不存在放置的列号相同
		//site[n]-site[j]==n-j,斜率为1,表示在斜率为1的对角线上
		//site[n]-site[j]==j-n,斜率为-1,表示在斜率为-1的对角线上
	}
	return true;
}
void outPut(){
	int j;
	iCount++;
	printf("%-4d",iCount);
	for (j=0;j<N;j++)
	{
		printf("%3d",site[j]);
	}
	printf("/n");
}


上面的代码没有设置冲突状态,都是要判断时才临时算的,所以退回去时不需要更改原来的状态。相信大家也看过下面保存冲突与否的状态的代码。
//这代码是逐行放置皇后的,

#include <stdio.h>
bool col[8]={false};//竖直线(列线),用于记录列线冲突状态
bool Left[15]={false};//左对角线,用于记录左对角线冲突状态,拿笔画画就知道有15条左对角线了
bool Right[15]={false};//右对角线,用于记录右对角线冲突,同样也有15条右对角线
int site[8]={0};//保存每行放置皇后的列号
int sum=0;//记录可行解数
int row=0;//行号,本代码是逐行放置的,这里的row是可以当做Queen函数的参数的。
void Queen();//如果row作为Queen的参数,这条语句变成void Queen(int row);
int main(){
	Queen();//如果row作为Queen的参数,这条语句变成Queen(0);
	return 0;
}
//这里给出Queen函数的两种写法,趋向于第二种写法,因为把极值单独列出来很直观,思路容易跟上
void Queen(){//如果row作为Queen的参数,这条语句变成void Queen(int row){
	/*函数写法一
	//对于每行,都从第0列试到第7列,l表示列号
	for (int l=0;l<8;l++)
	{
		//col[l]判断列号是否相同我想应该很容易就理解,判断对角线的两个数组可能费劲点
		//Left[row+l],row表示行号,l表示列号,这样理解,从左上角开始,下移到行号的地方,然后
		//再右移到列号的地方,那个点所在的对角线就记录在Left[row+l]里
		//Right[row-l+7],理解成Right[row+7-l],从左上角开始,下移至行号的地方,然后移到最右,
		//接着左移列号个单位,那个点所在的对角线就记录在Right[row+7-l]了
		if (!col[l]&&!Left[row+l]&&!Right[row-l+7])
		{
			//判断这个点可以放置后,就记录列号,并修改状态
			site[row]=l;
			col[l]=true;
			Left[row+l]=true;
			Right[row-l+7]=true;
			//然后行号加1;
			row++;//如果row作为Queen的参数,这条语句不要
			//row==8时,说明前8行都放好了(行号从0开始)
			if (row==8)
			{
				//可行解加一
				sum++;
				printf("%-4d",sum);
				for (int i=0;i<8;i++)
					printf("%4d",site[i]);
				printf("/n");
			}else
				//如果row作为Queen的参数,这条语句变成Queen(row+1);
				Queen();//row<8时,接着放置下一行的皇后,
			//如果失败,则返回到前一行,返回后要立马恢复原来的状态。
			row--;//如果row作为Queen的参数,这条语句不要
			col[l]=false;
			Left[row+l]=false;
			Right[row-l+7]=false;
		}
	}
	*///函数写法一
	//函数写法二
	if (row==8)
	{
		sum++;
		printf("%-4d",sum);
		for (int i=0;i<8;i++)
			printf("%4d",site[i]+1);
		printf("/n");
		return;
	}
	int l;
	for (l=0;l<8;l++)
	{
		//site[row]=l写在这里也可以
		if (!col[l]&&!Left[row+l]&&!Right[row-l+7])
		{
			site[row]=l;
			col[l]=true;
			Left[row+l]=true;
			Right[row-l+7]=true;
			row++;
			Queen();
			//一返回上一级就得立马恢复原来的状态
			row--;
			col[l]=false;
			Left[row+l]=false;
			Right[row-l+7]=false;
		}
	}//写法二
}


好了,到这里相信大家对八皇后都很熟悉了。对回溯也有更深的了解了,不过还得多做些题,递归和回溯好用但不好理解。
稍微改改就可以得到任意皇后的排列代码了。
结果是:
皇后数 解个数
1 1
2 0
3 0
4 2
5 10
6 4
7 40
8 92
9 352
10 724
11 2680
12 14200
.
.

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值