n皇后问题是一个以国际象棋为背景的问题:在n×n的国际象棋棋盘上放置n个皇后,使得任何一个皇后都无法直接吃掉其他的皇后,即任意两个皇后都不能处于同一条横行、纵行或斜线上。
利用递归和回溯算法轻松解决
递归:
模拟手工摆放:我们逐列放皇后(从小到大逐列摆放),现在先给第一列放皇后,很显然我们会把它放在第一行,接下来,给第二列放皇后,那么第二列的皇后能放在哪些位置?这时候需要一个判断函数来判断第二列的皇后能放在那里。如果第二列找到放皇后的某一行,那么就进行第三列的摆放,这里就是递归。
回溯:
如果还没有进行到最后一列的摆放时就已经不能再放皇后,那么此时需要怎么做?就返回递归的前面一次,把当前列的前面一列的皇后放在后面的行数上(从小到大逐行检验)。如果此时逐行检验已经遍历完所有的行数还是出现上面不能摆放的情况,那就在再返回上一次的递归,在进行逐行检验。这里就是回溯。
为方便描述,我们定义一个一维数组pos[i]表示皇后所在位置:设pos[i]=j,则表示第i列的皇后放在第j行上。
#include<cstdio>
#include<cmath>
#include<iostream>
using namespace std;
#define Max 20 //表示最大的棋盘边长,可以自定义为其它数据
int pos[Max+1]; //为什么只需要定义一个一维数组就能描述二维的棋盘?
//pos[i]是这样定义的:即第i列的皇后放在第pos[i]行上,
// 也就是说,pos[i]的索引i代表皇后所在的列,它的值pos[i]代表皇后所在的行
int n; //棋盘的边长和皇后的数量
int sum; //可以成功摆放的数量,每次遍历成功就加1
bool checkNQ(int col){ //对第col之前的列进行逐列检查,pos[i]中i的值为列,pos[i]的值为行
for(int i=1;i<col;i++)
if(pos[i]==pos[col]||abs(i-col)==abs(pos[i]-pos[col]))
//如果行数相同,或者行数相减的绝对值等于列数相减的绝对值
//此时都不能放皇后,因为对第col列之前的列进行逐列检查,
//所以不需要再进行列是否相同的判断
return false;
return true;
}
void dfsNQ(int col,int n){
if(col==n+1) //成功遍历一次,sum加1,然后继续探索其他情况
sum++;
for(int i=1;i<=n;i++){
pos[col]=i;//假设第col列的皇后放在第i行上,然后利用checkNQ()函数检查是否能放入
//第一种情况,如果能放入,则继续假设下一列也放在第i行(实际上第i行此时已经不能放了,
//所以cheakNQ()函数就会直接返回false,
//然后上面的for循环中的i自动加1,即假设第col+1列放在第i+1行,然后又继续检查能否放入。
//第二种情况,如果不能放入,for循环中的i就自动加1,即假设第col列的皇后放在第i+1行上,
//又继续检查能否放入
//如果当col<=n时(即列还没有遍历完)再也不能在任何一行放皇后,那么此时dfsNQ中的for循环的i已经
//遍历完,dfsNQ就会返回到上一级的dfsNQ(col+1,n),此时col就会自动减1(因为每次递归都是加1),
//然后,尝试第col列的皇后能否放在第i+1行上,如此进行回溯。
if(checkNQ(col))
dfsNQ(col+1,n); //进行递归
}
}
int main()
{
cout<<"请输入皇后的数量:";
cin>>n;
dfsNQ(1,n); //传入第一列和n,从第一列开始放皇后。
cout<<endl<<"满足条件的所有摆放次数为:";
cout<<sum;
return 0; //说明:dfsNQ函数完全退出的条件是所有满足条件的情况都已经遍历过,再也没有满足条件的遍历
//根据递归的初始化分析得知,前面的遍历都会默认第一行第一列的位置会放皇后,
//然而实际情况是这个位置不放皇后也能满足条件的次数甚至更多,那么程序在运行到什么时候会把
//第一行第一列的位置放空呢?答案是当第一行第一列放皇后的满足条件的所有遍历都结束时,
//就会把第一列的皇后放在第二行,而把第一行第一列的位置放空。
//如此进行到最后,最后面是把第一列的皇后放在最后一行,
//然后再全部遍历,结束时整个dfsNQ函数递归运行结束。主函数return 0.
}
接下来是2n皇后问题
问题描述
给定一个n*n的棋盘,棋盘中有一些位置不能放皇后。现在要向棋盘中放入n个黑皇后和n个白皇后,使任意的两个黑皇后都不在同一行、同一列或同一条对角线上,任意的两个白皇后都不在同一行、同一列或同一条对角线上。问总共有多少种放法?n小于等于8。
输入格式
输入的第一行为一个整数n,表示棋盘的大小。
接下来n行,每行n个0或1的整数,如果一个整数为1,表示对应的位置可以放皇后,如果一个整数为0,表示对应的位置不可以放皇后。
输出格式
输出一个整数,表示总共有多少种放法。
样例输入
4
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
样例输出
2
样例输入
4
1 0 1 1
1 1 1 1
1 1 1 1
1 1 1 1
样例输出
0
如果你之前熟悉n皇后问题的解法,相信对于2n皇后问题也是很好理解的。如果对于n皇后问题还是不太清楚,那请浏览本人前面的文章,就很清楚n皇后后问题的递归与回溯解法。
对于递归与回溯,原理跟n皇后问题差不多,其难点就在于如何对该棋盘进行既不重复也不遗漏的遍历,还有就是递归的结束条件,这两点在前面的n皇后问题中已经讲解得很透彻。
现在,我们假设你已经对n皇后问题的解法有较为透彻的理解。那么我们就这样摆放皇后:我们先摆放黑皇后(或者先摆放白皇后也可以),我们先定义一个棋盘map_Q[][],从标准输入流中定义,然后我们用两个一维数组posb[i],posw[i]分别表示棋盘的放置黑/白皇后的位置,如posb[i]=j,则表示棋盘上的第i列的黑皇后被放在第j行上。然后,我们同样根据n皇后的算法把所有的黑皇后放置完成,此时,我们在这样的基础上再放置白皇后,原理也跟n皇后的放置一样,只是多了一些判断能否放置的前提条件。当白皇后也完全放置时,结束一次大的递归循环,然后返回到黑皇后的dfs循环里面,在进行又一次大循环。
#include<cmath>
#include<iostream>
using namespace std;
const int maxn = 10;
int n;
int map_Q[maxn][maxn];
int posb[maxn]={0};
int posw[maxn]={0};
int ans;
bool checkw( int cur) //检查函数
{
for( int i = 1; i < cur; i++)
if( posw[i] == posw[cur] || abs(i-cur) == abs(posw[i]-posw[cur]))
return false;
return true;
}
bool checkb( int cur) //检查函数
{
for( int i = 1; i < cur; i++)
if( posb[i] == posb[cur] || abs(i-cur) == abs(posb[i]-posb[cur]))
return false;
return true;
}
void dfs_white( int cur)
{
if( cur == n+1) //白皇后也全部放完,次数+1
{
ans++;
}
for( int i = 1; i <= n; i++)
{
if( posb[cur] == i) //表示第cur列的第i行位置已经被黑皇后占用,
continue; //结束当前循环,i+1
if( map_Q[cur][i] == 0) //再判断前提条件是否成立
continue;
posw[cur] = i; //尝试把第cur列的白皇后放在第i行上
if( checkw(cur)) //判断能否放置白皇后
dfs_white(cur+1); //递归
}
}
void dfs_black( int cur)
{
if( cur == n+1) //当黑皇后处理完时,再处理白皇后
{
dfs_white(1);
}
for( int i = 1; i <= n; i++)
{
if( map_Q[cur][i] == 0) //如果第cur列第i行满足放皇后的前提条件即 mp[cur][i] == 1
continue; //如果不满足,则结束当前循环,进行下一次循环即i+1。
posb[cur] = i; //就尝试把第cur列的黑皇后放在第i行上
if( checkb(cur)) //然后判断该尝试是否成立,如成立,则进行递归,如不成立,则尝试把当前列的黑皇后放在下一行(i+1行)上。
dfs_black(cur+1); //递归
}
}
int main()
{
cin>>n;
for( int i = 1; i <= n; i++) //定义棋盘
for( int j = 1; j <= n; j++)
cin>>map_Q[i][j];
ans = 0;
dfs_black(1); //先把黑皇后放在第一列
cout<<ans<<endl;
return 0;
}