学习紫书从此节开始突然觉得难度提升了一个等级,分析一个程序需要花不少时间(因为自己太渣)。“回溯法”这个算法是非常重要的甚至是搞算法的必须要掌握的一个高级算法,它的技巧就是“碰到就回解”。当然,它还是需要使用**“递归”**。那我就跟着紫书的顺序继续整理下去吧!
####-1.八皇后问题
这个问题,呃,熟悉到简直不能再熟悉了,把它理解好其实就能够理解“回溯”的精髓了。
简单介绍一下这个问题:就是在n*n的格子中,摆放n个皇后,而在国际象棋中,皇后是可以横向攻击也能纵向攻击,也能斜向攻击,那么摆放这n个皇后的时候就需要避免这些皇后互相攻击,即不能在同一行同一列同一对角线上。最典型的就是八皇后,即在8×8的格子里摆放8个皇后。
我们思考一下,如果我们把八个皇后在这个8×8的格子里做个全排列然后判断是否符合条件,这样是不是可行的。很明显,这样的时间复杂度是非常大的,所以我们需要设计一个更优化的算法。那么我们就每一行摆一个皇后,这是一个循环,然后再在其中一列中摆一个皇后,这又是一个循环,发现冲突回到上一个过程。
我们模拟一下四皇后问题(其实只是规模上小一点而已),下图就是假定的一个棋盘(4*4),0表示空位置,1表示皇后。我们就按照刚才的摆放思路来摆一摆。
0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 -> 0 0 0 0
0 0 0 0 0 0 0 0
1.这是初始的棋盘 2.先摆第一行第一列
啥都没有
1 0 0 0 1 0 0 0
0 0 1 0 0 0 1 0
0 0 0 0 -> 0 0 0 0
0 0 0 0 0 0 0 0
3.然后摆第二行 4.再摆第三行
而第一二列摆不了 我们发现哪列都摆不了都冲突
(列和对角线冲突)
所以摆第三列
1 0 0 0 1 0 0 0
0 0 0 1 0 0 0 1
0 0 0 0 -> 0 1 0 0
0 0 0 0 0 0 0 0
5.只能回到第3步 6.摆第三行
即重新需要摆第二行 发现只能摆第二列
第三列发现不行
就摆第四列
1 0 0 0 0 1 0 0
0 0 0 1 0 0 0 0
0 1 0 0 -> 0 0 0 0
0 0 0 0 0 0 0 0
7.摆第四行 8.回到摆第一行的地步
发现又失败了 因为第二行所有位置都不行
那么我们接下来回到哪呢 所以摆第一行时候换到第二列
0 1 0 0 0 0 1 0
0 0 0 1 0 0 0 0
1 0 0 0 -> 0 0 0 0
0 0 1 0 0 0 0 0
9—12.按照刚才的思路发 13.又回到摆第一行的时候
现当第一行摆放第二列时 然后继续按照刚才的思路摆
依次摆放二三四行都有 接下来不在赘述
不冲突的摆放情况,
说明这个成功了。
喷血、、画这个过程真是坑!其实画一个解答树会更简单,但是我希望把整个思想过程直观地描述下来。
代码如下:
#include<cstdio>
#include<iostream>
using namespace std;
const int maxn = 100010;
int n,tot=0;
int A[maxn];
/*cur代表填数的当前行*/
void search(int cur)
{
if(cur == n) tot++;//当cur等于行数,那么就代表摆放成功
else for(int i=0;i<n;i++)
{
int ok = 1;
A[cur] = i;//尝试把第cur行的皇后放入第i列
for(int j=0;j<cur;j++)
{
if(A[cur]==A[j]||cur-A[cur]==j-A[j]||cur+A[cur]==j+A[j])//判断是否同列同主对角线和同副对角线
{
ok = 0; break;//如果是,则不递归并退循环,回溯
}
}
if(ok) search(cur+1);//如果合法,则递归
}
}
int main()
{
cin>>n;
search(0);
cout<<tot;
return 0;
}
好了,下面问题明天继续整理。
后记:
今天刷洛谷回顾一些经典算法,发现上面的代码过不去一个点,原因是因为判断同列对角线有点耗时,所以使用一个二维数组vis[ 2 ] [ ]来判断。
else for(int i=0; i<N; i++)
{
if(!vis[0][i] && !vis[1][cur+i] && !vis[2][cur-i+N])
{
A[cur] = i;
vis[0][i] = vis[1][cur+i] = vis[2][cur-i+N] = 1;
solve(cur+1);
vis[0][i] = vis[1][cur+i] = vis[2][cur-i+N] = 0;
}
}