N皇后独立解的寻求

本文提出了一种求解N皇后问题独立解的算法,通过对比每个解的旋转变换,确保输出的解集不包含重复的解。文章详细介绍了算法的实现思路和代码,旨在减少空间复杂度并提高效率。

这个问题其实百度能百度到,但是那些代码我实在是,读不懂(没注释+长)。而且用了很多奇奇怪怪的数组,我个人认为并不需要用这么多的空间,遂有此文。

首先开头处希望某些实验报告里有这一题的同学不要抄我作业

n皇后问题是回溯法的一个经典例子,但是以8皇后为例,92个解中并非所有都是独立解,而是可能由别的解通过旋转变换得来的(8种旋转,8种变换)。

如何去求独立解呢?我其实参考了这篇论文

N皇后问题独立解算法研究和仿真实现

(居然还是佛科院写的)

但是后半部分的具体操作我也没太看懂。

借鉴了前人的思路之后,我的思路是,添加一个solutions数组,只存已判定为独立解的解,然后对每个求出的解进行旋转变换,再与独立解进行比较,若其旋转变换全与独立解不同,则它也是独立解,输出,否则不断覆盖当前序号的solutions,只有当求出独立解了,才能占下一个坑

具体代码如下:

#include <math.h>
#include <iostream>
#include <vector>

/*思路!!!!
对每个可行解,求出所有对称解,将所有对称解与前面的已知为独立解的解比较
如果相等的,就不是独立解,解数不加1,solutions数组行下标不变 
即下一次求出的可行解会覆盖这个位置的解(如果非独立的话)
且只有constructing函数返回值为1时才打印解,所以打印的时独立解
而solutions数组用于记录所有已知独立解,其他解的所有对称解将与solutions里存的独立解进行比较 

constructing也相当于是一个约束函数,有点深度优先的感觉(搜索完一颗子树,在找下一棵)
但并不是边找边生成结点,而是有了一颗完整的子树之后对整颗树进行判断,不满足条件则舍弃子树
从而减小空间复杂度 
*/

using namespace std;


class nqueen{
	public:
		//临时存旋转变换后的可行解 
		vector<int> finding; 
		//最多12个解 ,行(hang)为解的个数,方便循环的时候理逻辑 
                //存独立解
		int solutions[2000][12];
		void nqueens(int n,int *x);
	private:
		bool place(int k,int i,int*x);
		void nqueens_out(int k,int n,int *x);
		bool constructing(int n,int *x,int temp);
		bool test(int num,int n);
};

//约束函数 
//输入参数,(到目前为止的总)行号k(已经放了的皇后),列号i,数组首址x
bool nqueen::place(int k,int i,int*x){
	//判断两个皇后是否在同一列或同一斜线 
	for(int j=0;j<k;j++)
  	if((x[j]==i)||(abs(x[j]-i)==abs(j-k))) 
  	//在同一列或同一斜线,返回false 
		return false;
  	return true;
}

//输入参数,皇后个数n,数组首址x 
void nqueen::nqueens(int n,int *x)
{
	//初始化数组长度 
    finding.resize(n);
    
	//依次从0,1,。。。。放置 
    nqueens_out(0,n,x); 
    return ;
}
//输入参数,(到目前为止的总)行号k(已经放了的皇后),皇后个数n,数组首址x
void nqueen::nqueens_out(int k,int n,int *x){
	int static number=0;
	for(int i=0;i<n;i++){
		//T在这儿不用判断,n皇后问题没有像排序问题那样能基于前面的状态判定后面一定没有解  
		//必须满足第一个皇后在中线(包括中线)以左 
    	if(place(k,i,x)){ 
    		x[k]=i;
    		if(k==n-1){
			//放满则输出存解
			//此处用的是递归调用,但number的值并不会传参,(static,并不会减)故导致无法将赋值过程与x[k]同步
			//否则在求解时,子问题的解会部分覆盖solutions的某几位(即便x【k】是全赋值了的
			//同时导致constructing函数返回值为真,能输出,且number++,出现非预期的情况 
			//若想避免下述循环,进一步优化,可传参number(我觉得),但我没有写,也没有调 
    			for(i=0;i<n;i++)
    				//第 number个解的第i个元素放在第x[i]列 	
					solutions[number][i]=x[i]; 

				if(constructing(n,x,number)){
					//number+1为次序 
					cout<<"No."<<number+1<<":"<<endl; 
					for(i=0;i<n;i++)
						cout<<"x["<<i<<"]="<<x[i]<<" ";
					number++;
					cout<<endl;
				}
			}
			else 
				nqueens_out(k+1,n,x);
		}
	} 
}

//皇后个数n,数组x,已有的解的个数temp(0开始算) 
bool nqueen::constructing(int n,int *x,int temp){
	int j=0,k=0;
    //把右旋90°保存到findings中
    for(j=0;j<n;j++)
            finding[solutions[temp][j]]=n-1-j;
     for(k=0;k<temp;k++)    
		if (test(k,n))
        	return false;

    //把右旋180°保存到findings中
    for(j=0;j<n;j++)
           finding[n-1-j] =n-1-solutions[temp][j];
    for(k=0;k<temp;k++)    
		if (test(k,n))
        	return false;

	//把右旋270°保存到findings中
	for(j=0;j<n;j++)
		finding[n-1-solutions[temp][j]]=j;
	 for(k=0;k<temp;k++)    
		if (test(k,n))
        	return false;
        
    //把上下对称保存在finding中    
	for(j=0;j<n;j++)
		finding[n-1-j]=solutions[temp][j];
	for(k=0;k<temp;k++)    
		if (test(k,n))
    		return false;

    //把左右对称保存在z中
 	for(j=0;j<n;j++)
		finding[j]=n-1-solutions[temp][j];
    for(k=0;k<temp;k++)    
		if (test(k,n))
        	return false;

    //主对角线的情形
    for(j=0;j<n;j++)
		finding[solutions[temp][j]] =j;
    for(k=0;k<temp;k++)    
		if (test(k,n))
        	return false;

    //次对角线的情形
    for(j=0;j<n;j++)
		finding[n-1-solutions[temp][j]] =n-1-j;
    for(k=0;k<temp;k++)    
		if (test(k,n))
        	return false;
        
    return true;
}

//输入参数,解的次序num,皇后个数n 
//如果旋转之后对应的值跟之前的第row个解相等  
bool nqueen::test(int num,int n) {
	//对第row个解逐行比较(对于一个棋盘而言,列的次序相当于行) 
    for (int i=0;i<n;i++)
    {
    	//只要出现不相等的元素即return false 
        if (finding[i]!= solutions[num][i])
            return false;
    }
    //没有不相等的,即相同return true,此时该解为重复解 
    return true;
}

int main(){
	int num;
	nqueen solution;
	printf("input number of queens:\n");
	scanf("%d",&num);
	int x[num-1]={0};
	solution.nqueens(num,x);
	return 0;
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值