《算法竞赛进阶指南》0x27A* 八数码问题 POJ1077

题目链接: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;
}

 

posted @ 2020-06-21 15:00  WA自动机~  阅读(164)  评论(0编辑  收藏  举报