转载于https://blog.csdn.net/qq_38063665/article/details/79629652
问题描述:N皇后问题是一个古老而著名的问题,是回溯算法的典型案例。该问题由西洋棋棋手马克斯·贝瑟尔于1848年提出。在国际象棋上,N皇后问题变成了8皇后问题,著名的数学家高斯认为有76种方案,后来有人用图论的知识解出92种结果,计算机发明后,可以通过算法实现问题的求解。显然,大数学家有时候也会败在计算机面前。8皇后问题是指在8*8的棋盘上摆放8个皇后,使得任意两个皇后都不在同一行、同一列或者同一斜线上,求满足这种摆放的解为多少个。(答案是92种)
现在需要分析以下问题:
1.如何摆放才能不重复也不遗漏?
很显然,我们可以模拟手工摆放:我们逐列放皇后(从小到大逐列摆放),现在先给第一列放皇后,很显然我们会把它放在第一行,接下来,给第二列放皇后,那么第二列的皇后能放在哪些位置?这时候需要一个判断函数来判断第二列的皇后能放在那里。如果第二列找到放皇后的某一行,那么就进行第三列的摆放,这里就是递归。
如果还没有进行到最后一列的摆放时就已经不能再放皇后,那么此时需要怎么做?就返回递归的前面一次,把当前列的前面一列的皇后放在后面的行数上(从小到大逐行检验)。如果此时逐行检验已经遍历完所有的行数还是出现上面不能摆放的情况,那就在再返回上一次的递归,在进行逐行检验。这里就是回溯。
为方便描述,我们定义一个一维数组pos[i]表示皇后所在位置:设pos[i]=j,则表示第i列的皇后放在第j行上。
2.递归的终止条件是什么?
由初始条件知道,第一行第一列放皇后,那么会不会有第一行第一列不放皇后的情况呢?我们能不能把这个位置放空,然后把第一列的皇后放在第二行上,或者更后面的一行。答案是肯定能的。那么问题来了,程序在运行到什么时候会进行这样的处理?当第一行第一列放皇后的所有情况都已经遍历完之后,就会把第一列的皇后放在第二行上,再把这个情况都遍历完,然后又把第一列的皇后放在第三行上,依次类推,直到把第一列的皇后放在最后一行上,当遍历完成时,递归终止。
以下为C++实现的代码:(已经调试运行成功)
#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.
}