您的位置:首页 > 其它

回溯 ——八皇后问题

2018-02-24 18:02 295 查看
八皇后问题:

在8*8的棋盘上,放置8个皇后,使两两之间互不攻击。满足:

1).不在同一列

2).不在同一行

3).不在同一对角线

回溯算法:

确定了解空间的组织结构后,回溯法从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。这个开始结点就成为一个活结点,同时也成为当前的扩展结点。在当前的扩展结点处,搜索向纵深方向移至一个新结点。这个新结点就成为一个新的活结点,并成为当前扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点。回溯法即以这种工作方式递归地在解空间中搜索,直至找到所要求的解或解空间中已没有活结点时为止。

回溯算法的基本思想:

从一条路往前走,能进则进,不能进则退回来,换一条路再试。八皇后问题就是回溯算法的典型,第一步按照顺序放一个皇后,然后第二步符合要求放第2个皇后,如果没有位置符合要求,那么就要改变第一个皇后的位置,重新放第2个皇后的位置,直到找到符合条件的位置就可以了。回溯在迷宫搜索中使用很常见,就是这条路走不通,然后返回前一个路口,继续下一条路。回溯算法说白了就是穷举法。不过回溯算法使用剪枝函数,剪去一些不可能到达 最终状态(即答案状态)的节点,从而减少状态空间树节点的生成。回溯法是一个既带有系统性又带有跳跃性的的搜索算法。它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树。算法搜索至解空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。如果肯定不包含,则跳过对以该结点为根的子树的系统搜索,逐层向其祖先结点回溯。否则,进入该子树,继续按深度优先的策略进行搜索。回溯法在用来求问题的所有解时,要回溯到根,且根结点的所有子树都已被搜索遍才结束。而回溯法在用来求问题的任一解时,只要搜索到问题的一个解就可以结束。这种以深度优先的方式系统地搜索问题的解的算法称为回溯法,它适用于解一些组合数较大的问题。

每一行一定有一个皇后,因此思考每个应该放在哪一列;

用试探的方法
“向前走,碰壁回头”
的策略,这就是
“回溯法”
的解题思路。

i为行数 j为列数

q[i] = j 表示第i行上的皇后放在第j列

C[j] = false 说明放好皇后之后当前列不安全

左上到右下的对角线1需要 i - j 为常数;

L[i-j+9] = false 表示在( i , j ) 位置放皇后之后,对角线1不安全。

左下到右上的对角线2需要 i + j 为常数;

R[i+j] = false 表示在( i , j ) 位置放皇后之后,对角线2不安全。

于是得出了在( i , j ) 放皇后之后的安全条件为
nq = C[j] && L[i-j+9] && R[i+j]


C[j]为布尔型,j = 1, 2, … , 8 初始化时全部为true;

L[k]为布尔型,k = i-j+9, k = 2, 3, … , 16 初始化时全部为true;

R[m]为布尔型,m = i+j, k = 2, 3, … , 16 初始化时全部为true;

思路:

1. 放皇后q[i] = j 后,同时让第j列和过( i , j )位置的两条对角线变为不安全;

2. 检查i是否为8,如果为8,表示已经放完8个皇后,这时让方案数Num加1,输出该方案下8个皇后在棋盘上的位置;否则,未到8个,还要让皇后数i加1再试着放,这时还要递归调用Try(i+1)。

3. 为了寻找不同的方案,当一个方案输出之后,要回溯,将先前放的皇后从棋盘上拿起来,看看还有没有可能换一处放置。这时要将被拿起来的皇后的所在位置的第j列和两条对角线恢复为安全的。

代码的解释:以下代码单步执行,Try(1)~Try(5)会先安排第一行到第五行的皇后(第五行的皇后在第4列),之后调用Try(6)
(向前走)
,Try(6)中由于每一列都无法放置皇后,因此Try(6)的for循环结束后什么都没做就结束了。此时回溯到Try(5)
(碰壁回头)
,Try(5)将之前的i=5,j=4安全标志修改为安全,继续Try(5)的for循环,第五行的皇后位置变为i=5,j=8,调用Try(6),第六列依旧没有位置放皇后,Try(5)的for循环结束,回到Try(4)……

#include <iostream>
using namespace std;

const int Normalize = 9;
int Num;
int Q[9]; //记录8个皇后所占用的列号
bool C[9]; //C[1]~C[8],当前列是否安全
bool L[17]; //L[2]~L[16],(i-j)对角线是否安全
bool R[17]; //R[2]~R[16],(i+j)对角线是否安全

void Try(int i) //i为行号
{
for(int j = 1; j <= 8; j++)
{
if(C[j] && L[i - j + Normalize] && R[i + j]) //第i行j列安全
{
//第一件事,占用位置(i,j)
Q[i] = j;
//修改安全标志,包括所在列和两个对角线
C[j] = false;
L[i-j+Normalize] = false;
R[i+j] = false;

//第二件事,判断是否放完8个皇后
if(i < 8)
Try(i+1);
else
{
Num++;
cout << "方案" << Num << ":";
for(int k = 1; k <= 8; k++)
cout << Q[k] << " ";
cout << endl;
}
//第三件事,修改安全标志,回溯
C[j] = true;
L[i - j + Normalize] = true;
R[i + j] = true;
}
}
}

int main()
{
Num = 0;
for(int i = 0; i < 9; i++)
C[i] = true;

for(int i = 0; i < 17; i++)
L[i] = R[i] = true;

Try(1); //递归放置8个皇后,从第一个开始放
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息