洛谷 1379 八数码难题
Christopher_Yan · · 题解
八数码难题,一道经典的BFS题目(反正我也不会什么康托展开...)写完后,发现众多题解中并没有几篇双向BFS,于是就来发一波题解(尽管单向的BFS也能AC,但双向BFS明显要高效许多)。
思路如下:
首先我们可以将这个3x3的矩阵转化为一个九位数字,存入队列时也会方便很多,按方向尝试0周围的数字,判重记答案时可以使用map。然后就这样进行单向的BFS,accepted!
重要优化:双向BFS
双向BFS的使用要求之一就是知道终止状态,这道题目实在是在合适不过了。 这里可以将判重数组的值设为0,1,2,分别代表未访问过,顺序访问过,逆序访问过,当某个状态被顺序逆序都访问过时,那么这就是连接答案的那个状态。如果看到这里还不甚理解,不用着急,代码是比较清晰的。
双向BFS满分代码:
#include<bits/stdc++.h>
#define ll long long int
using namespace std;
int n,g=123804765;
short a[4][4],fx,fy,nx,ny;
int dx[4]={1,-1,0,0};
int dy[4]={0,0,1,-1}; //代表向四个方向移动
queue<int> q;
map<int,int> v;
map<int,int> ans;
void solve()
{
if(n==g) //特判
{
printf("0");
exit(0);
}
q.push(n); //起始状态与终止状态同时入队
q.push(g);
ans[n]=0;
ans[g]=1;
v[g]=2; //将两个方向标记成不同的数字
v[n]=1;
while(!q.empty())
{
ll now,cur=q.front();
q.pop();
now=cur;
for(int i=3;i>=1;i--)
for(int j=3;j>=1;j--)
{
a[i][j]=now%10,now/=10;
if(a[i][j]==0) fx=i,fy=j;
}
for(int i=0;i<4;i++)
{
nx=fx+dx[i];
ny=fy+dy[i];
if(nx<1 || nx>3 || ny<1 || ny>3) continue;
swap(a[fx][fy],a[nx][ny]);
now=0;
for(int p=1;p<=3;p++)
for(int j=1;j<=3;j++)
now=now*10+a[p][j]; //数字转矩阵
if(v[now]==v[cur])
{
swap(a[fx][fy],a[nx][ny]); //一定要先换回来再跳过
continue;
}
if(v[now]+v[cur]==3) //说明新延伸出的点已被另一方向访问过
{
printf("%d",ans[cur]+ans[now]);//两方向步数和即为总步数
exit(0);
}
ans[now]=ans[cur]+1;
v[now]=v[cur]; //与上一状态的方向保持一致
q.push(now);
swap(a[fx][fy],a[nx][ny]); //不要忘记将还回来
}
}
}
int main()
{
scanf("%d",&n);
solve();
}
最后说一下这个双向的效率有多高:
单向BFS:8388ms.
双向BFS:228ms.
这里列举了两组数据,科学准确的说明了....
所以我认为这道题目写双向BFS是个不错的选择。