n皇后问题(分支限界法)

一、问题描述

*问题描述:在n*n格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n皇后问题等价于在n*n的棋盘上放置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线上。

*算法设计:设计一个解n皇后问题的队列式分支限界法,计算在n*n个方格上放置彼此不受攻击的n个皇后的一个放置方案。

*数据输入:由文件input.txt给出输入数据。第一行有1个正整数n

*结果输出:将计算的彼此不受攻击的n个皇后的一个放置方案输出到文件output.txt。文件的第一行是n个皇后的放置方案。

输入文件:

input.txt

5

output.txt

1 3 5 2 4

二、问题分析

1.题目分析:

对于每一个放置点而言,它需要考虑四个方向上是否已经存在皇后。分别是行列,45度斜线和135度斜线。

扫描二维码关注公众号,回复: 1994451 查看本文章

行:每一行只放一个皇后,直到我们把最后一个皇后放到最后一行的合适位置,则算法结束。

列:列相同的约束条件,只需判断j是否相等即可。

45度斜线和135度斜线:约束条件——当前棋子和已放置好的棋子不能存在行数差的绝对值等于列数差的绝对值的情况,若存在则说明两个棋子在同一条斜线上

2.算法选择:

用分支限界法来解决n皇后问题。对于该问题它满足两种树结构。这里由于每一个合适的放置点出现最优解的概率是相等的,因此不需要使用优先队列。编译器提供了一个已经封装好的队列queue

A.子集树。我们把行约束条件和其它约束条件放在一起,即认为每一行,棋子都有n个位置可以放置。于是它的空间结构如下:


(假设n=3,这个图只画出了两层

B.排列树。我们把行约束条件单独拿出来,也就是我们认为总共有八个皇后,这八个皇后必须都要放上去,但是放的位置不同,显然这是一个排列树。于是它的空间结构如下

 

这里由于我们还存在着其他的约束条件,并且行约束条件和这些条件完全可以放在一起,于是我就选择了相对容易理解的子集树作为解空间结构。

 

3.算法设计

A.数据存储

对于每一个棋子节点,它需要储存两个信息,一个是该棋子所处的层数,另一个就是这个棋子它是由哪些棋子扩展而来的,也就是它的路径。

//定义一个节点类
struct Node{
	int number;//棋子所处的层数,也就是棋盘上的i
	vector<int>x;//保存该棋子以及在它之前的棋子所在的列数j。也就是它的所有父辈节点的信息 
}; 

比如图中的节点m,它储存的信息就是number=3x[1]=1;x[2]=2;x[3]=3。指明了前面棋子的列信息。

B.定义一个Queen的类,封装一些相关的信息,比如皇后个数和最优解的数组

class Queen{
	friend int nQueen(int);
	public:
		bool Place(Node q,int n);
		void Research();
		int n;//皇后个数
		int *bestx;//最优解
}; 

4.细节处理

A.当前所处层数的判断:每一层的结尾都压入一个number= -1的节点,每一次在取出队列中的节点时进行判断如果该节点的nember= -1,那么当前层数t1,并且再压入一个nember= -1的节点。然后重新往队列取出下一个节点

B.算法终止条件:如果当前取出的节点number==n,表明已经处理到最后一层了,并且最后一层满足约束条件的棋子位置已经都存在队列中了。这时我们把当前节点的数组x赋值给bestx,然后结束算法。

三、详细设计(从算法到程序)

#include<iostream>
#include<queue>
#include<cmath>
#include<vector>
#include<fstream>
#include<sstream>
using namespace std;

//定义一个节点类
struct Node{
	int number;
	vector<int>x;//保存当前解 
}; 

//定义一个Queen的类 
class Queen{
	friend int nQueen(int);
	public:
		bool Place(Node q,int n);
		void Research();
		int n;//皇后个数
		int *bestx;//最优解
}; 

//判断是否能够放置的函数 
bool Queen::Place(Node q,int n)
{
	for(int j=1;j<n;j++)
	  if((abs(n-j)==abs(q.x[j]-q.x[n]))||(q.x[j]==q.x[n])) return false;
	return true;
}

void Queen::Research()
{
	queue<Node>Q;//活节点队列
	Node sign;
	sign.number=-1;
	Q.push(sign);//同层节点尾部标志
	int t=1;//当前节点所处的层
	Node Ew;//当前扩展节点 
	Ew.number=0; 
	//搜索子集空间树
	while(1){	
	   //检查所有的孩子节点 
	   for(int k=1;k<=n;k++){
		//把当前扩展节点的值赋给下一个节点 
		Node q;
	        q.number=t; 
	        q.x.push_back(0);//第一个位置为0 
    	        for(int i=1;i<t;i++) q.x.push_back(Ew.x[i]);
    	        q.x.push_back(k);
		if(Place(q,t))
    	    	Q.push(q);
    	}	 
		//取下一扩展节点,取出,赋值给Ew 
		Ew=Q.front();
		Q.pop();
		if(Ew.number==-1){
		    //同层节点尾部标记
		    t++;//进入下一层 
		    Q.push(sign);//增加标记
		    //继续往下去下一个节点 
		    Ew=Q.front();
		    Q.pop();
		}		
		if(Ew.number==n){//找到最后一层的节点 
		   for(int i=0;i<=n;i++) bestx[i]=Ew.x[i];
		   break;
		} 
	}
}

int nQueen(int n,ofstream &outfile)
{
	Queen X;
	X.n=n;	
	X.bestx=new int[n+1];
	for(int i=0;i<=n;i++) X.bestx[i]=0;
	X.Research();
	for(int i=1;i<=n;i++){
		outfile<<X.bestx[i]<<" ";
	}
}

int main(){
	int N;
	ifstream cinfile;
	cinfile.open("input.txt",ios::in);
	cinfile>>N;
	cinfile.close();
	
	ofstream outfile;
	outfile.open("output.txt",ios::out);
	
	nQueen(N,outfile);
	
	outfile.close();
	return 0;
}


四、运行结果

 

 

 

五、分析与总结

(1).对于一个动态数组*a,和一个定长数组b[n]。直接令a[i]=b[i]虽然编译可以通过,但是会出现程序终止的情况。这里我改用了vector来保存定长数组的值

 

 

猜你喜欢

转载自blog.csdn.net/alexwym/article/details/80872956