两个版本。思路大体相同,都是用的回溯。不过数据结构上的版本不太容易懂。先写上来吧。
struct Queen{
int x,y;
Queen(int xx, int yy):x(xx), y(yy){};
bool operator==(Queen const& q)const
{
return (q.x == x) || (q.y == y) || (x + y == q.x + q.y) || (x - y == q.x - q.y); //在判断这里巧妙的解决了对角线的问题
}
bool operator != (Queen const& q)const
{
return !((*this) == q);
}
};
static int nCheck = 0;
static int nResult = 0;
void placeQueens(int N)
{
stack<Queen> solu;
Queen q(0,0);
do{
if ((solu.size() >= N) || (q.y >= N)) //y值大于N越界不必多说,此外若栈中储存元素达到n
//个,说明此时已经是一组解。所以相当于越界了,回溯
{
q = solu.pop(); //这里就是回溯的具体解决方案,将上一个储存元素弹出
q.y++; //并使列增加,这里不管增加后有没有越界,由下一次循 //环来判断
}
else //这里用if,else,因为不能保证回溯后是否能继续执 //行,干脆用分支,用下一次的if判断来保证
{
while((q.y < N) && (solu.find(q) >= 0) //搜寻可放置皇后的位置并记录步数
{
q.y++;
nCheck++;
}
if (q.y <N) //如果找到一个可以放置的地方,就将其入栈,判断能否 //输出完整的解,并搜寻下一行,从第0列开始
{
solu.push(q);
if (solu.size() >= N)
nResult++;
q.x++;
q.y = 0;
}
}while((q.x >0) || (q.y < N)) //退出循环的条件是搜寻到第0行第N列
}
一些总结:
①退出循环的条件是搜寻到第0行第N列
②进入程序后会发生以下几种情况:
(一)满足循环条件,没有越界,进过循环判断后可以放置,并且不是最后一行,那么下一步就是入栈并执行下一行
(二)满足循环条件,没有越界,进过循环判断后可以放置,是最后一行,下一步仍然是执行下一行,但是会回溯
(三)满足循环条件,没有越界,进过循环判断后没有可以放置的位置,那么经过循环之后q.y应该是等于N的,跳过入栈,到下次循环会回溯
(四)solu.size() >= N,即栈满了,此时像越界一样处理,回溯并y++
(五)q.y >=N,越界,需要回溯
③相比于递归来说比较难理解,但是空间复杂度较少,而且通过剪枝也得到了很好的优化。
///
八皇后递归版
const int Normalize = 9;
int Num;
int q[9];
bool s[9];
bool L[17]; //用来存放对角线标记的
bool Y[17];
void try(int col)
{
if(col == 9)
{
Num++; //此时q中存放一个解,可输出
}
for (int row = 1; row <= 8; row++)
{
if (S[row] && R[col + row] && L[col - row + Normalize])
{
q[col] = row; //标记呃,递归,回溯
S[row] = false;
R[col + row] = false;
L[col - row + Normalize] = false;
Try(col + 1);
S[row] = true;
R[col + row] = true;
L[col - row + Normalize] = true;
}
}
}
int main()
{
Num = 0;
for (int i = 0 ; i < 9; i ++)
S[i] = true;
for (int i = 0 ; i < 9; i ++)
{
L[I] = R[i] = true;
}
try(1);
return 0;
}
感觉递归版没啥好说的,简单便捷,回溯也方便》。。。