zoukankan      html  css  js  c++  java
  • cdoj 414 八数码 (双向bfs+康拓展开,A*)

     一道关乎人生完整的问题。

    DBFS的优越:避免了结点膨胀太多。

    假设一个状态结点可以扩展m个子结点,为了简单起见,假设每个结点的扩展都是相互独立的。

    分析:起始状态结点数为1,每加深一层,结点数An = An-1*m。假如搜索了i层找到终点,那么经过的结点数是O(i^m),如果从两边同时搜索,结点数是O(i^(m/2))。

    极端情况,终点完全封闭。

    DBFS的正确姿势:

    图片来源:http://www.cppblog.com/Yuan/archive/2011/02/23/140553.aspx

    下面是原博客上的分析:

    交替结点可能会因为扩展顺序而认为s-1-5-3-t是最短路。//这也可能得到正确结果,与结点的扩展顺序有关系

    然而交替层次的做法才是正确的。

    优化:提供速度的关键在于使状态扩展得少一些,所以优先选择队列长度较少的去扩展,保持两边队列长度平衡。这比较适合于两边的扩展情况不同时,一边扩展得快,一边扩展得慢。如果两边扩展情况一样时,加了后效果不大,不过加了也没事。

     ----------------------------------分割线------------------------------------------------------

    DBFS的代码也是磕了好久想到怎么实现的。

    加了奇偶剪枝和一些小优化

    更新。经过仔细思考,简化了代码。

    /*
     Created by Rey Chen on 2015.7.5
     */
    
    #include<bits/stdc++.h>
    using namespace std;
    
    //#define local
    const int maxn = 362880;
    int vis1[maxn];
    int vis2[maxn];//保存距离起点的距离,初始值-1
    int fac[9];
    
    struct node
    {
        int e[9];
        int p;
        int cod;//避免二次计算,初始值为-1
        int code() {
            if(~cod) return cod;
            int Hash = 0;
            for(int i = 0; i < 9; i++) {
                int cnt = 0;
                for(int j = i+1; j < 9; j++)
                    if(e[j] < e[i]) cnt++;
                Hash += fac[8-i] * cnt;
            }
            return cod = Hash;
        }
        int rev_value(){//用于奇偶剪枝
            int res = 0, cnt ,i ,j;
            for(i = 0; i < 9; i++) {
                if(e[i]) {
                    cnt = 0;
                    for(j = i+1; j < 9; j++)
                        if(e[j] && e[j] < e[i]) cnt++;
                    res += cnt;
                }
            }
            return res;
        }
    };
    
    node start;
    node ed;
    int edHash;
    int nodesz;
    typedef vector<node>* vnodep;
    vector<node> v1;
    vector<node> v2;
    vector<node> v3;
    vector<node>:: iterator it,tmp_it;
    
    const int dx[] = {-1, 1, 0, 0};
    const int dy[] = { 0, 0,-1, 1};
    bool ilegal[9][4];
    void solve()
    {
        if(start.rev_value()&1) { puts("unsolvable"); return; }//忽略0,操作不会改变逆序数总数的奇偶性。
        int t;
        t = start.code();
        if(t == edHash) { puts("0");return;}
        memset(vis1,-1,sizeof(vis1) );
        memset(vis2,-1,sizeof(vis2) );
        vis1[t] = 0;
        vis2[edHash] = 0;
    
        v1.clear(); v2.clear(); v3.clear();
        vnodep q1 = &v1, q2 = &v2, nxt = &v3;
        q1->push_back(start);
        q2->push_back(ed);
        int *V1 = vis1, *V2 = vis2;
        while( !q1->empty() && !q2->empty() ) {
            if(q1->size() > q2->size()) swap(q1,q2),swap(V1,V2); //化简代码的小技巧
            for(it = q1->begin(), tmp_it = q1->end(); it != tmp_it ; it++){
                node& u = *it;
                node v;
                for(int i = 0;i < 4;i++){
                    if(ilegal[u.p][i]) continue;
                    int np = u.p + dx[i]*3 + dy[i];
                    memcpy(&v,&u,nodesz); v.cod = -1;//memcpy 比直接赋值要快
                    swap(v.e[np],v.e[u.p]);
                    if(!~V1[t = v.code()]){
                        V1[t] = V1[u.code()] + 1;
                        if(~V2[t]){  printf("%d
    ",V2[t]+V1[t]);  return;  }
                        v.p = np;
                        nxt->push_back(v);
                    }
                }
            }
            q1->clear();
            swap(q1,nxt);
        }
        puts("unsolvable");
    }
    
    void init(){
        fac[0] = 1;
        for(int i = 1; i < 9; i++)
            fac[i] = fac[i-1]*i;
         for(int i = 0; i < 3; i++)
         for(int j = 0; j < 3; j++){
            for(int k = 0; k < 4; k++)
                if( (i == 0&& k == 0) || (i == 2&& k == 1) || (j == 0&& k == 2) || (j == 2&& k == 3) )
                    ilegal[i*3+j][k] = true;
                else ilegal[i*3+j][k] = false;
         }
    }
    
    int main()
    {
    #ifdef local
        freopen("in.txt","r",stdin);
        freopen("out.txt","w",stdout);
    #endif // local
        char ch,s[24];
        init();
        for(int i = 0; i < 8;i ++)
            ed.e[i] = i+1;
        ed.e[8] = 0;
        ed.p = 8;
        ed.cod = -1;
        edHash = ed.code();
        nodesz = sizeof(ed);
    
    
        while(gets(s)) {
            int j = 0;
            for(int i = 0; i < 9; i ++, j++) {
                while(sscanf(s+j,"%c",&ch),ch == ' ')j++;
                if(ch == 'x'){
                    start.e[i] = 0; start.p = i;
                }else {
                    start.e[i] = ch - '0';
                }
            }
            start.cod = -1;
            solve();
        }
        return 0;
    }
    View Code

     花了点时间写了A*。A*的关键在于估价函数,估价函数必须要小于实际值,越接近越好。

    这里取的是除去x以后的曼哈顿距离。

    这题为什么不需要两个表?需要open表是因为有可能有捷径的出现,这题不需要。

    #include<bits/stdc++.h>
    using namespace std;
    
    int t[9],s[9],Zero,tHashCode;
    int fac[9];
    struct node
    {
        int p[9], z, f, dist, hashCode;
        bool operator < (const node& rhs) const {
           return f > rhs.f || (f == rhs.f && dist > rhs.dist);
        }
    };
    
    inline int Hash(int *a)
    {
        int ans = 0;
        for(int i = 0; i < 8; i++) {
            int cnt = 0;
            for(int j = i+1; j < 9; j++) if(a[j] < a[i]) cnt++;
            ans += fac[8-i] * cnt;
        }
        return ans;
    }
    
    inline int Rev_value(int *a){
        int ans = 0;
        for(int i = 0; i < 8; i++) {
            int cnt = 0;
            for(int j = i+1; j < 9; j++) if(a[j] && a[j] < a[i]) cnt++;
            ans += cnt;
        }
        return ans;
    }
    int Cost[9][9];
    
    //除去x之外到目标的网格距离和
    //x 和 其他数交换,理想情况每次距离减一
    inline void Manhattan(node &A)
    {
       A.f = A.dist;
        for(int i = 0; i < 9; i++)if(A.p[i])
            A.f += Cost[i][A.p[i]-1];
    }
    
    bool vis[362880];
    const int dx[] = {-1, 1, 0, 0};
    const int dy[] = { 0, 0,-1, 1};
    int dz[9];
    bool ilegal[9][4];
    
    void AstarBfs()
    {
        if(Rev_value(s)&1) { puts("unsolvable"); return; }
        node u;
        u.hashCode = Hash(s);
        if(u.hashCode == tHashCode) {puts("0"); return;}
        memset(vis,0,sizeof(vis));
        vis[u.hashCode] = 1;
        memcpy(u.p,s,sizeof(s));
        u.dist = 0;
        u.z = Zero;
        priority_queue<node> q;
        Manhattan(u);
        q.push(u);
        while(q.size()) {
            u = q.top(); q.pop();
            if(u.hashCode == tHashCode) {printf("%d
    ",u.dist);return;}
            node v;
            for(int i = 0; i < 4; i++) {
                if(ilegal[u.z][i]) continue;
                v.z = u.z + dz[i];
                memcpy(v.p,u.p,sizeof(u.p));
                swap(v.p[v.z],v.p[u.z]);
                v.hashCode = Hash(v.p);
                if(vis[v.hashCode]) continue;
                vis[v.hashCode] = 1;
                v.dist = u.dist +1;
                Manhattan(v);
                q.push(v);
            }
        }
      puts("unsolvable");
    }
    
    void init()
    {
        for(int i = 0; i < 9; i++)
            for(int j = 0; j < 9; j++)
            Cost[i][j] = (abs(i/3-j/3) + abs(i%3-j%3));
        for(int i = 0; i < 8; i++)
            t[i] = i+1;
        t[8] = 0;
        fac[0] = 1;
        for(int i = 1; i < 9; i++)
            fac[i] = fac[i-1]*i;
        tHashCode = Hash(t);
        for(int i = 0; i < 3; i++)
        for(int j = 0; j < 3; j++)
            for(int k = 0; k < 4; k++){
                int nx = i+dx[k], ny = j + dy[k];
                ilegal[i*3+j][k] = !(nx>=0 && nx < 3 && ny >= 0 && ny < 3);
            }
        for(int k = 0; k < 4; k++) dz[k] = dx[k]*3 + dy[k];
    }
    
    
    int main()
    {
        init();
        char str[20];
        while(fgets(str,20,stdin)) {
            int j = 0;
            for(int i = 0; i < 9; i++, j++){
                char ch;
                while(sscanf(str+j,"%c",&ch),ch == ' ')j++;
                if(ch == 'x'){
                    s[i] = 0;Zero = i;
                }else {
                    s[i] = ch - '0';
                }
            }
            AstarBfs();
        }
        return 0;
    }
    View Code

    附上数据生成器

    #include<bits/stdc++.h>
    using namespace std;
    
    int main()
    {
        srand( time( NULL ) );
        char s[20] ;
        char ori[20] = "12345678";
        int n = 10;
        int m = 10;
        int init = 1;
        for(int i = 0; i < 50; i++) next_permutation(ori,ori+8);
            for(int i=0;i<n;i++)
            {
                for(int j = 0;j<m;j++){
                    strcpy(s,ori);
                    s[8] = 'x';s[9] = '';
                    swap(s[8],s[rand()%8]);
                    for(int k = 0;k < 9; k++)
                        printf("%c%c",s[k],k==8?'
    ':' ');
                }
                next_permutation(ori,ori+8);
            }
    
    }
    View Code

    .bat

    :loop
    make.exe>data.txt
    std.exe<data.txt>std.txt
    my<data.txt>my.txt
    fc my.txt std.txt
    if not errorlevel 1 goto loop
    pause
  • 相关阅读:
    共享库的使用(.so)文件
    C/C++ 的宏中#和##的作用和展开
    有趣的打字训练
    libtool 创建库的工具
    vcpkg-微软开发的VC++打包工具
    Q他中的乱码再理解
    关于头文件和源文件的分别
    std::set 中内部元素有序条件删除的理解
    python 的 字节码 导入使用
    Pychar-20170301快捷键
  • 原文地址:https://www.cnblogs.com/jerryRey/p/4621887.html
Copyright © 2011-2022 走看看