问题描述
在n×n格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n后问题等价于在n×n格的棋盘上放置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线上。
问题分析
对于N后问题,实质上是对于一棵n+1层的满n叉树的深度优先遍历问题。所以通过这一点可以确定问题解空间。
-
解空间
- 解的形式:(X1,X2,⋅⋅⋅,Xn)
Xi的取值范围:Xi=1,2,⋅⋅⋅,n
组织解空间
- 满n叉树,树深为n
对于求解过程,可以分为两种,采用递归的思想或者使用迭代的思想。两者都可以得到所有解。
问题解决
思考过程
开始解决问题的时候没有第一时间抽象出对于的算法思想,只是就题论题。所以变量的定义就不成功。对于变量的合理定义我认为是问题解决的重要一步,正确的变量定义可以使得问题思路明确富有逻辑。下边是我一开始的变量定义与声明。
int* chess=NULL;
chess=new int[N*N];
很明显,这时问题考虑很不全面。但是并没有意识到。根据限制条件,可以和简单的写出判断是否可以放子的函数,如下:
bool queen::check(point p){
//x从0开始
int w=p.gety();
int h=p.getx();
for(int i=0;i<h;i++){
if(chess[i*N+w]==1){
return false;
}
}
int fh=h-1;
int fw=w-1;
while(fh>=0&&fw>=0){
if(chess[fh*N+fw]==1){
return false;
}
fh--;
fw--;
}
fh=h-1;
fw=w+1;
while(fh>=0&&fw<N){
if(chess[fh*N+fw]==1){
return false;
}
fh--;
fw++;
}
return true;
}
上边提到的这个判断函数十分麻烦,毫无效率可言。下边提到另一种思考方式会有一个十分简单的写法。
之后,就是根据逻辑开始深度遍历满n叉树。这里的遍历是遍历抽象的树,所以要考虑实际的约束条件。
- 以层为序,每层从第一个开始遍历。
- 回溯时,当清除上一行的旧标记,从旧标记的下一个开始继续检查。
- 结束循环的条件是在最后一行中找到了合适的位置。此时输出一个解
- 当回溯到第一行时算法结束。
非递归:
void queen::start(){
point p(0,0);
while(p.getx()>=0)
{
while(p.gety()<N)
{
if(p.getx()==N-1 && check(p))
{
chess[p.getx()*N+p.gety()]=1;
showresult();
}
else if(check(p))
{
chess[p.getx()*N+p.gety()]=1;
p.setX(p.getx()+1);
p.setY(0);
} else {
p.setY(p.gety() + 1);
}
}
p.setX(p.getx()-1);
for(int i=0;i<N;i++){
if(chess[p.getx()*N+i]==1){
chess[p.getx()*N+i]=0;
p.setY(i+1);
break;
}
}
}
}
递归:
void queen::r_start(point p) {
for(int i=0; i<N; i++)
{
p.setY(i);
if(p.getx() == N-1 && check(p))
{
chess[p.getx()*N+p.gety()]=1;
showresult();
return;
}
else if(check(p))
{
chess[p.getx()*N+p.gety()]=1;
p.setX(p.getx()+1);
r_start(p);
p.setX(p.getx()-1);
chess[p.getx()*N+p.gety()]=0;
}
}
}
对于上边的解法的改进
1. 对于变量声明的改进
分析问题的特点,对于每个棋子来说每一行只有一个这个限制条件是可以在循环中体现的。所以将保存结果的数组大小定义为n。这样下标表示行号,数组中的元素表示列号。
2. 对于判断函数的改进
bool canPlaceQueen(int k)
{
for(int i = 1; i < k; i++)
{
if(queen[i] == queen[k] || abs(k-i) == abs(queen[k]-queen[i])) return false;
}
return true;
}
这样通过数组中两个元素下标和元素的关系可以判断是否符合条件。对于对角线的判断就可以用一个绝对值的式子进行判断。
总结
回溯法,主要思想是对解空间的深度优先遍历,利用约束条件和限界函数对解空间树进行剪枝。只需要一个解的时候只需要输出最先遇到的解即可。需要全部解的时候就要遍历整棵解空间树。
核心还是对于数据结构中树的相关知识的使用??