C解决八皇后问题

问题表述为:在8×8格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
在这里插入图片描述
思路1

  1. 棋盘是8乘8块格子,那么用8乘8的数组来表示棋盘是合适的
  2. 一个位置能否放置格子,得看他的衡竖方向的直线,以及斜线是否有皇后,如果有,则不能放置。如图:在这里插入图片描述
    这里k表示斜率,以(0,0)为坐标原点的话,两条斜线的斜率分别为1和-1。那么根据点斜式:y - y0 = k(x - x0),变式为:y = kx +b。
    所以这两条斜线的方程为:y = x + y0 -x0,以及y = x0 + y0 - x;另外两条更是十分轻松,分别是,y = y0,x = x0。所以,根据这些原理编写一个是否能放置皇后位置的函数,该函数接受一个坐标(x,y)以及表示棋盘的二维数组,若可以放置就返回1,否则返回0:
int check_queen(int x,int y,int queens[][8])
{

//标志量,都为1则表示可放置皇后,默认为可放置皇后
	int flag_x = 1;
	int flag_y = 1;
	int flag_NE = 1;
	int flag_NW = 1;
	
	//x方向上的检查,按行放置的话,这段代码可免。建议先把下面这段注释取消掉
	/**
	for( int i = 0;i < MAX_SIZE; i++){
		
		if( queens[x][i] == 'Q'){
			flag_x = 0;
			break;
		}
	} 
	*/
	
	//y方向上的检查 这两个代码块其实是可以合并成一个代码块的,但是也会带来潜在的隐患 
	for( int i = 0; i< MAX_SIZE; i++){
		
		if(queens[i][y]){
			flag_y = 0;
			break;
		}
	}
	// 由斜率 y - y1 = k(x - x1 ) 变换而来的 y = kx + b 
	
	int b1 = y -x;
	int b2 = x + y;
	
	//斜率为1方向上的检查
	for( int i = 0; i < MAX_SIZE; i++){
		
		//之所以用cotinue是因为有些斜线所穿过的格子并不全在8乘8的棋盘里 
		if( i + y - x > 7 || i + y - x < 0)continue;
		
		if( queens[i][ i + y - x]){
			flag_NE = 0;
			break;
		}
	} 
	
	//斜率为-1方向上的检查,这两个虽然也可以进行合并,但是由于判断条件不同,所以,不推荐进行合并 
	for( int i = 0; i < MAX_SIZE; i++){
		
		if( x + y - i > 7 || x + y -i < 0)continue;
		if( queens[i][ x + y - i]){
			flag_NW = 0;
			break;
		}
	}
	
	if( flag_x && flag_y && flag_NE && flag_NW){
		return 1;
	}
	else{
		return 0;
	}
}

3.接下来便是问题的关键。
我们怎么放置皇后呢?
默认的思路是。从第0行开始,检查当前位置是否能放置一个皇后,若不能则寻找该行的下一个能放置皇后的位置,若找到了一行的列尾部,那么程序就该返回。若能放置皇后,就放置皇后,并进入下一行,然后进入第一步,直到找到最后一行的皇后被放置,然后返回。根据这个思路,我们编写函数如下(该函数接受一个行数,和一个表棋盘的二维数组):

void travelsing_chessBoard(int cols,int queens[][8])
{
	

	for( int i = 0; i < MAX_SIZE; i++)
	{
		if( check_queen(cols,i,queens) ){
			
		

			queens[cols][i] = 'Q';
			
			//成功放置一个皇后,则进入下一行的皇后检查,如果已经是最后一行,那么就返回 
			if( cols == MAX_SIZE - 1)
			{
				//打印棋盘 
				print_chessBoard(queens);
				
				return;
			}
			travelsing_chessBoard( cols + 1,queens);
		}
				
	}
	
	
	//如果出了行的遍历,就代表到达了行末还没有找到可以放皇后的位置应该返回 
	return;	
	
	
} 

在这里先放出程序的主体:

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define MAX_SIZE 8

int Chess_manual = 0; 

int check_queen(int x,int y,int queens[][8]);
/*
*参数列表的cols和rows是x坐标和y坐标,表面目前检查的位置 
*/ 

//棋盘打印函数
void print_chessBoard(int queens[][8]);

void travelsing_chessBoard(int cols,int queens[][8])

int main()
{
	int queens[MAX_SIZE][MAX_SIZE] = {0};
	travelsing_chessBoard(0,queens);
	printf("\n共有%d张棋谱",Chess_manual);
 } 

当把检查函数(检查函数x的方向上注释应该去掉)和遍历函数填进去,执行后,程序输出0个棋盘。
这是为什么呢?很明显,我们人工遍历一次就可以知道了。
这就是刚才程序的执行结果
在这里插入图片描述第五行的皇后被放置之后,第六行便没有合适的位置来放置皇后呢。而此时皇后的放置行数没有达到最后一行,所以不会打印棋盘。

		//成功放置一个皇后,则进入下一行的皇后检查,如果已经是最后一行,那么打印棋盘,然后返回 
			if( cols == MAX_SIZE - 1)
			{
				
				print_chessBoard(queens);
				
				return;
			}

所以程序最后到第6行的末尾,然后返回,即:

//如果出了行的遍历,就代表到达了行末还没有找到可以放皇后的位置应该返回 
	return;	

就这样,程序返回到了第五行所放置皇后的下一个位置,因为该位置有皇后了,所以后面的位置都不能放皇后,所以这样一直返回到了第0行,程序结束。
这就暴露了刚才所编写函数的缺点了。

  1. 死路问题:当k(k > 1)行不能放置皇后,程序返回到(k - 1)行后,该行皇后位置并没有做出更改。导致程序不能走其他路,就陷入了死路。所以,我们应该移动k-1行的皇后的位置,然后重新走一遍。也就是说:当k行无法放皇后的时候,我们应该移动k-1行皇后的位置,然后在走一遍。

所以对函数改进一下(注释掉x方向上的检查),变为:

	int rows_note = 1; //用于记录本行皇后的位置 
	int num = 0;	  // 用于记录本行皇后的数量 

	for( int i = 0; i < MAX_SIZE; i++)
	{
		if( check_queen(cols,i,queens) ){
			
			//当一行放置了大于一个的皇后的时候,移除掉前面放置的皇后。因为是按行的顺序放置的,所以说,行的检查是要避免的 
			if(num > 0){
				
				queens[cols][rows_note] = 0;
				num--;
				
			}

			queens[cols][i] = 'Q';
			rows_note = i;
			num++;
			
			//成功放置一个皇后,则进入下一行的皇后检查,如果已经是最后一行,那么就返回 
			if( cols == MAX_SIZE - 1)
			{
				//打印棋盘 
				print_chessBoard(queens);
				
				return;
			}
			travelsing_chessBoard( cols + 1,queens);
		}
				
	}
	//如果出了行的遍历,就代表到达了行末还没有找到可以放皇后的位置应该返回 
	return;	
	

执行后,函数依然是0张棋谱,这是为什么?我们在跟着程序返回一遍。
因为即使我们想更改第五行的皇后的位置,但是并没有第五行第二个位置可以放皇后了,所以程序返回到第四行放皇后的地方。同样是没有合适的位置,就这样一直放回到第0行。这一切的根源是我们想更改第五行的皇后的位置时,没有合适的位置了,但是我们没有清空他,导致第四行皇后取舍的时候会受到它的英雄。这也是一个死路问题。没有位置再放第二个,就代表该行的遍历出了for循环,,所以我们在该函数的最后一个return前加上这句:

queens[cols][rows_note] = 0; 

再次运行或发现,程序的输出结果只有一次(字符Q的值是81)。即:在这里插入图片描述多次运行都是这个结果,这是为什么呢?
很明显,当每一行都正确放置了皇后后,函数就直接返回了。因为什么?其实皇后放到最后一行,也是死路,虽然是我们要的结果,但我们并没有清除,也就是说函数返回时,他依然会发生我们前面说的低层皇后影响上一层皇后的取舍问题。所以,当最后一行的皇后成功放置时,打印棋盘后,将皇后去掉。即:

queens[cols][rows_note] = 0;

在调用打印棋盘函数之后加上这句,程序便可正确输出了。
可见,主要是死路问题阻挠了程序的执行。
但死路的表现形式又是多种多样化的,有行内的影响,有上下行的影响。
另外每一条代码并不都是全时段有效代码,比如,当成功找到最后一行的一个皇后,程序还试图在该行寻找第二个皇后等。是无效也是无意义的。但是,如果删掉这一步骤,则有多个合适位置放皇后的行便会收到影响。也就是说,是否进行优化,还是有讨论性的。

这就是本文的完整代码

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define MAX_SIZE 8

int Chess_manual = 0; 

int check_queen(int x,int y,int queens[][8])
{
	int flag_x = 1;
	int flag_y = 1;
	int flag_NE = 1;
	int flag_NW = 1;
	
	//x方向上的检查,按行放置的话,这段代码可免
	/**
	for( int i = 0;i < MAX_SIZE; i++){
		
		if( queens[x][i] == 'Q'){
			flag_x = 0;
			break;
		}
	} 
	*/
	
	//y方向上的检查 这两个代码块其实是可以合并成一个代码块的,但是也会带来潜在的隐患 
	for( int i = 0; i< MAX_SIZE; i++){
		
		if(queens[i][y]){
			flag_y = 0;
			break;
		}
	}
	// 由斜率 y - y1 = k(x - x1 ) 变换而来的 y = kx + b 
	
	int b1 = y -x;
	int b2 = x + y;
	
	//斜率为1方向上的检查
	for( int i = 0; i < MAX_SIZE; i++){
		
		//之所以用cotinue是因为有些斜线所穿过的格子并不在8乘8的棋盘里 
		if( i + y - x > 7 || i + y - x < 0)continue;
		
		if( queens[i][ i + y - x]){
			flag_NE = 0;
			break;
		}
	} 
	
	//斜率为-1方向上的检查,这两个虽然也可以进行合并,但是由于判断条件不同,所以,不推荐进行合并 
	for( int i = 0; i < MAX_SIZE; i++){
		
		if( x + y - i > 7 || x + y -i < 0)continue;
		if( queens[i][ x + y - i]){
			flag_NW = 0;
			break;
		}
	}
	
	if( flag_x && flag_y && flag_NE && flag_NW){
		return 1;
	}
	else{
		return 0;
	}
}

/*
*参数列表的cols和rows是x坐标和y坐标,表面目前处理的位置 
*/ 

void print_chessBoard(int queens[][8])
{
	printf("\n**************************************************************\n");
	for(int i = 0; i < MAX_SIZE; i++){
		
		for(int j = 0; j < MAX_SIZE; j++){
			
			printf(" %d ",queens[i][j]);
			
		}
		
		printf("\n"); 
	}
	
	Chess_manual++;
}



void travelsing_chessBoard(int cols,int queens[][8])
{
	
	int rows_note = 1;
	int num = 0;
	
	for( int i = 0; i < MAX_SIZE; i++)
	{
		if( check_queen(cols,i,queens) ){
			
			
			//当一行放置了大于一个的皇后的时候,移除掉前面放置的皇后。因为是按行的顺序放置的,所以说,行的检查是可以避免的 
			if(num > 0){
				
				queens[cols][rows_note] = 0;
				num--;
				
			}

			queens[cols][i] = 'Q';
			rows_note = i;
			num++;
			
		
			//成功放置一个皇后,则进入下一行的皇后检查,如果已经是最后一行就清空本层皇后,然后返回 
			if( cols == MAX_SIZE - 1)
			{
				print_chessBoard(queens);
				
				queens[cols][rows_note] = 0;
				return;
			}
			travelsing_chessBoard( cols + 1,queens);
		}
		
	}
	//如果已经达到了行的末尾,那么本行中的皇后清0,为返回上一层的枝做空间清理
	queens[cols][rows_note] = 0; 
	return;	
	
} 

int main()
{
	int queens[MAX_SIZE][MAX_SIZE] = {0};
	travelsing_chessBoard(0,queens);
	printf("\n共有%d张棋谱",Chess_manual);
 } 

现在再来看这三句注释,其实就是被我们忽视的死路处理:

  1. 当一行放置了大于一个的皇后的时候,移除掉前面放置的皇后。因为是按行的顺序放置的,所以说,行的检查是可以避免的
  2. 成功放置一个皇后,则进入下一行的皇后检查,如果已经是最后一行就清空本层皇后,然后返回
  3. 如果已经达到了行的末尾,那么本行中的皇后清0,为返回上一层的枝做空间清理

程序结果如图:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值