N皇后算法—优化版

N皇后问题

【题目描述】 N皇后问题

         世界历史上曾经出现一个伟大的罗马共和时期,处于权力平衡的目的,当时的政治理论家波利比奥斯指出:“事涉每个人的权利,绝不应该让任何权利大的压过其它力量,使他人无法立足于平等条件与之抗辩的地步。”这类似著名的N皇后问题,即在NXN格的国际象棋上摆放N个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,请问有多少中摆法,并将每种摆法打印出来。图1所示即是摆法的一种。

《N皇后算法—优化版》

【输入格式】

输入一个整数,即N(14>N>3)。

【输出格式】

输出所有摆法,每个摆法占一行

【输入样例】

4

【输出格式】

2413

3142

【输出说明】

N=4的棋盘输出的两种方案即图2所示

《N皇后算法—优化版》

递归算法1

    若想遍历所有摆法而无一遗漏,可以逐行从上至下、从左至右尝试棋子的摆放。以N=4的棋盘为例,其遍历过程如图3所示。

《N皇后算法—优化版》

Tips:棋盘坐标上一般会想到用二维数组来表示,如定义二维数组a[8][8],则a[0][0]=1代表棋盘第一行第一列有棋子,a[3][4]=0代表棋盘第4行第5列无棋子……但实际上只用一个一维数组可以解决该问题。

           

            例如,使用a[8]来表示棋盘坐标时,假设a[0]=7,即表示第1行第7列有棋子,a[1]=2即表示第2行第2列有棋子,而且这种方法无须再判断两皇后是否在同一行。

         可以定义一个Try(x,y)函数判断棋盘(x,y)处是否可以放皇后:

(1)不在同一列

(2)不在对角线上,即有两棋子坐标分别为(X1,Y1),(X2,Y2),则|X1-X2|!=|Y1-Y2|

源码:

//N皇后问题——递归算法
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
int n,a[21];


void print()				//打印棋子 
{int i=1;
	for(;i<=n;i++)  
		cout<<a[i];
	cout<<" ";
		

		
	if(i%7==0)
		cout<<endl;
} 


void printGraph()			//图形化打印布局
{
	system("color F0");  	//DOs命令改变背景色和前景色,颜色为16进制数0~F
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=n;++j)
			if(a[i] == j)
				cout<<"O";
			else
				cout<<"▅";
		cout<<endl;
	} 
	cout<<endl;
} 


int Try(int x,int y)			//测试x行y列可否摆放棋子,成功返回1,否则返回0 
{
	int j=1;
	while(j<x)					//与数组中已放好的数比较 
	{
		if((a[j] == y) || (abs(x-j) == abs(a[j]-y)))
			return 0;
		++j;					//右移一格继续尝试 
	}
	return 1;
}

void place(int x)				//递归函数 
{
	int y;
	if(x>n)						//棋子第n行已摆好,则打印成功方案 
	{
		print();
		//printGraph();			//打印图形化布局 
	}
	else
	{
		for(y=1;y<=n;++y)		//该行棋子依次从左向右移 
			if(Try(x,y))		//如果可以摆放 
			{
				a[x]=y;			//给a[x]赋值 
				place(x+1);		//继续下一行的递归 
			}
	}
}


int main()
{
	cin>>n;
	place(1);			//从第1行开始递归 
	return 0;
}

该程序中有个用于调试的printGraph()函数,选用它可图形化显示布局方案,如图所示:

《N皇后算法—优化版》

为了深入理解递归调用的过程,设N=4,则可将place(4)到place(4)的过程展开,如图所示:

《N皇后算法—优化版》

运行过程如图下图所示。

         模拟该程序运行过程时会发现,通过程序不断的进进退退,最后就打印除了全部的布局。

由此可知,递归中常常隐含回溯,回溯即选择一条路走下去,发现走不通,就返回去再选一条路走。

《N皇后算法—优化版》

以下为八皇后问题的解决,共92种摆法:

《N皇后算法—优化版》

改程序当N>13时,速度将慢的难以忍受,并且考虑到输出方案浪费了大量的时间,此后的优化算法只需要输出方案数即可。

递归算法2

         以N=4为例,下图左是正斜线,右是反斜线,即有2xN-1条、反斜线。

《N皇后算法—优化版》《N皇后算法—优化版》

1号正斜线所占据的棋盘单元为(1,1)

2号正斜线所占据的棋盘单元为(1,2)(2,1)

3号正斜线所占据的棋盘单元为(1,3)(2,2)(3,1)

4号正斜线所占据的棋盘单元为(1,4)(2,3)(3,2)(4,1)

5号正斜线所占据的棋盘单元为(2,4)(3,3)(4,2)

6号正斜线所占据的棋盘单元为(3,4)(4,2)

7号正斜线所占据的棋盘单元为(4,4)

可以发现规律如下:

(1)   同一正斜线所占据的棋盘单元行列之和相等,如上面7条正斜线分别为2、3、4、5、6、7、8.

(2)   同一反斜线所占据的棋盘单元行列之差相等。

故可以定义bool数组x1[ ]用来记录行列之和为i的正斜线是否已经被占据,bool数组x2[ ]用来记录行列之差为i的反斜线是否已经被占据。考虑到行列之差可能为负数,棋盘坐标(x,y)对应x2[ x-y+n]。

再定义一个bool数组y[ ]判断列冲突,如果y[i]=1,说明前面已有皇后放到在这一列,再放会发生列冲突,y[i]=0,说明当前第i列还没有放置皇后。

参考代码:

//N皇后——递归算法2
#include<iostream>
#include<cstdio>
#include<cstdlib>

using namespace std;

int n,num;

int a[20]={0};
bool y[41],x1[41],x2[41];


void place(int x)   					//递归函数
{
	if(x>n)
		++num;							//找到一个摆法,计数器加1 
	else
	{
		for(int i=1;i<=n;++i)
		{
			if(y[i] == 0 && x1[x+i] ==0 && x2[x-i+n] == 0)		//如果列,正斜线,反斜线无冲突 
			{
				a[x] = i;						//给a[x]赋值 
				y[i] = 1;						//列坐标做标记 
				x1[x+i] = 1;					//正斜线做标记 
				x2[x-i+n] = 1;					//反斜线做标记 
				place(x+1);						//继续下一行的递归 
				y[i] = 0;						//恢复 
				x1[x+i] = 0;					//恢复正斜线标记为0 
				x2[x-i+n] = 0;					//恢复反斜线标记为0 
			}
		}
	}
} 




int main()
{
	cin>>n;
	place(1);
	cout<<num<<endl;
}

优化后的程序提高了很多,但是当N>=14时候,速度还是较慢,我们还可以再次基础上继续优化,这里我就不继续写了,希望大家能够自己优化。

    原文作者: 汉诺塔问题
    原文地址: https://blog.csdn.net/you_will_know_me/article/details/78419088
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞