POJ3239 Solution to the n Queens Puzzle

POJ3239 http://poj.org/problem?id=3239

Solution to the  n Queens Puzzle
Time Limit: 1000MS Memory Limit: 131072K
Total Submissions: 3445 Accepted: 1267 Special Judge

Description

The eight queens puzzle is the problem of putting eight chess queens on an 8 × 8 chessboard such that none of them is able to capture any other. The puzzle has been generalized to arbitrary n × n boards. Given n, you are to find a solution to the n queens puzzle.

Input

The input contains multiple test cases. Each test case consists of a single integer n between 8 and 300 (inclusive). A zero indicates the end of input.

Output

For each test case, output your solution on one line. The solution is a permutation of {1, 2, …, n}. The number in the ith place means the ith-column queen in placed in the row with that number.

Sample Input

8
0

Sample Output

5 3 1 6 8 2 4 7

Source


这道题是经典的八皇后问题的扩展——n皇后问题,解决n皇后问题的方法很多,但是这一题的特别之处在于n的规模可以达到300,尽管只要求一个解即可,但是300的规模也使得常见的搜索算法不可避免地超时。所以针对这一问题,目前有两种主流的解法:1.构造公式;2.随机算法。目前我的实现是基于随机算法,而这个算法的思路主要来源于如下博文:http://hi.baidu.com/zhuangxie1013/item/39f94824082c29869d63d1f7, 我的实现过程也是基本参照了该文的描述。

解题思路

对于这个题的每一个解可以由一个一维数组表示,数组的索引值表示列数,而数组中每一项的值,表示在该列上,皇后所处的行数。因此随机算法的思路就是随机生成一组数字,然后检测这组数字是否满足要求,如果不符合,记录有多少个冲突,然后交换数组中的两列,然后检测冲突数,如果冲突数没有减少,那么取消该次交换,然后将两列中的一列与数组中其他某列交换。如果交换后冲突数减少了,那么保留该次交换,如果冲突数已经减少到0,那么找到一个解,输出,如果还不为零,那么在该次交换的基础上继续进行交换,直到冲突数减到0。

下面就根据我的具体的代码实现,来详细阐述,顺便提一下poj提交系统对/**/注释的支持好像有问题,我提交这一题时,点击submit后,页面卡死,换用其他浏览器后显示“访问禁止”,原本以为是服务器维护之类的,使得这一题没办法提交,结果看提交记录,发现其他人在近期有提交该题的记录,所以觉得很奇怪,写了个hello world在代码框,发现可以提交了,这就费解了,这明显不是服务器的问题了。一点一点地添代码来测试,在牺牲了近10次提交数之后,终于发现时注释的问题。但让我现在也很费解的是:/**/作为C的注释类型,没理由不被C++编译器支持啊。

#include <iostream>     // std::cout
#include <algorithm>    // std::random_shuffle

using namespace std;

int num;
int board[301];
int diagRDtoLU[301*2-1];                  //record the number of queens in each diagonal(right down to left up)
int diagLDtoRU[301*2-1];                  //record the number of queens in each diagonal(left down to right up) 
int NumOfConflicts;
num用于记录n皇后问题的维数, board数组用于存储n个皇后的位置,因为最高维数可能是300,所以使用301为了安全。diagRDtoLU和diagLDtoRU分别用于记录从右下到左上,和从左下到右上各条对角线上的冲突数,不难发现,对于n维的数组,它所对应的对角线数维2n-1,如下图:10*10,19条对角线。

                                                          
NumofConflicts用于记录总的冲突个数。

void ComputeConflicts()
 {
	 for(int i=0;i<num;i++)
	 {
		 int r = i;
		 int c=board[r];
		 diagRDtoLU[c+r+1]++;              // the queen at (r,c) is on the (c+r+1) diagnoal(RD to LU)  (r,c begin from 0)
		 diagLDtoRU[num-c+r]++;          // the queen at (r,c) is on the (num-c+r) diagnoal(LD to RU) (r,c begin from 0)	 
	 }

	 for(int j=0;j<num*2-1;j++)
	 {
		 if(diagRDtoLU[j]>1)
			 NumOfConflicts += diagRDtoLU[j]-1;
		 if(diagLDtoRU[j]>1)
			 NumOfConflicts += diagLDtoRU[j]-1;
	 }
 }
在这题中我将数组坐标看做行数,数组中每一个的值看做列数,这与题目叙述相反,但对于这题来说,不会对解产生影响,也就是说行和列在这题中是等效的。不难总结出如下规律对于右下到左上的对角线,坐标为(r,c)的网格对应于第c+r+1条对角线(从1开始算),同理,对于左下到右上的对角线,坐标为(r,c)的网格对应于第num-c+r条对角线,num为问题的维数。从代码可以看出,首先遍历整个数组,找出在从右下到左上和从左下到右上的所有对角线上的皇后数,当每条对角线上的皇后数多于1时,就发生了冲突,所以总的冲突数NumOfConflicts加1,遍历完所有的从右下到左上和从左下到右上的对角线后,得到当前总的冲突数。

int Delete(int r, int c, int NoC)      //NoC means the number of conflicts
 {
	 if(--diagRDtoLU[c+r+1])
	 {
		 NoC--;
	 }

	 if(--diagLDtoRU[num-c+r])
	 {
		 NoC--;
	 }
	 return NoC;
 }

int Insert(int r, int c, int NoC)
{
	if(++diagRDtoLU[c+r+1]>1)
	{
		NoC++;
	}

	if(++diagLDtoRU[num-c+r]>1)
	{
		NoC++;
	}
	return NoC;
}

void swap(int i, int j)
{
	int temp = board[i];
	board[i] = board[j];
	board[j] = temp;
}
Delete和Insert两个函数用于计算在交换数组中两项后的冲突数,这个交换过程可以分为两部分,第一部分将要交换的数组的元素删掉,第二部分在要交换到的位置插入这个单元。Delete和Insert这两个函数并不真正的删除或插入数组中的元素,它们只是“模拟”删除或插入后的情况来计算冲突数,如果冲突数变少,那么才使用swap函数,真正的交换两个数组元素。Delete和Insert的if判断条件比较巧妙,例如:Delete中,先自减1,如果大于等于1,那么自减之前大于等于2,也就是说有冲突存在,那么删除该元素后,总的冲突数就自减1。同理,对于Insert,先自加,如果大于1,那么自加之前大于0,也就是说之前该对角线上就有皇后存在,那么插入新的数组元素后,在该对角线上必然产生冲突,所以冲突数自加1。swap函数作用很简单,在确定交换后,冲突数会减小的情况下,交换两个数组元素。

下面就通过main函数来看一下,整个程序的流程:

int main()
{
	int NumOfSwap;

	while(scanf("%d", &num))
	{
		if(num==0)
			break;
		
		for(int i=0;i<num;i++)
			board[i]=i;                         //fill out the array, then we can use random_shuffle

		while(1)
		{	
			random_shuffle(&board[0],&board[num]);  //shuffle from board[0]~board[num-1]
			 
			NumOfConflicts=0;
			memset(diagRDtoLU,0,sizeof(int)*601);
			memset(diagLDtoRU,0,sizeof(int)*601);
			ComputeConflicts();
			NumOfSwap=1;    //if NumOfSwap==0, even though random_shuffle create an array with 0 conflicts, the program is still in the loop.
			while(NumOfConflicts)
			{
				NumOfSwap = 0;
				for(int i=0;i<num-1;i++)
				{
					for(int j=i+1;j<num;j++)
					{
						int NoC;  //NoC:number of conflicts
						NoC=Delete(i,board[i],NumOfConflicts);
						NoC=Delete(j,board[j],NoC);

						NoC=Insert(i,board[j],NoC);
						NoC=Insert(j,board[i],NoC);

						if(NoC<NumOfConflicts)
						{
							swap(i,j);
							NumOfConflicts=NoC;
							NumOfSwap++;
						}
						else
						{
							NoC=Delete(i,board[j],NoC);
							NoC=Delete(j,board[i],NoC);

							NoC=Insert(i,board[i],NoC);
							NoC=Insert(j,board[j],NoC);
						}
					}
				}
				if(NumOfSwap==0)
					break;
			}
			if(NumOfSwap!=0)
				break;
		}

		 for(int i=0;i<num;i++)
			 cout<<board[i]<<" ";
	}
	return 0;
}
首先读入问题的维数,当维数为0时,程序退出。要构造一个随机数组,我使用random_shuffle()这个库函数,需要引入预编译头#Include<algorithm>,第一个参数用要随机排列的数组的起始位置,第二个参数指出结束位置,但要注意的是这个位置实际上是随机排列的最后一位的后一位,例如两个参数为&board[o]和&board[num],所以排列的是board[0]到board[num-1]这n个元素。在使用random_shuffle之前,我们必须先填充这个数组,我们就顺序填充0到num-1这num个数,这样做的好处就是,不管random_shuffle产生怎样的结果,这个结果在行和列上都不会有冲突,所以我们只用检测两种对角线上的冲突数就可以了。我先将要使用的几个变量初始化,然后使用ComputerConflicts计算当前的冲突数,如果冲突数不为0,那么就需要进行交换。进入到循环体中,现将交换次数初始化为0,因为当前还没有进行交换,然后从第一个元素开始,让它和相邻的元素开始交换,如果交换后冲突数小于原来的冲突数,那么,就认为当前的数组比之前的数组更优,所以就执行swap真正地交换两个元素,交换次数NumOfSwap加1。如果交换后的冲突数大于等于原来的冲突数,那么就废除这次交换。然后进入下一次循环。如果整个双层的for循环执行完之后,NumOfSwap==0,那么就意味着当前数组已没有进一步优化的可能,所以跳出while循环,重新使用random_shuffle()函数得到一个随机的排列,然后在进入到循环之中,重新寻找满足条件的解。如果 整个双层的for循环执行完之后,NumOfSwap!=0,那么也就说明在刚刚这个双重循环中,数组中的结果得到了优化,那么就回到while的判断条件处,看冲突数是否已经为0,如果为0,那么已经找到了解,如果不为0,那么继续在当前数组的基础上从头开始进行交换,直至最终找到没有冲突的排列,然后退出循环打印结果。





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值