八数码难题
八数码难题
我们看到这个题的第一感觉就是BFS,显然BFS可做
经过我坚持不懈的努力发现了问题所在
我们发现,这个题具有一定的奇特性质:
既知道起始状态也知道终点状态
我们就想到了运用双向BFS的内容(一开始也我也不会)
大体是记录空格位置和棋盘状态,加入队列不断增加节点入队。同时注意判重,将棋盘转换为九位数(如果第一个数字为0则是八位数),用hash表解决
双向宽搜,顾名思义就是从两边搜,适用于已知起始状态和目标状态,求最短步骤的题目。
我们可以开两类数组,分别表示正方方向搜索的队列,然后初始,目标状态分别入对应队列,进行扩展节点,直到两个方向搜索相遇时即得出最短步骤。
出于优化时间的目的,我们往往先搜队列中节点少的方向,轮流进行。
具体讲就是:两个方向根据队列中节点数交替扩展节点,每扩展一个节点为,在本队列判重后还要在另一个方向的队列中找是否出现,出现说明相遇,输出最短步骤为正方方向扩展到该节点所需最短步骤。
注意双向广搜时,反方向扩展节点时操作与正方向相反,比如向左移动要变为向右移动,但这点对本题没有影响。
这里找了一份比较好看的代码,相对来说比较容易理解
#include <iostream> #include <cstdio> #include <algorithm> #include <map> #include <queue> using namespace std; const int las=123804765;//状态压缩终状态 //空格与右、左、上、下交换 const int dx[4]={1,-1,0,0}; const int dy[4]={0,0,1,-1}; int ma[4][4];//现状态地图 int beg;//初始状态 int zero_x,zero_y;//零的位置 int go_x,go_y;//要交换的位置 //开一个876543211的数组肯定compile error,因此用map记录 map<int,int> dir; map<int,int> ans; queue<int> qu; //双向bfs可节省大量时间 //其使用要求是知道最终状态,适合此题 void pre_work() { scanf("%d",&beg); if(beg==las) { printf("%d\n",0); exit(0); } qu.push(beg); qu.push(las); //将两头的bfs区分为不同方向 ans[beg]=0; ans[las]=1; dir[beg]=1; dir[las]=2; } void work() { int now,cur; while(!qu.empty()) { cur=now=qu.front(); qu.pop(); //将now状态分到地图中 for(int i=3;i>0;i--) for(int j=3;j>0;j--) { ma[i][j]=now%10; now/=10; if(!ma[i][j]) { zero_x=i; zero_y=j; } } //空格尝试与上下左右交换 for(int i=0;i<4;i++) { go_x=zero_x+dx[i]; go_y=zero_y+dy[i]; if(go_x<1||go_x>3||go_y<1||go_y>3) continue; swap(ma[zero_x][zero_y],ma[go_x][go_y]); now=0; //重新将交换后的状态记录为now for(int i=1;i<=3;i++) for(int j=1;j<=3;j++) now=10*now+ma[i][j]; //判重:来回走,舍弃 if(dir[now]==dir[cur]) { //注意要先交换回来 swap(ma[zero_x][zero_y],ma[go_x][go_y]); continue; } //如果搜索到了另一边已经搜到的状态 if(dir[now]+dir[cur]==3) { printf("%d\n",ans[cur]+ans[now]); return; } //方向不变,路程加一 ans[now]=ans[cur]+1; dir[now]=dir[cur]; swap(ma[zero_x][zero_y],ma[go_x][go_y]); qu.push(now); } } } int main() { pre_work(); work(); return 0; }