题目链接:http://poj.org/problem?id=1077
使用A*搜索中的估价函数要小于等于真实值才能更快的收敛,本题中已经花费的开销是从start状态到目前状态的花费,也就是dist,可以将字符串映射到一个整数表示开销,如果在hash中没有这个键则表示
这个state并没与进入过队列,从队列中取出的每一个值一定是到这个状态的最优解,这个很容易证明,所以我们只需要标识每个状态第一次出队就可以了。
八数码问题最经典的也在于这个估价函数,也就是曼哈顿距离和,因为移动的步数一定是大于等于曼哈顿距离和的。
每次从堆中取出估价最小的值进行扩展,进入堆中时的代价为dist[state]+f(state).
本题中移动方式的记录是通过倒推的方式,有当前状态通过一个操作operation 达到下一个状态,只要从终态一直向前回溯直到start状态就得到了反的操作,取反之后就是结果。结果不一。
八数码问题无解当且仅当逆序对数为奇数,有解当且仅当逆序对数为偶数,前者容易证明,后者的证明异常复杂。
代码:
#include<iostream> #include<queue> #include<cstdio> #include<string> #include<algorithm> #include<map> using namespace std; int f(string state){ int res=0; for(int i=0;i<state.length();i++){ if(state[i]!='x'){ int t=state[i]-'1'; res+=abs(i/3-t/3)+abs(i%3-t%3); } } return res; } int dx[]={-1,0,1,0},dy[]={0,1,0,-1}; char op[]={'u','r','d','l'}; string bfs(string start){ string end="12345678x";//最终状态 map<string,int> dist;//使用平衡树记录一个状态的dist map<string ,bool> vis;//状态的访问标记 map<string,pair<string,char>>prev;//状态转移记录 priority_queue< pair<int,string>,vector< pair<int,string> >,greater< pair<int,string> > >heap; heap.push(make_pair(f(start),start)); dist[start]=0; while(!heap.empty()){ pair<int ,string> p=heap.top(); heap.pop(); string now=p.second; if(vis[now])continue; vis[now]=true; if(now==end)break;//第一次出队的最终状态一定是最优状态 int step=dist[now]; int x,y; for(int i=0;i<now.length();i++){ if(now[i]=='x'){ x=i/3,y=i%3; break; } } for(int i=0;i<4;i++){ int a=x+dx[i]; int b=y+dy[i]; if(a>=0 && a<3 && b>=0 && b<3){ string nxt = now; swap(nxt[x*3+y],nxt[a*3+b]); //如果没有对新的状态进行过记录或者是距离值更大,则重新计算 //unodered_map中的count函数返回该键对应的值的数量 if(!dist.count(nxt) || dist[nxt]>step+1){ dist[nxt]=step+1; prev[nxt]=make_pair(now,op[i]);//由source状态通过op[i]转移获得 heap.push(make_pair(dist[nxt]+f(nxt),nxt)); } } } } string ans=""; while(end!=start){ ans+=prev[end].second; end=prev[end].first; } reverse(ans.begin(),ans.end()); return ans; } int main(){ string g,c,seq; for(int i=0;i<9;i++){ cin>>c; g+=c; if(c!= "x")seq+=c; } int t=0; for(int i=0;i<seq.length();i++)//八数码问题无解仅当逆序对数量是奇数 for(int j=i+1;j<seq.length();j++){ if(seq[i]>seq[j])t++; } if(t%2)puts("unsolvable"); else cout<<bfs(g)<<endl; }