zoukankan      html  css  js  c++  java
  • 《算法竞赛进阶指南》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;
    }
  • 相关阅读:
    tty & pty & pts
    PageRank
    How to run a terminal inside of vim?
    vimdiff
    svn's tree conflict
    svn's diff command
    符号表分离
    gcc -D
    Options for Debugging Your Program or GCC
    invoking gdb
  • 原文地址:https://www.cnblogs.com/randy-lo/p/13172454.html
Copyright © 2011-2022 走看看