八皇后问题

八皇后问题是一个关于回溯算法的典型例题,所谓回溯,就是按选优条件向前搜素,以达到目标。但当搜索到某一步时,发现原先选择并不是最优或者达不到目标,则退一步重新选择。具体问题描述如下:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线(对角线)上,问有多少种摆法。

下面先以四皇后问题来说明回溯法的基本思想:先在4X4的方格的第一行第一列的位置上放置一个皇后,如图1所示,则灰色阴影部分都是它的攻击范围,现在我们从第二行开始放置第二个皇后,先放置在第二行第一列,显然此位置在第一个皇后的攻击范围之内,所以将第二个皇后的位置右挪一位,它仍处于第一个皇后的攻击范围之内,继续右移,直到第二个皇后的位置不在第一个皇后的攻击范围之内,此时由两个皇后构成的攻击范围如图2所示。接下来开始再第三行放置皇后,但是此时整个第三行都在前两个皇后的攻击范围内,导致这种放置方法不能继续下去,这时候我们退回一步,重新放置第二个皇后的位置,如图3所示。这时候,再从第三行第一列的位置开始放置皇后,直到前三行的皇后都互不攻击,就开始在下一行放置皇后,如果有冲突,就返回一步,重新放置上一行的皇后位置,如果上一行的皇后位置已经是最右边,则再退回一行,将上上一行的皇后位置向右挪动一位,但最后一行的皇后摆放以后,没有互相攻击,说明所有皇后已经摆放好,此时将计数器加1。然后将最后一行的皇后位置再右挪一位,寻找下一种互不攻击的摆放方法,经过这样的一个过程,就可以寻找出所有的摆放方法。

                                 

该算法的重点步骤如下:

     1, 从第一行开始放置皇后,当第i行放好以后,在放置第i+1行时,从左边第一列开始放置,如果有冲突,皇后往右挪一位,同时判断是否越界,写一个函数用来判断当前位置是否可以放置皇后;

     2,如果最后一行位置的皇后也已经放置好了,说明一种成功摆放方法已经出现,计数器+1,;

     3,当出现一种摆放方法以后,还要试探其它方法,可以把最后最后一行的皇后“拿走”,然后再试探其它未试探过的棋盘格;

     4,如果该位置不能放置皇后,要把它置0,如果该位置可以放置皇后就置1,当一种摆放方法成功以后,将皇后“拿走”的操作也是置0;

    下面讨论具体的程序、

定义一个NxN的二维数组来表示棋盘,chess[i][j]表示第i行第j列处的元素,变量nCount用来统计最终的摆放次数,棋盘的行和列的表示如图4,

#include<iostream>
using namespace std;
#define N 8             //可以根据N来修改棋盘的格数   
int nCount = 0;         //设置一个计数器   
int chess[N][N] = { 0 }; //用于存放棋盘的二维数组  

用来判断棋盘当前位置chess[i][j]是否可以放置皇后的函数

//判断在当前位置放入皇后以后是否会和以前的皇后相互攻击    
//返回1 表示攻击范围互相重叠 
//返回0 表示攻击范围不会重叠,可以在此处放置皇后
int check(int i, int j)//i = 7,j = 4   
{
	int k = 0;
	for (k = 0; k <= i; k++)                             //判断纵向是否有皇后
	{
		if (chess[k][j] == 1) 
			return 1;
		if (j + k < N&&chess[i - k][j + k] == 1)     //  判断右侧对角线是否有皇后
			return 1;
		if (j - k >= 0 && chess[i - k][j - k] == 1)  //判断左侧对角线是否有皇后
			return 1;
	}
	return 0;
}

递归的主要算法

 
  1. void queen(int i, int j)
  2. {
  3. if (j == N)      //判断列数是否越界,如果当前行的八个位置都不能放置皇后,
  4. return;      //当前的递归结束,返回到上一层
  5.  
  6. if (check(i, j) == 0)       //判断当前位置是否能放皇后
  7. {
  8. chess[i][j] = 1;      //如果可以放置,则置1
  9. if (i == N - 1)      //第七行已经放置好,已经成功找到一种摆放方法,将计数器加1,并将棋盘打印
  10. {
  11. nCount++;
  12. print();      //打印
  13. }
  14.  
  15. else //没有到第七行,则从第i+1行第一列的位置继续摆放
  16. queen(i + 1, 0);
  17. }
  18. chess[i][j] = 0;
  19. queen(i,j + 1);             //第j列不能放置皇后,向右挪一位
  20. }

queen函数是解决八皇后问题的核心,程序以i=0,j=0的参数进入。在程序18行中,将chess[i][j]置0的原因有两个:

     1,在将第i行的皇后摆放好以后,在放置第i+1行的皇后时,发现第i+1行已经不能再放置皇后,这时递归需要退回一步,将上一行已经放置好的皇后“拿走”,即置0,然后向右移动一位,继续判断下一个位置的合法性,

     2,当第七行的皇后已经摆放好,说明已经找到一种成功的方法,此时将计时器+1,然后将最后一行的皇后“拿走”,即置0,然后继续右移,继续寻找下一种摆放方法。

打印函数

void print()//打印函数   
{
	int i = 0;
	int j = 0;
	cout<<"*****************************************"<<endl;
	for (i = 0; i < N; i++)
	{
		for (j = 0; j < N; j++)
			cout << chess[i][j] << " ";
		cout << endl;
	}
	cout << "*****************************************" << endl;
}

完整代码如下:

#include <iostream>
using namespace std;
#define N 8             //可以根据N来修改棋盘的格数   
int nCount = 0;         //设置一个计数器   
int chess[N][N] = { 0 }; //用于存放棋盘的二维数组   

void print()//打印函数   
{
	int i = 0;
	int j = 0;
	cout<<"*****************************************"<<endl;
	for (i = 0; i < N; i++)
	{
		for (j = 0; j < N; j++)
			cout << chess[i][j] << " ";
		cout << endl;
	}
	cout << "*****************************************" << endl;
}

//判断在当前位置放入皇后以后是否会和以前的皇后相互攻击    
//返回1 表示攻击范围互相重叠 
//返回0 表示攻击范围不会重叠,可以在此处放置皇后
int check(int i, int j)  
{
	int k = 0;
	for (k = 0; k <= i; k++)                             //判断纵向是否有皇后
	{
		if (chess[k][j] == 1) 
			return 1;
		if (j + k < N&&chess[i - k][j + k] == 1)     //  判断右侧对角线是否有皇后
			return 1;
		if (j - k >= 0 && chess[i - k][j - k] == 1)  //判断左侧对角线是否有皇后
			return 1;
	}
	return 0;
}

//递归的主要算法   
void queen(int i, int j)
{
	if (j == N)                               //判断列数是否越界,如果当前行的八个位置都不能放置皇后,
		return;                           //当前的递归结束,返回到上一层
	if (check(i, j) == 0)                     //判断当前位置是否能放皇后
	{
		chess[i][j] = 1;                   //如果可以放置,则置1
		if (i == N - 1)                    //第七行已经放置好,已经成功找到一种摆放方法,将计数器加1,并将棋盘打印
		{
			nCount++;      
			print();                    //打印 
		}		
		else                               //没有到七行,则从第i+1行第一列的位置继续摆放
			queen(i + 1, 0);
	}
	chess[i][j] = 0; 	
	queen(i,j + 1);                              //第j列不能放置皇后,向右挪一位      

}
int main(void)
{
	queen(0, 0);
	cout << "nCount=" << nCount << endl;
	return 0;
}

运行结果如下

程序运行结果为92,说明八皇后问题一共有92种摆放方法,这与八皇后问题的实际解是一样的。

 

在上面的讨论中,我们是利用一个二维数组chess[N][N]来表示一个NxN的棋盘的,chess[i][j]表示第i行第j列的皇后状态。事实上,我们要在每一行都放置一个皇后,那么八皇后问题的解可以表示为{(1,X1)、(2,X2)、(3,X3)、(4,X4)、(5,X5)、(6,X6)、(7,X7)、(8,X8)},其中(1,X1)表示第一个皇后的位置在第一行第X1列的位置上(Xi的取值范围是1到8),由于行号是固定的,因此可以简记为(X1,X2,X3,X4,X5,X6,X7,X8)。所以,我们可以使用一个一维数组chess[N]来表示八皇后的状态,chess[i] 中下标i表示皇后的行数,值表示皇后所处的列数,(i的取值范围是1到8,chess[i]的取值也是1到8),比如chess[3]=5就表示,第三个皇后处于第三行第五列的位置上,下面我们分析当用一个一维数组时怎么解决皇后之间的冲突问题,如下图所示,

(1),两个皇后不在同一列: chess[i] != chess[j],表示位于第i行和第j行的皇后不在同一列;

(2),两个皇后不在同一条主对角线上:如上面左图,同一颜色的区域代表皇后在同一条对角线上,同一颜色的皇  后位置满足 chess[i] - chess[j] = i-j;要想皇后位置不冲突,须满足  chess[i] - chess[j] != i-j;

(3),两个皇后不在同一条次对角线上:如上面右图,同一颜色的皇后位置满足 chess[i] - chess[j] = j-i;

综合上面三种情况,当用一维数组记录皇后位置时,皇后位置不冲突的判断条件为:

                   abs( chess[i] - chess[j] ) != abs(i-j) 或者 chess[i] != chess[j]。检查函数如下:

//判断在当前位置放入皇后以后是否会和以前的皇后相互攻击    
//返回0 表示攻击范围互相重叠 
//返回1 表示攻击范围不会重叠,可以在此处放置皇后
bool Check(int n)
{
	for (int i = 0; i < n;i++)
	{
		if (abs(chess[n] - chess[i]) == abs(n - i) || (chess[n] == chess[i]))
			//绝对值括号里面的一项用来排除两条对角线冲突的情况,后面一项排除同一列有冲突的情况
			return false;
	}
	return true;
}

 

接下来,开始逐行放置皇后,先从第1行开始,也就是在chess[0]放置皇后,chess[0]里面存储的是皇后在第一行的列数,其取值范围为0到7,接下来开始在第2行放置皇后,也就是在chess[1]的位置上放置0到7,如果有冲突,换数字即可,如果放到7以后还有冲突,退出当前的递归层,返回上一层,将上一行的皇后位置右挪一位(即chess[0]里的数字加1);如果不冲突,进入下一层,直到第8行(即chess[7])放置以后,还没有发生冲突,则找到一种组合。递归函数如下:

 

void Queen(int n)
{
	for (int i = 0; i < N;i++)
	{
		chess[n] = i;       //从第n行的第0列开始放置皇后
		if (Check(n))       //检查放在第n行第i的皇后是否有冲突
		{
			//若没有冲突
			if (n==N-1)    //第七行新放置的皇后不冲突,说明八个皇后全部放置完毕
			{
				nCount++;  //计数器加1
				Show();    //打印放置成功的棋盘
			}
			else
  			  Queen(n + 1);   //若还没有到最后一层,递归调用,摆放下一层
		}
	}
}

打印函数如下:

void Show()
{
	for (int i = 0; i < N;i++)
	{
		cout << chess[i] << " ";
	}
	cout << endl;
}

完整代码如下:

#include <iostream>
#include<math.h>
using namespace std;
#define N 8
int chess[N] = { 0 };
//用一个一维的数组来存储皇后的位置,
// chess[i] 中下标i表示皇后的行数,值表示皇后所处的列数
//i的取值范围是0到7,chess[i]的取值也是0到7

int nCount;

//判断在当前位置放入皇后以后是否会和以前的皇后相互攻击    
//返回0 表示攻击范围互相重叠 
//返回1 表示攻击范围不会重叠,可以在此处放置皇后
bool Check(int n)
{
	for (int i = 0; i < n;i++)
	{
		if (abs(chess[n] - chess[i]) == abs(n - i) || (chess[n] == chess[i]))
			//绝对值括号里面的一项用来排除两条对角线冲突的情况,后面一项排除同一列有冲突的情况
			return false;
	}
	return true;
}

void Show()
{
	for (int i = 0; i < N;i++)
	{
		cout << chess[i] << " ";
	}
	cout << endl;
}
void Queen(int n)
{
	for (int i = 0; i < N;i++)
	{
		chess[n] = i;       //从第n行的第0列开始放置皇后
		if (Check(n))    //检查放在第n行第i的皇后是否有冲突
		{
			//若没有冲突
			if (n==N-1)    //第七行新放置的皇后不冲突,说明八个皇后全部放置完毕
			{
				nCount++;  //计数器加1
				Show();    //打印放置成功的棋盘
			}
			else
  			  Queen(n + 1);   //若还没有到最后一层,递归调用,摆放下一层
		}
	}
}
int  main()
{
	Queen(0);  //先从第0行开始放置
	cout << nCount << endl;
	return 0;
}

运行结果如上图,输出的每一行表示chess数组的各个元素,分别代表第一行到第八行的皇后所在列数,程序最后输出八皇后问题的所有组合解。通过改变宏定义里的N,可以得到任意皇后数目的解。

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值