洛谷 1379 八数码难题
八数码难题,一道经典的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是个不错的选择。