八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?在国际象棋的规则中,皇后的攻击范围为一个米字型,也就是说两个皇后不能位于同一个纵行,横行,斜线上。
其实八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n×n,而皇后个数也变成n。当且仅当 n = 1 或 n ≥ 4 时问题有解。令一个一位数组a[n]保存所得解,其中a[i] 表示把第i个皇后放在第i行的列数(注意i的值都是从0开始计算的),下面就八皇后问题做一个简单的从规则到问题提取过程。
(1)因为所有的皇后都不能放在同一列,因此数组的不能存在相同的两个值。
(2)所有的皇后都不能在对角线上,那么该如何检测两个皇后是否在同一个对角线上?我们将棋盘的方格成一个二维数组,如下:
这里我们采用回溯策略来求解八皇后问题:
回溯法(英语:backtracking)是穷尽搜索算法(英语:Brute-force search)中的一种。
回溯法采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况:
* 找到一个可能存在的正确的答案
* 在尝试了所有可能的分步方法后宣告该问题没有答案
在最坏的情况下,回溯法会导致一次复杂度为指数时间的计算。
采用递归来实现:先设定一个位置摆放一个皇后,按这种方式一直搜索下去,如果发现行不通了,就返回上一层递归,把搜索的位置向后移,直到搜索到符合条件的解:
下面是用递归的回溯法实现的代码:
#include<cstdio>
#include<cstring>
#include<cstdlib>
const int maxn=8;
int queen[maxn],sum=0;//储存每一行皇后的位置
//int n,m;
void print()//输出当前解法的位置
{
int i;
printf("(");
for(i = 0; i < maxn; i++)
{
printf(" %d", queen[i]);
}
printf(")\n");
sum++;//记录解法的种数
}
bool check(int n)//判断当前列是否能放置皇后
{
for(int i=0;i<n;i++)
{
if(queen[i]==queen[n] || abs(queen[n]-queen[i])==abs(n-i))//判断列,对角线
return false;
}
return true;
}
void nqueen(int n)//回溯递归
{
for(int i=0;i<maxn;i++)
{
queen[n]=i;//把皇后摆到当前的位置
if(check(n))
{
if(n==maxn-1)//说明摆放成功
{
print();
}
else
nqueen(n+1);
}
}
}
int main()
{
nqueen(0);
printf("%d\n",sum);
}
把上面的程序改一点就可以实现n皇后的求解。
在网上看到的别人的非递归写的代码:(参考一下)
#include<iostream>
using namespace std;
#define N 8
//N代表皇后数
void queen()
{
int Count=0; //计算总共的解的数量
int column[N+1]; //column[m]=n表示第m行,第n行放置了皇后,这里下表并从0开始
int row[N+1]; //row[m]=1表示第m行没有皇后,=0表示有皇后
int b[2*N+1]; //b[m]=1表示第m条主对角线没有皇后,
int c[2*N+1]; //c[m]=1表示第m条次对角线没有皇后,=0表示有皇后
int numQueen=1; //计数已经放置的皇后数目,当numQueen=N时候则表示已经完成探测
int good=1; //good=1表示没有发生冲突,good=0表示发生冲突
//初始化这些标记
for(int j=0;j<N+1;++j)
{
row[j]=1;
}
for(int j=0;j<2*N+1;++j)
{
b[j]=c[j]=1;
}
column[1]=1;
column[0]=0; //初始化第一行第一列,第二行第二列放置皇后
do
{
//没有发生冲突,则继续向下探测,增加皇后或者判断当前是否是解
if(good)
{
//当前皇后数是解,打印,继续向下探测
if(numQueen==N)
{
Count++;
cout<<"找到解"<<endl;
for(int j=1;j<N+1;++j)
{
cout<<j<<"列"<<column[j]<<"行"<<endl;
}
//最后一个棋子向下移动,移动到本列最后一个
while(column[numQueen]==N)
{
numQueen--; //皇后数减1,即列数减1,回溯
//回溯后将该列以及该列最后一行状态位修改
//第numQueen列column[numQueen]行处状态位置修改
row[column[numQueen]]=1;
b[numQueen+column[numQueen]]=1;
c[N+numQueen-column[numQueen]]=1;
}
column[numQueen]++; //回溯至上一行,向上一行的下一列继续探测
}
//当前不是解,那么继续向下探测
else
{
//改变该位置对应标志
row[column[numQueen]]=0;
b[numQueen+column[numQueen]]=0;
c[N+numQueen-column[numQueen]]=0;
//本次位置没有发生冲突,也不是正确解,那么就应该向下探测下一列的第一行
column[++numQueen]=1;
}
}
//如果当前发生了冲突,就在本列继续向下,如果到了本列最后一行,则回溯到上一列
else
{
while(column[numQueen]==N) //到了本列最后一行,还是冲突,那么回溯到上一列
{
numQueen--;
row[column[numQueen]]=1;
b[numQueen+column[numQueen]]=1;
c[N+numQueen-column[numQueen]]=1;
}
column[numQueen]++; //发生冲突了,又没有到本列的最后一行,那么在本列继续向下一行探测
}
//检测放置了这个位置后是否冲突
good=row[column[numQueen]]&b[numQueen+column[numQueen]]&c[N+numQueen-column[numQueen]];
}while(numQueen);
cout<<N<<"皇后总共找到解:"<<Count<<"个"<<endl;
}
int main()
{
queen();
// system("pause");
return 0;
}