c语言中关于八皇后问题的详细

八皇后问题是一个古老而又著名的问题,时间退回到1848年,国际西洋棋棋手马克斯·贝瑟尔提出了这样的一个问题

在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问一共有多少种摆法。

后面陆续有不同的学者提出自己的见解。大数学家高斯认为一共有76种摆法,1854年在柏林的象棋杂志上不同的作者发表了共计40种不同的见解,后来还有人利用图论的方法得出共有92种摆法。

而如今,通过我们的计算机以及编程语言我们可以轻松的解决这个问题。对于一个棋盘,下面我们给一个解法例子方便我们去讨论所有的算法步骤。

 当然如果使用暴力解法,那是相当复杂且难以想象的,这里我们推荐一种非常好的回溯算法,也是平时我们学过的递归中的一些算法。

1.首先我们想一想,从第一个皇后开始,每一个皇后可以在8列中任意一列存在,其他皇后在此基础上去找自己可以存在的列,接下来不同的皇后占据不同的行,所以为了满足每个皇后在不同列不同行这个条件,我们可以使用一个循环,让每个皇后从第一行开始递增,找到属于自己的列,然后并用一个数组place去记录此时此刻,8个皇后的位置,如上图就可以用以下方法表示

首先定义一个数组place[8]={0};

通过一些手段得到从第一个皇后开始的列的位置。

得到上图的位置分布place[8]={0,4,7,5,2,6,1,3};

但是我们如何去知道某一列是否被占用了呢?所以我们还需要一个标记数组,把每一列首先都定义成可以使用的1,如果被占领,则变成0。  在布尔类型中,我们用1代表true,用0代表false

bool flag[8] = { 1,1,1,1,1,1,1,1 };

如果该列是合法的,那此时这个皇后会记住该列:place[n]=col;

该列被记住后,其他列无法使用该列,所以我们需要修改标记:  flag[col]=false;

此时稍微简单的一部分我们在逻辑上已经完成了,可是八皇后问题有这么简单嘛?

很显然我们并没有去想到一个合适的方法去把每一列都完美的表达出来,如果对此图不理解可以看完分析再来看此图。

 

 

首先我们来表示上对角线:

 如何把每一个上对角线表示成独一无二的呢?我们可以用n-col来表示每一条线,则有:

一共十五个数字,那么我们就可以用一个 用一个数组d1[15]={1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,}来初始化一个表示上对角线。且要自己明白该数组中的1时从右上角往左下角排的,

但是我们考虑到数组下标不能出现负数,所以我们干脆让n-col变成n-col+7

方便我们接下来来表示占领上对角线,使其每个数字从右上角到左下角为14,13,12........2,1,0。.

知道上对角线之后,下对角线就比较好来表示了:

 可以直接用n+col来表示下对角线的每一条,

所以我们也要建立一个数组d2[15]={1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,}来表示每条对角线的情况。并自己明白这是从左上角往右下角排的。

每占领一个对角线我们就访问数组把把为占领的1改成已经占领的0。

d1[n - col + 7] = false;                //占领上对角线
 d2[n +col ] = false;                    //占领下对角线

因为每一个皇后都有8个列可以去执行,但是具体安排在哪一列我们需要去进行一个循环一次判断

for (col = 0; col < 8; col++)          //第一个皇后从第0行的第0列开始
	{                                       //每个皇后都有8个列可以走
if (flag[col] && d1[n - col + 7] && d2[n + col])         //如果该皇后的位置是合理的		
   {

             //一些方法
   }
}


此时一个皇后在找到了自己可以占领的位置,接下来就应该去表示第二个皇后的情况了,但是到了最后一个皇后,只要满足情况,我们就不用再讨论下一个皇后,直接打印棋盘就可以了

			if (n < 7)                                //进行下一个皇后
				generate(n + 1);
			else
				print();                 	//最后一个皇后了开始打印

但是:假如说前面7个皇后位置现在确定了,第八个皇后如果在n=7,也就是最后一行的某个位置满足时,我们已经把标记填满了,所有位置此时都是0,下一种情况怎么判断?所以我们需要进行回溯,我这种情况满足之后,打印了棋盘,我应该把此时的标记去删去。

flag[col] = true;                        
			d1[n - col + 7] = true;
			d2[n + col] = true;

 在接下来我们已经完成了逻辑上的表示,那我们是不是也应该相应的去打印每一种情况,打印每一种情况的棋盘呢?我们有如下代码:

void print()
{
	int col, i, j;                         
	number++;                      //标记一共有几种可能
	printf("No.%d\n", number);
	int table[8][8] = { 0 };             //定义棋盘大小
	for (i = 0; i < 8; i++) 
		table[i][place[i]] = 1;           //标记皇后位置
	for (i = 0; i < 8; i++)
	{
		for (j = 0; j < 8; j++)
		{
			printf("%d  ", table[j][i]);          //打印出结果
		}
		printf("\n");
	}
}

我们用number表示此时是第几种情况,用table二维数组来定义一下棋盘的大小,用0表示棋格,用1表示皇后位置。最后在把此棋盘打印出来。

 

所以我们总体算法思路如下:首先定义一个无返回值的函数generate(),主函数调用时,定义一共有n=8个皇后,从第一个皇后ganerate(0)开始,第一个皇后始终在第0行,但是有8个列可以选择,遇到合法的位置就会进行标记,然后进行第二个皇后generate(1)进行合法位置的循环,找到可以的位置就标记,一直到第8个皇后generate(7)时,此时第八个皇后的位置是唯一,就不再进行下一个皇后的迭代,直接调用print()函数打印此时的情况,并用number记录这是第几个解,第八个皇后位置出现了之后,应该删除标记,一直删除到可能出现其他位置的皇后那里,进行循环,列的递增,把所有的情况找完。

打个简单的比喻,假如说第6个皇后generate(5),找到自己的第一个合法位置时,标记此时的位置,第7个皇后也找到自己的第一个合法位置,标记此时的位置,第八个皇后的位置其实就已经确定了,然后标记此时自身位置,打印棋盘。删除标记到第七个皇后可能有第二个合法位置的时候,再去确定第八个皇后的位置,标记自身位置,打印棋盘。此时第七个皇后在第六个皇后第一个合法位置时的情况已经判断完了,第六个皇后假如有第二个合法位置,那么第七个皇后的位置相应变化,第八个皇后位置相应也变化。第6个的所有合法位置都判断完,那么就要去找第五个皇后的第二种合法位置时,此时第六个皇后以及后面的皇后位置。

时间复杂度为

完整代码:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int place[8] = { 0 };
bool flag[8] = { 1,1,1,1,1,1,1,1 };
//上下对角线一共-7,-6,-5.......,5,6,7一个15个
bool d1[15] = { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, };
bool d2[15] = { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, };
int number = 0;
void print();
void generate(int n);
int main() 
{
	generate(0);
	return 0;
}
//n-col为了让得到上对角线-7,-6,-5.......,5,6,7,n-col+7得到0到14,一共15个数字
//n+col可以得到下对角线的0到14;
void generate(int n)
{
	int col;
	for (col = 0; col < 8; col++)       //第一个皇后从第0行的第0列开始
	{                                       //每个皇后都有8个列可以走
		if (flag[col] && d1[n - col + 7] && d2[n + col])         //如果该皇后的位置是合理的
		{
			place[n] = col;                      //记住该列
			flag[col]= false;                        //占领该列
			d1[n - col + 7] = false;                //占领上对角线
			d2[n +col ] = false;                    //占领下对角线
			if (n < 7)                                //进行下一个皇后
				generate(n + 1);
			else
				print();                 	//最后一个皇后了开始打印
														 //回溯,取消占领把所有可能情况都迭代出来
			flag[col] = true;                        
			d1[n - col + 7] = true;
			d2[n + col] = true;
		}
	}
}
void print()
{
	int col, i, j;                         
	number++;                      //标记一共有几种可能
	printf("No.%d\n", number);
	int table[8][8] = { 0 };             //定义棋盘大小
	for (i = 0; i < 8; i++) 
		table[i][place[i]] = 1;           //标记皇后位置
	for (i = 0; i < 8; i++)
	{
		for (j = 0; j < 8; j++)
		{
			printf("%d  ", table[j][i]);          //打印出结果
		}
		printf("\n");
	}
}

本文讲的比较详细,如果有疑问可以和作者联系,创作不易,麻烦各位读者点点赞,如果觉得作者文章质量不错可以关注一下作者,鼓励发布更有质量的文章。

 

  • 31
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 18
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值