学习过程中的一些个人理解,有错误还请指出。
n皇后问题是算法中的经典问题,常见解法就是dfs,这里先分析4皇后问题,进而推广到n皇后。
皇后问题要求在n*n的棋盘上放置n个皇后,保证同行同列以及两条对角线上只能有一个
个人觉得在没写代码之前可以这样考虑,我们先在第一行第一列(0,0)位置放置,那么第一行不能在放置了;我们跳到第二行,显然第一列、第二列、无法放置,因为他们四个方位上都有皇后,我们放到第三列上,那么第二行不能在放置了;我们跳到第三行,显然每一列都不能放置;我们回到第二行,将皇后放到第四列上,再跳到第三行去试,第一列不行,第二列可以,我们跳到第四行,发现都不行,我们回到第三行,第三列、第四列也不行,这时我们回到第三行;第三行已经试完了,回到第一行,把原来放在第一行第一列(0,0)位置的皇后放到(0,1),再进行一次上面的步骤。
由以上分析我们发现存在递归,因为每行的处理方式一样;存在穷举,因为需要把所有的情况都试试。符合dfs的特点。先贴下代码,然后再着重分析下过程。
#include<iostream>
using namespace std;
int ans=0;
//列 对角线1 对角线2
bool col[10],x1[20],x2[20];
bool check(int r,int i){
return !(col[i]||x1[r+i]||x2[r-i+4]);
}
void dfs(int r){
if(r==4)
{
ans++;
return;
}
for(int i=0;i<4;i++){
//在这里我们可以将访问的格子坐标打印出来进行观察
//cout<<r<<" "<<i<<endl;
if(check(r,i)){
col[i]=x1[r+i]=x2[r-i+4]=true;
dfs(r+1);
col[i]=x1[r+i]=x2[r-i+4]=false;
}
}
}
int main(){
dfs(0);
cout<<endl;
cout<<ans;
}
如果我们把程序运行过程中访问的棋盘格子坐标按递归的顺序写下来,其实是一个树状结构。和深度优先搜索的概念一样,每次都是先向更深一层搜索,这对应了棋盘的行数,每次往下一行去试。当器子节点没有孩子时,也就是下一行不能再放皇后了,就返回其父节点,继续遍历其他子节点。
如果需要打印棋盘,定义一个mp全局二维数组就行了
void dfs(int r){ if(r==4){ for(int i=0;i<4;i++){ for(int j=0;j<4;j++){ if(mp[i][j]){ cout<<"Q"; } else{ cout<<"*"; } } cout<<endl; } cout<<endl; ans++; return; } for(int i=0;i<4;i++){ //cout<<r<<" "<<i<<endl; if(check(r,i)){ mp[r][i]=true; col[i]=x1[r+i]=x2[r-i+4]=true; dfs(r+1); mp[r][i]=false; col[i]=x1[r+i]=x2[r-i+4]=false; } } }
另外 一个例子马踏飞棋盘,马起始位置在(0,0),求出马走完棋盘所有格子有几种方案。
以5*5的棋盘为例,马每次进行跳跃,有八个落脚的位置,选择一个位置落脚,那么下次还会有八个选择。初始位置在(0,0),这样可以画出马跳跃位置的状态树(部分)。
当子节点是叶节点时返回其父节点继续遍历其他子节点,这里子节点就相当于马下一步跳的坐标,我们可以想象,如果有方案能使马跳完整个棋盘,那么这棵树的深度应为24(假设根节点在0层)。
#include<iostream>
#include<stdio.h>
using namespace std;
int ans;
bool vis[20][20];
int direction[8][2]={{-1,-2},{-1,2},{1,-2},{1,2},{2,-1},{2,1},{-2,1},{-2,-1}};
int jump[20][20];
void dfs(int x,int y,int cur){
if(x<0||y<0||x>=5||y>=5||vis[x][y]==true){
return ;
}
jump[x][y]=cur+1;
if(cur==24){
for(int i=0;i<5;i++){
for(int j=0;j<5;j++){
printf("%4d",jump[i][j]);
}
cout<<endl;
}
cout<<endl;
ans++;
return ;
}
vis[x][y]=true;
for(int i=0;i<8 ;i++){
dfs(x+direction[i][0],y+direction[i][1],cur+1);
}
vis[x][y]=false;
}
int main(){
dfs(0,0,0);
cout<<ans;
return 0;
}
5*5的棋盘总共有304中方案。
说下写代码时遇到的问题,首先很明显dfs函数需要两个参数x,y坐标,然后为了判断是否已经走完了整个棋盘,需要一个参数cur记录已经走了多少步,这个其实对应了搜索树的深度。
另外,dfs()函数中的if(x<0||y<0||x>=5||y>=5||vis[x][y]==true)和if(cur==24)这两个代码段不能互换位置,如果互换的话会出现逻辑错误。因为我们要先判断下一跳的位置是否合理,若不合理,那就算当前步数是24也就不需要判断,只有这一步是第24步并且跳的合理我们才认为这是一个可行的方案。这个地方有点绕,我个人觉得这样考虑比较好,如果已经走了23步(注意我们从零开始计数),还差最后一步,可选择的八个下一跳坐标中有一个不符合要求(可能是出界了或者已经访问过),但这时我们先判断的是cur==24,结果ans自加了1,这个就是不正确的,所以要先判断下一坐标点是否符合,不符合就直接return返回。
输出马跳的顺序用的是一个jump数组,里面实际上存储的是搜索树中一个可行方案中的各节点的高度,马是按照树上的坐标从上到下依次跳跃的。