【无私奉献】百万N皇后问题秒级解决源代码

一、引言

百万皇后问题在上世纪90年代比较流行,是一个约束优化求解问题。本人在看《人工智能——一种现代化的方法》一书时看到一句话:百万皇后问题在1分钟内可找到一个可行解。于是很好奇,这是怎么做到的?自己先尝试解决这个问题,可是天资有限,不得解,网上查找,也基本没有,而且要收费。后来查看论文,根据论文写就了这个代码,于是有了今天的这篇文章。

论文:Efficient Local Search with Conflict Minimization: A Case Study of the n-Queens Problem (1994)

作者:Rok Sosic,Jun Gu

DOI: 10.1109/69.317698

二、原理

首先随机生成一个皇后排列,要求:尽可能少的冲突,这可以随机生成一个数(该数范围,当前皇后序号~N),以交换当前列的皇后,如果交换了,可使得当前列没有冲突,则继续交换下一列,作者经过数学推导,这个的循环次数是3.08*皇后个数N。然后实在不行的,就采取随机交换方法。

然后进行最终搜索:经过上面的操作,冲突的皇后个数已经不多,可能有冲突的皇后序号在前面已经记录下来(最后随机交换方法之前的那些皇后是不冲突的)。下面仍然进行随机搜索,先生成一个随机数(该数范围0~N),交换之,如果交换后这两个列的皇后都不存在冲突,则接受该交换,否则继续生成随机数,继续判断。。。,直到满足条件为止或者达到7000次(这个数字也是作者数学推导得出),如果达到7000次(平均72次随机搜索即可找到1个解),重新生成一个皇后排列,即重复上面段落的过程。

三、实现

1.准备工作——数据结构

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <iostream>
using namespace std;
#define TIMES 10  // 运行次数
#define longx long long int
unsigned longx RandSeed = (unsigned)time(NULL);
//皇后数目 
longx N;
//*queen皇后摆放位置,下标为棋盘行,值为棋盘列
//*pdiag棋盘主对角线皇后个数,共2N-1条,每条线上 [行号-列号+N-1]是一样的,可代表下标,从右上到左下标号 
//*cdiag棋盘主对角线皇后个数,共2N-1条,每条线上 [行号+列号]是一样的,可代表下标,从左上到右下标号
//*pdiag1棋盘主对角线,当前皇后左边的皇后个数 
//*cdiag1棋盘副对角线,当前皇后左边的皇后个数 
longx *queen,*pdiag,*cdiag,*pdiag1,*cdiag1;

2.随机函数

//生成一个随机数(实质返回int型大小) 
//rand函数默认最大只能产生32767的数,应放弃使用这个函数
longx Random(longx start=0,longx end=100)
{
	if(end>start)
	  return rand()%(end-start)+start; 
	else
	  return end; 
}
//随机函数,此函数可产生大于32767的数 
unsigned longx BRandom(longx start=0,longx end=100)
{
	unsigned longx x ;
	double i ;
	
	x = 0x7fffffff;
	x += 1;
	RandSeed*= ((unsigned longx)134775813);
	RandSeed += 1;
	RandSeed = RandSeed % x;
	i = ((double)RandSeed) / (double)0x7fffffff;
	if(end>start) return start+(unsigned longx)((end-start) * i);
	else return end;
}

3.交换皇后

思路:(1)三条线皇后数减一 (2)交换 (3)三条线皇后数加一。

其中,flag参数,表示是否处理只处理当前皇后最左边的列

//交换皇后位置 
void swap(longx a,longx b,int flag)
{
	longx t;
	pdiag[queen[a]-a+N-1]--;
	cdiag[queen[a]+a]--;
	pdiag[queen[b]-b+N-1]--;
	cdiag[queen[b]+b]--;
	if(flag==0)  //处理当前皇后a左边的 
	{
	    pdiag1[queen[a]-a+N-1]--;
	    cdiag1[queen[a]+a]--;	
	}
	
	t=queen[a];
	queen[a]=queen[b];
	queen[b]=t;
	
	pdiag[queen[a]-a+N-1]++;
	cdiag[queen[a]+a]++;
	pdiag[queen[b]-b+N-1]++;
	cdiag[queen[b]+b]++;
	if(flag==1) //处理当前皇后a左边的 
    {
	   pdiag1[queen[a]-a+N-1]++;
	   cdiag1[queen[a]+a]++;
	}	
}

4.冲突判断及其他

只判断对角线即可

int totalCollisions(longx k)
{
   return pdiag[queen[k]-k+N-1]>1 || cdiag[queen[k]+k]>1;	
}
//对角线设置为0 
void setZero()
{
	for(longx i=0;i<2*N-1;i++)
	  {
	  	pdiag[i]=0;
	  	cdiag[i]=0;
	  	pdiag1[i]=0;
	  	cdiag1[i]=0;
	  }
}

5.评估函数(调试用)

//冲突评估函数,也是调试用的 
longx heuristic()
  {
  	  //初始化主副对角线 
      setZero(); 
  	  longx h=0;
      longx s=2*N-1;
	  for(longx i=0;i<N;i++)  
	  {
         pdiag[queen[i]-i+N-1]++;
         cdiag[i+queen[i]]++; 
     }
     for(longx i=0;i<s;i++)
	  {
		h+=pdiag[i]*(pdiag[i]-1)/2;
		h+=cdiag[i]*(cdiag[i]-1)/2;
	 }
	 return h;
  }

6.初始化搜索

//初始化皇后排列,冲突评估值尽量小,花的时间少 
longx initSearch()
{
	//初始化主副对角线 
    setZero(); 
   // clock_t	start1, finish1;
	//start1 = clock();	// 计时开始
	longx i,j,m;
	//论文里通过概率分析得出是3.08 
	longx x=N*3.08;
	for(i=0;i<N;i++)
	  {
	  	queen[i]=i;
	  	pdiag[queen[i]-i+N-1]++;
	  	cdiag[queen[i]+i]++;
	   } 
	for(j=0,i=0;i<x && j<N;i++)
	{
		//m=Random(j,N);
		m=BRandom(j,N);
		swap(j,m,1);
		if(pdiag1[queen[j]-j+N-1]>1 || cdiag1[queen[j]+j]>1) 
           swap(j,m,0);
		else j++;
	}
	//print();
	//print1();
	//cout<<endl<<"已排好的皇后数:"<<j<<",达到3.08N时冲突评估值:"<<heuristic()<<endl;
	for(i=j;i<N;i++)
    {  
	    //m=Random(i,N);
	    m=BRandom(i,N);
        swap(i,m,2);   
    }
    //print();
    //finish1 = clock();
	//double totaltime1 =	(double)(finish1 - start1)/CLOCKS_PER_SEC;
	//cout<<"初始化耗时:"<<totaltime1<<endl;
    cout<<endl<<"已排好的皇后数:"<<j<<",随机交换后的冲突评估值:"<<heuristic()<<endl;
    return N-j;
}

7.终止搜索

//最后搜索,一般找到不冲突的位置,据论文说平均是72次随机搜索即可 
void finalSearch(longx k)
{
	longx i,j;
	longx b;
	longx c=0,t=0;
	//,ac=0;
	//longx av=0; 
	for(i=N-k-1;i<N;i++)
	{
		c=0;
		if(totalCollisions(i)) //两条线上可能有冲突 
		  {
		  	//ac++;
		    do
		    {
		    	//j=Random(0,N);
		    	j=BRandom(0,N);
		    	c++;
		    	swap(i,j,2);
		    	//交换后,判断这2个位置是否有冲突 
		    	b=(totalCollisions(i)||totalCollisions(j));
		    	//交换后有冲突,再次交换回去 
		    	if(b) swap(i,j,2); 
				//else av+=c; 
		    	//7000是论文中说的数字
				//实际上,皇后到8万后,似乎就很难找到解了 
				//上面的问题是因为随机数生成函数,已解决 
				if(c==7000)  
				{
				  cout<<endl<<endl<<"现在重新初始化皇后排列:"<<endl;
				  k=initSearch(); //重新生成皇后的初始排列 
				  i=N-k-1;
				  //cout<<"重新开始前平均搜索次数:"<<(av+c)/ac<<endl;
				  //av=0;ac=0;
				  break;		
				}	    	
			}while(b);
		}
			//if(c>=7000) cout<<c<<endl;	
	}
	 //cout<<"平均搜索次数:"<<av/ac<<endl;
}
//输出皇后位置到文件,调试用的 
void outputQueen(const char *file)
{
	FILE *out1=fopen(file,"w");
	for (longx i=0;i<N;i++)	
	{
	   fprintf(out1, "%lld\t", queen[i]);
	   if((i+1)%10==0) fprintf(out1, "\n");
    }	
	fclose(out1);
}

8.测试

int main(int argc,char *argv[])
{
    cout<<"请输入皇后数:"; 
	cin>>N;
	//皇后位置,下标i表示列,值为行 
	queen=(longx *)malloc(sizeof(longx)*N);
    //全局对角线 
    pdiag=(longx *)malloc(sizeof(longx)*(2*N-1));
    cdiag=(longx *)malloc(sizeof(longx)*(2*N-1));
     //左边对角线 
    pdiag1=(longx *)malloc(sizeof(longx)*(2*N-1));
    cdiag1=(longx *)malloc(sizeof(longx)*(2*N-1));
    
    double tt=0.0;
    for(int i=0;i<TIMES;i++)
	 {	
	   srand((unsigned)time(NULL)+rand());
	   cout<<endl<<endl<<"第"<<i+1<<"次测试,现在开始初始化:"<<endl; 
	   clock_t	start, finish;
	   start = clock();	// 计时开始
       finalSearch(initSearch());
       finish = clock();
	   double totaltime =	(double)(finish - start)/CLOCKS_PER_SEC;
	   tt+=totaltime;
	   cout<<endl<<"最终冲突评估值: "<<heuristic()<<endl;
	   cout<<endl<<"耗费时间:"<<totaltime<<endl;
      }
      
      cout<<endl<<endl<<"平均时间"<<tt/10.0<<endl; 
	 //outputQueen("queen.txt");
	//释放内存 
	 free(queen);
	 free(pdiag);free(cdiag);
	 free(pdiag1);free(cdiag1);
	return 0;
 } 

如果你想完整的代码,可以下载https://download.csdn.net/download/obestboy/11242376

当然上面链接的代码与本文内嵌的代码完全一致!!!

  • 6
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值