八数码

境界一、 暴力广搜+STL?(HDU 内存超限,POJ 时间超限)

开始的时候,自然考虑用最直观的广搜,因为状态最多不超过40万,计算机还是可以接受的,由于广搜需要记录状态,并且需要判重,所以可以每次图的状态转换为一个字符串,然后存储在stl中的容器set中,通过set的特殊功能进行判重,由于set的内部实现是红黑树,每次插入或者查找的复杂度为Log(n),所以,如果整个算法遍历了所有状态,所需要的复杂度为n*Log(n),在百万左右,可以被计算机接受,由于对string操作比较费时,加上stl全面性导致 速度不够快,所以计算比较费时,这样的代码只能保证在10秒内解决任何问题。但,明显效率不够高。POJ上要求是1秒,无法通过。
map存路径,set判重,string存状态,毫无疑问,炸了。

#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<algorithm>
#include<iostream>using namespace std;
char input[1000];int dir[4][2] = { { -1,0 },{ 1,0 },{ 0,-1 },{ 0,1 } };string d = "durl";set<string>f;
map<string, string>m;int sz = 0;
struct node
{
    string s;
    string path;
    int pos;

    node() {}
    node(string str, string pa, int Pos)
    {
        s = str;
        path = pa;
        pos = Pos;
    }
};
bool g(int a, int b)
{
    if (a >= 0 && a <= 2 && b >= 0 && b <= 2) return 1;
    return 0;
}
void pre()
{
    queue<node>q;
    q.push(node("12345678x", "", 8));
    m["12345678x"] = "";
    f.insert("12345678x");

    while (!q.empty())
    {
        node h = q.front(); q.pop();
        int a = h.pos / 3, b = h.pos % 3;
        for (int i = 0; i<4; i++)
        {
            int x = a + dir[i][0], y = b + dir[i][1];
            if (!g(x, y)) continue;
            int pos = 3 * x + y;
            swap(h.s[h.pos], h.s[pos]);
            if (f.find(h.s) != f.end())
            {
                swap(h.s[h.pos], h.s[pos]);
                continue;
            }
            q.push(node(h.s, d[i] + h.path, pos));
            f.insert(h.s);
            m[h.s] = d[i] + h.path;
            swap(h.s[h.pos], h.s[pos]);
        }
    }

}
int main()
{
    pre();
    while(~scanf("%s",input))
    {
        string v="";
        v = v + input[0];
        for (int i = 1; i <= 8; i++)
        {
            scanf("%s", input);
            v = v + input[0];
        }
        if (m[v] == "") cout << "unsolvable" << endl;
        else cout << m[v] << endl;
    }

    return 0;
}

境界二、广搜+哈希(POJ 453ms)

  考虑到费时主要在STL,对于大规模的遍历,用到了ST的set和string,在效率上的损失是很大的,因此,现在面临一个严重的问题,必须自己判重,为了效率,自然是自己做hash。有点麻烦,hash函数不好想,实际上是9!种排列,需要每种排列对应一个数字。网上搜索,得知了排列和数字的对应关系。取n!为基数,状态第n位的逆序值为哈希值第n位数。对于空格,取其为9,再乘以8!。例 如,1 3 7 24 6 9 5 8 的哈希值等于:0*0! + 2*1! + 0*2! + 1*3! + 3*4! +1*5! + 0*6! + 1*7! + 0*8! <9!具体的原因可以去查查一些数学书,其中1 2 34 5 6 7 8 9 的哈希值是0 最小,9 8 7 6 54 3 2 1 的哈希值是(9!-1)最大。而其他值都在0 到(9!-1) 中,且均唯一。然后去掉一切STL之后,甚至包括String之后,得到单向广搜+Hash的代码,算法已经可以在三秒钟解决问题,可是还是不够快!POJ时限是1秒,后来做了简单的更改,将路径记录方法由字符串改为单个字符,并记录父节点,得到解,这次提交,266ms是解决单问题的上限。当然,还有一个修改的小技巧,就是逆序对数不会改变,通过这个,可以直接判断某输入是否有可行解。由于对于单组最坏情况的输入,此种优化不会起作用,所以不会减少单组输入的时间上限。
利用康托展开对状态进行hash,hash值对应0–(9!-1),因此可以开数组判重,路径的记录可以记录到达某状态的最后一步操作是什么与父节点是什么。
从输入的状态开始进行BFS,直到找到最终状态。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#include<algorithm>using namespace std;
char t[1000];int c[10];int dir[4][2]={{-1,0},{1,0},{0,-1},{0,1}};char path[10]={'u','d','l','r'};char op[5],input[20];int ans;
stack<int>S;
struct Node
{
    int s,p;
    Node(){}
    Node(int S,int P){s=S,p=P;}
};
struct Path
{
    int from,dir;
}pa[400000];bool f[400000];
int getnum()
{
    int res=0;
    for(int i=0;t[i];i++)
        for(int j=i+1;t[j];j++)
            if(t[j]<t[i]) res=res+c[8-i];
    return res;
}
void getstr(int val)
{
    int tmp[10],flag[10];
    memset(flag,0,sizeof flag);
    for(int i=0;i<9;i++) tmp[i]=val/c[8-i],val=val%c[8-i];
    for(int i=0;i<9;i++)
    {
        int num=0;
        for(int j=0;j<9;j++)
        {
            if(flag[j]==0) num++;
            if(num==tmp[i]+1)
            {
                t[i]=j+'0'+1; if(t[i]=='9') t[i]='x';
                flag[j]=1;break;
            }
        }
    }
}
void bfs(int val,int Pos)
{
    queue<Node>Q;
    Q.push(Node(val,Pos));
    f[val]=1; pa[val].from=-1,pa[val].dir=-1;

    while(!Q.empty())
    {
        Node h=Q.front(); Q.pop();

        if(h.s==0)
        {
            ans=1;
            int now=h.s;
            while(1)
            {
                if(pa[now].from==-1) break;
                S.push(pa[now].dir);
                now=pa[now].from;
            }
            break;
        }


        int a=h.p/3, b=h.p%3; getstr(h.s);

        for(int i=0;i<4;i++)
        {
            int x=a+dir[i][0],y=b+dir[i][1];
            if(!(x>=0&&x<=2&&y>=0&&y<=2)) continue;
            int newpos=3*x+y;
            swap(t[newpos],t[h.p]);
            int news=getnum();
            if(f[news]) {swap(t[newpos],t[h.p]);continue;}
            pa[news].from=h.s, pa[news].dir=i, f[news]=1;
            Q.push(Node(news,newpos));
            swap(t[newpos],t[h.p]);
        }
    }
}
int main()
{
    c[0]=1; for(int i=1;i<=8;i++) c[i]=c[i-1]*i;
    while(~scanf("%s",op))
    {
        t[0]=op[0];
        int pos;
        for(int i=1;i<=8;i++)
        {
            scanf("%s",op); t[i]=op[0];
            if(t[i]=='x') pos=i;
        }
        int state=getnum();

        int sum=0;
        for(int i=0;t[i];i++)
        {
            if(t[i]=='x') continue;
            for(int j=0;j<i;j++)
            {
                if(t[j]=='x') continue;
                if(t[i]<t[j]) sum++;
            }
        }

        if(sum%2==1) { printf("unsolvable\n"); continue; }

        ans=0;
        memset(f,0,sizeof f);
        bfs(state,pos);

        while(!S.empty())
        {
            printf("%c",path[S.top()]);
            S.pop();
        }
        printf("\n");

    }
    return 0;
}

境界三、广搜+哈希+打表(HDU 263ms,POJ 579ms)

  好,问题可以在200—300ms间解决,可是,这里我们注 意到一个问题,最坏情况下,可能搜索了所有可达状态,都无法找到解。如果这个题目有多次输入的话,每次都需要大量的计算。其实,这里可以从反方向考虑下,从最终需要的状态,比如是POJ 1077需要的那种情况,反着走,可达的情况是固定的。可以用上面说的那种相应的Hash的方法,找到所有可达状态对应的值,用一个bool型的表,将可达状态的相应值打表记录,用“境界三”相似的方法记录路径,打入表中。然后,一次打表结束后,每次输入,直接调用结果!这样,无论输入多少种情况,一次成功,后面在O(1)的时间中就能得到结果!这样,对于ZOJ的多组输入,有致命的帮助!
从最终状态(0)开始进行BFS。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#include<algorithm>using namespace std;
char t[1000];int c[10];int dir[4][2]={{-1,0},{1,0},{0,-1},{0,1}};char path[10]={'d','u','r','l'};char op[5],input[20];
struct Node
{
    int s,p;
    Node(){}
    Node(int S,int P){s=S,p=P;}
};
struct Path
{
    int from,dir;
}pa[400000];bool f[400000];
int getnum()
{
    int res=0;
    for(int i=0;t[i];i++)
        for(int j=i+1;t[j];j++)
            if(t[j]<t[i]) res=res+c[8-i];
    return res;
}
void getstr(int val)
{
    int tmp[10],flag[10];
    memset(flag,0,sizeof flag);
    for(int i=0;i<9;i++) tmp[i]=val/c[8-i],val=val%c[8-i];
    for(int i=0;i<9;i++)
    {
        int num=0;
        for(int j=0;j<9;j++)
        {
            if(flag[j]==0) num++;
            if(num==tmp[i]+1)
            {
                t[i]=j+'0'+1; if(t[i]=='9') t[i]='x';
                flag[j]=1;break;
            }
        }
    }
}
void pre()
{
    queue<Node>Q;
    Q.push(Node(0,8));
    f[0]=1; pa[0].from=-1,pa[0].dir=-1;

    while(!Q.empty())
    {
        Node h=Q.front(); Q.pop();
        int a=h.p/3, b=h.p%3; getstr(h.s);
        for(int i=0;i<4;i++)
        {
            int x=a+dir[i][0],y=b+dir[i][1];
            if(!(x>=0&&x<=2&&y>=0&&y<=2)) continue;
            int newpos=3*x+y;
            swap(t[newpos],t[h.p]);
            int news=getnum();
            if(f[news]) {swap(t[newpos],t[h.p]);continue;}
            pa[news].from=h.s, pa[news].dir=i, f[news]=1;
            Q.push(Node(news,newpos));
            swap(t[newpos],t[h.p]);
        }
    }
}
int main()
{
    c[0]=1; for(int i=1;i<=8;i++) c[i]=c[i-1]*i;
    pre();

    while(~scanf("%s",op))
    {
        t[0]=op[0];
        for(int i=1;i<=8;i++) {scanf("%s",op); t[i]=op[0];}
        int state=getnum();
        if(f[state]==0) printf("unsolvable\n");
        else
        {
            while(1)
            {
                if(pa[state].from==-1) break;
                printf("%c",path[pa[state].dir]);
                state=pa[state].from;
            }
            printf("\n");
        }
    }
    return 0;
}

境界四、双向广搜+哈希(HDU 2636ms, POJ 32ms)

  Hash,不再赘述,现在,我们来进行进一步的优化,为了减少状态的膨胀,自然而然的想到了双向广搜,从输入状态点和目标状态1 2 3 4 5 6 7 8 9同时开始搜索,当某方向遇到另一个方向搜索过的状态的时候,则搜索成功,两个方向对接,得到最后结果,如果某方向遍历彻底,仍然没有碰上另一方向,则无法完成。
从起点和终点同时开始搜,当某个状态被两个方向的搜索同时搜过时,搜索结束,输出路径。
HDU 不加无解判断剪枝会超时。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#include<string>
#include<algorithm>using namespace std;
char input[1000],t[1000],op[5];int c[10];struct Node
{
    int s,p;
    Node(){}
    Node(int S,int P){s=S,p=P;}
};struct Path
{
    int from,dir;
}path[400000];int f[400000];
int dir[4][2] = { { -1,0 },{ 1,0 },{ 0,-1 },{ 0,1 } };string d[3] = {"","udlr","durl"};

queue<Node>q[3];int ans;

stack<int>S;
queue<int>Q;
int getnum()
{
    int res=0;
    for(int i=0;t[i];i++)
        for(int j=i+1;t[j];j++)
            if(t[j]<t[i]) res=res+c[8-i];
    return res;
}
void getstr(int val)
{
    int tmp[10],flag[10];
    memset(flag,0,sizeof flag);
    for(int i=0;i<9;i++) tmp[i]=val/c[8-i],val=val%c[8-i];
    for(int i=0;i<9;i++)
    {
        int num=0;
        for(int j=0;j<9;j++)
        {
            if(flag[j]==0) num++;
            if(num==tmp[i]+1)
            {
                t[i]=j+'0'+1; if(t[i]=='9') t[i]='x';
                flag[j]=1;break;
            }
        }
    }
}
bool g(int a, int b)
{
    if (a >= 0 && a <= 2 && b >= 0 && b <= 2) return 1;
    return 0;
}
void bfs(int now)
{
    Node h=q[now].front(); q[now].pop();
    int a=h.p/3,b=h.p%3;
    getstr(h.s);

    for(int i=0;i<4;i++)
    {
        int x=a+dir[i][0],y=b+dir[i][1];
        if(!g(x,y)) continue;
        int pos = 3 * x + y;

        swap(t[h.p],t[pos]);

        if(f[getnum()]==now) { swap(t[h.p],t[pos]); continue; }
        else if(f[getnum()]!=0)
        {
            ans=1;
            if(now==1)
            {
                S.push(i);
                int u=h.s;
                while(path[u].from!=-1) { S.push(path[u].dir); u=path[u].from; }
                u=getnum();
                while(path[u].from!=-1) { Q.push(path[u].dir); u=path[u].from; }
            }
            else
            {
                Q.push(i);
                int u=h.s;
                while(path[u].from!=-1) { Q.push(path[u].dir); u=path[u].from; }
                u=getnum();
                while(path[u].from!=-1) { S.push(path[u].dir); u=path[u].from; }
            }
            break;
        }
        else
        {
            f[getnum()]=now;
            path[getnum()].from=h.s;
            path[getnum()].dir=i;
            q[now].push(Node(getnum(),pos));
            swap(t[h.p],t[pos]);
        }
    }
}
void read()
{
    t[0]=op[0];
    for(int i=1;i<=8;i++)
        {scanf("%s",op); t[i]=op[0];}
    for(int i=0;i<=9;i++) input[i]=t[i];
}
void init()
{
    memset(f,ans=0,sizeof f);
    while(!q[1].empty()) q[1].pop();
    while(!q[2].empty()) q[2].pop();
}
void work(int s,int pos)
{
    q[1].push(Node(s,pos)); q[2].push(Node(0,8));
    f[s]=1; path[s].from=path[s].dir=-1;
    f[0]=2; path[0].from=path[0].dir=-1;
    while((!q[1].empty())&&(!q[2].empty()))
    {
        if(ans==1) break;
        bfs(1); if(ans==1) break;
        bfs(2); if(ans==1) break;
    }
}
int main()
{
    c[0]=1; for(int i=1;i<=8;i++) c[i]=c[i-1]*i;
    while(~scanf("%s",op))
    {
        read();

        int sum=0;
        for(int i=0;t[i];i++)
        {
            if(t[i]=='x') continue;
            for(int j=0;j<i;j++)
            {
                if(t[j]=='x') continue;
                if(t[i]<t[j]) sum++;
            }
        }

        if(sum%2==1)
        {
            printf("unsolvable\n");
            continue;
        }
         init();
        for(int i=0;i<9;i++)
            if(input[i]=='x'){work(getnum(),i); break; }
        if(ans==1)
        {
            while(!S.empty()) { printf("%c",d[1][S.top()]); S.pop(); }
            while(!Q.empty()) { printf("%c",d[2][Q.front()]); Q.pop(); }
        }
        else printf("unsolvable");
        printf("\n");
    }
    return 0;
}

境界五、A*+哈希+简单估价函数(POJ 391ms)

  用到广搜,就可以想到能用经典的A*解决,用深度作为g(n),剩下的自然是启发函数了。对于八数码,启发函数可以用两种状态不同数字的数目。接下来就是A*的套路,A*的具体思想不再赘述,因为人工智能课本肯定比我讲的清楚。但是必须得注意到,A*需要满足两个条件:
1.h(n)>h’(n),h’(n)为从当前节点到目标点的实际的最优代价值。
2.每次扩展的节点的f值大于等于父节点的f值小。
自然,我们得验证下我们的启发函数,h验证比较简单不用说,由于g是深度,每次都会较父节点增1。再看h,认识上, 我们只需要将h看成真正的“八数码”,将空格看空。这里,就会发现,每移动一次,最多使得一个数字回归,或者说不在位减一个。 h最多减小1,而g认为是深度,每次会增加1。所以,f=g+h, 自然非递减,这样,满足了A*的两个条件,可以用A*了!

之后的境界采用最小堆优化,这里我直接用了,f小的先取
试了一下用普通队列的,G++跑了680ms左右,C++TLE(主要原因是写法不好,我常数写的有点大了)。显然最小堆优化在效率上有极大的提高。
g(n)是深度,即从初始到目前的操作次数,h(n)是简单估价函数,表示目前状态与最终状态同一位置不同数字的个数。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#include<string>
#include<algorithm>using namespace std;
char input[1000],t[1000],op[5];int c[10];struct Node
{
    int s,p;
    int f,g,h;
    Node(){}
    Node(int S,int P,int G,int H)
    {
        s=S,p=P;
        g=G,h=H;
        f=g+h;
    }
    bool operator < (const Node &a) const {
        return f>a.f;
    }
};
struct Path
{
    int from,dir;
}path[400000];int flag[400000];int G[400000];
int dir[4][2] = { { -1,0 },{ 1,0 },{ 0,-1 },{ 0,1 } };string d = "udlr";

priority_queue<Node>Q;int ans;

stack<int>S;
int getnum()
{
    int res=0;
    for(int i=0;t[i];i++)
        for(int j=i+1;t[j];j++)
            if(t[j]<t[i]) res=res+c[8-i];
    return res;
}
void getstr(int val)
{
    int tmp[10],flag[10];
    memset(flag,0,sizeof flag);
    for(int i=0;i<9;i++) tmp[i]=val/c[8-i],val=val%c[8-i];
    for(int i=0;i<9;i++)
    {
        int num=0;
        for(int j=0;j<9;j++)
        {
            if(flag[j]==0) num++;
            if(num==tmp[i]+1)
            {
                t[i]=j+'0'+1; if(t[i]=='9') t[i]='x';
                flag[j]=1;break;
            }
        }
    }
}
bool g(int a, int b)
{
    if (a>=0 && a<=2 && b>=0 && b<=2) return 1;
    return 0;
}
void read()
{
    t[0]=op[0];
    for(int i=1;i<=8;i++) {scanf("%s",op); t[i]=op[0];}
    for(int i=0;i<=9;i++) input[i]=t[i];
}
void init()
{
    memset(G,-1,sizeof G);
    memset(flag,0,sizeof flag);
    while(!Q.empty()) Q.pop();
}
int H(int val)
{
    char tmp[10];
    for(int i=0;i<=9;i++) tmp[i]=t[i];

    getstr(val);

    int res=0;
    for(int i=0;i<9;i++)
    {
        if(i<8) if(t[i]-'0'!=i+1) res++;
        if(i==8&&t[i]!='x') res++;
    }

    for(int i=0;i<=9;i++) t[i]=tmp[i];
    return res;
}
void A_star(int s,int pos)
{
    flag[s]=2; G[s]=0;
    path[s].from=-1;
    path[s].dir=-1;
    Q.push(Node(s,pos,0,H(s)));
    while(!Q.empty())
    {
        Node h=Q.top(); Q.pop(); flag[h.s]=2;
        getstr(h.s);
        if(h.s==0)
        {
            ans=1; int now=h.s;
            while(1)
            {
                if(path[now].from==-1) return;
                S.push(path[now].dir);
                now=path[now].from;
            }
        }

        int a=h.p/3,b=h.p%3;

        for(int i=0;i<4;i++)
        {
            int x=a+dir[i][0],y=b+dir[i][1];
            if(!g(x,y)) continue;
            int newpos=3*x+y;

            swap(t[h.p],t[newpos]);
            int news=getnum();
            swap(t[h.p],t[newpos]);

            if(flag[news]==0||(flag[news]==1&&h.g+1<G[news]))
            {
                flag[news]=1; G[news]=h.g+1;
                path[news].from=h.s;
                path[news].dir=i;
                Q.push(Node(news,newpos,h.g+1,H(news)));
            }
        }
    }
}
int main()
{
    c[0]=1; for(int i=1;i<=8;i++) c[i]=c[i-1]*i;
    while(~scanf("%s",op))
    {
        read();
        int sum=0;
        for(int i=0;t[i];i++)
        {
            if(t[i]=='x') continue;
            for(int j=0;j<i;j++)
            {
                if(t[j]=='x') continue;
                if(t[i]<t[j]) sum++;
            }
        }

        if(sum%2==1)
        {
            printf("unsolvable\n");
            continue;
        }
        init();

        for(int i=0;i<9;i++)
            if(input[i]=='x'){A_star(getnum(),i); break; }
        while(!S.empty()){printf("%c",d[S.top()]); S.pop();}
        printf("\n");
    }
    return 0;
}

境界六、A*+哈希+曼哈顿距离?(POJ 735ms)

  A*的核心在启发函数上,境界五若想再提升,先想到的是启发函数。这里,曼哈顿距离可以用来作为我们的启发函数。曼哈顿距离听起来神神秘秘,其实不过是“绝对轴距总和”,用到八数码上,相当与将所有数字归位需要的最少移动次数总和。作为启发函数,自然需要满足“境界五”提到的那两个条件。现在来看这个曼哈顿距离,第一个条件自然满足。对于第二个,因为空格被我们剥离出去,所以交换的时候只关心交换的那个数字,它至多向目标前进1,而深度作为g每次是增加1的,这样g+h至少和原来相等,那么,第二个条件也满足了。A*可用了,而且,有了个更加优化的启发函数。
与境界5唯一不同的是 估价函数h()。这里估价函数采用曼哈顿距离。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#include<string>
#include<algorithm>using namespace std;
char input[1000],t[1000],op[5];int c[10];struct Node
{
    int s,p;
    int f,g,h;
    Node(){}
    Node(int S,int P,int G,int H)
    {
        s=S,p=P;
        g=G,h=H;
        f=g+h;
    }
};
struct Path
{
    int from,dir;
}path[400000];int flag[400000];int G[400000];
int dir[4][2] = { { -1,0 },{ 1,0 },{ 0,-1 },{ 0,1 } };string d = "udlr";

queue<Node>Q;int ans;

stack<int>S;
int getnum()
{
    int res=0;
    for(int i=0;t[i];i++)
        for(int j=i+1;t[j];j++)
            if(t[j]<t[i]) res=res+c[8-i];
    return res;
}
void getstr(int val)
{
    int tmp[10],flag[10];
    memset(flag,0,sizeof flag);
    for(int i=0;i<9;i++) tmp[i]=val/c[8-i],val=val%c[8-i];
    for(int i=0;i<9;i++)
    {
        int num=0;
        for(int j=0;j<9;j++)
        {
            if(flag[j]==0) num++;
            if(num==tmp[i]+1)
            {
                t[i]=j+'0'+1; if(t[i]=='9') t[i]='x';
                flag[j]=1;break;
            }
        }
    }
}
bool g(int a, int b)
{
    if (a>=0 && a<=2 && b>=0 && b<=2) return 1;
    return 0;
}
void read()
{
    t[0]=op[0];
    for(int i=1;i<=8;i++) {scanf("%s",op); t[i]=op[0];}
    for(int i=0;i<=9;i++) input[i]=t[i];
}
void init()
{
    memset(G,-1,sizeof G);
    memset(flag,0,sizeof flag);
    while(!Q.empty()) Q.pop();
}
int H(int val)
{
    char tmp[10];
    for(int i=0;i<=9;i++) tmp[i]=t[i];

    getstr(val);

    int res=0;

    for(int i=0;i<9;i++)
    {
        if(t[i]=='x') continue;

        int num=t[i]-'0'-1;
        int a=i/3,b=i%3;
        int x=num/3,y=num%3;

        res=res+abs(a-x)+abs(b-y);
    }


    for(int i=0;i<=9;i++) t[i]=tmp[i];
    return res;
}
void A_star(int s,int pos)
{
    flag[s]=2; G[s]=0;
    path[s].from=-1;
    path[s].dir=-1;
    Q.push(Node(s,pos,0,H(s)));
    while(!Q.empty())
    {
        Node h=Q.front(); Q.pop(); flag[h.s]=2;
        getstr(h.s);
        if(h.s==0)
        {
            ans=1; int now=h.s;
            while(1)
            {
                if(path[now].from==-1) return;
                S.push(path[now].dir);
                now=path[now].from;
            }
        }

        int a=h.p/3,b=h.p%3;

        for(int i=0;i<4;i++)
        {
            int x=a+dir[i][0],y=b+dir[i][1];
            if(!g(x,y)) continue;
            int newpos=3*x+y;

            swap(t[h.p],t[newpos]);
            int news=getnum();
            swap(t[h.p],t[newpos]);

            if(flag[news]==0||(flag[news]==1&&h.g+1<G[news]))
            {
                flag[news]=1; G[news]=h.g+1;
                path[news].from=h.s;
                path[news].dir=i;
                Q.push(Node(news,newpos,h.g+1,H(news)));
            }
        }
    }
}
int main()
{
    c[0]=1; for(int i=1;i<=8;i++) c[i]=c[i-1]*i;
    while(~scanf("%s",op))
    {
        read();
        int sum=0;
        for(int i=0;t[i];i++)
        {
            if(t[i]=='x') continue;
            for(int j=0;j<i;j++)
            {
                if(t[j]=='x') continue;
                if(t[i]<t[j]) sum++;
            }
        }

        if(sum%2==1)
        {
            printf("unsolvable\n");
            continue;
        }
        init();
        for(int i=0;i<9;i++)
            if(input[i]=='x'){A_star(getnum(),i); break; }

        while(!S.empty()){printf("%c",d[S.top()]); S.pop();}
        printf("\n");
    }
    return 0;
}

境界七、A*+哈希+曼哈顿距离+小顶堆?(HDU 1404ms,POJ 16ms)

  经过上面优化后,我们发现了A*也有些鸡肋的地方,因为需要每次找到所谓Open表中f最小的元素,如果每次排序,那么排序的工作量可以说是很大的,即使是快排,程序也不够快!这里,可以想到,由于需要动态的添加元素,动态的得到程序的最小值,我们可以维护一个小顶堆,这样的效果就是。每次取最小元素的时候,不是用一个n*Log(n)的排序,而是用log(n)的查找和调整堆,好,算法又向前迈进了一大步。
境界六加上最小堆优化,这个优化的效率改进是巨大的!!!!

#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#include<string>
#include<algorithm>using namespace std;
char input[1000],t[1000],op[5];int c[10];struct Node
{
    int s,p;
    int f,g,h;
    Node(){}
    Node(int S,int P,int G,int H)
    {
        s=S,p=P;
        g=G,h=H;
        f=g+h;
    }
    bool operator < (const Node &a) const {
        return f>a.f;
    }
};
struct Path
{
    int from,dir;
}path[400000];int flag[400000];int G[400000];
int dir[4][2] = { { -1,0 },{ 1,0 },{ 0,-1 },{ 0,1 } };string d = "udlr";

priority_queue<Node>Q;int ans;

stack<int>S;
int getnum()
{
    int res=0;
    for(int i=0;t[i];i++)
        for(int j=i+1;t[j];j++)
            if(t[j]<t[i]) res=res+c[8-i];
    return res;
}
void getstr(int val)
{
    int tmp[10],flag[10];
    memset(flag,0,sizeof flag);
    for(int i=0;i<9;i++) tmp[i]=val/c[8-i],val=val%c[8-i];
    for(int i=0;i<9;i++)
    {
        int num=0;
        for(int j=0;j<9;j++)
        {
            if(flag[j]==0) num++;
            if(num==tmp[i]+1)
            {
                t[i]=j+'0'+1; if(t[i]=='9') t[i]='x';
                flag[j]=1;break;
            }
        }
    }
}
bool g(int a, int b)
{
    if (a>=0 && a<=2 && b>=0 && b<=2) return 1;
    return 0;
}
void read()
{
    t[0]=op[0];
    for(int i=1;i<=8;i++) {scanf("%s",op); t[i]=op[0];}
    for(int i=0;i<=9;i++) input[i]=t[i];
}
void init()
{
    memset(G,-1,sizeof G);
    memset(flag,0,sizeof flag);
    while(!Q.empty()) Q.pop();
}
int H(int val)
{
    char tmp[10];
    for(int i=0;i<=9;i++) tmp[i]=t[i];

    getstr(val);

    int res=0;

    for(int i=0;i<9;i++)
    {
        if(t[i]=='x') continue;

        int num=t[i]-'0'-1;
        int a=i/3,b=i%3;
        int x=num/3,y=num%3;

        res=res+abs(a-x)+abs(b-y);
    }


    for(int i=0;i<=9;i++) t[i]=tmp[i];
    return res;
}
void A_star(int s,int pos)
{
    flag[s]=2; G[s]=0;
    path[s].from=-1;
    path[s].dir=-1;
    Q.push(Node(s,pos,0,H(s)));
    while(!Q.empty())
    {
        Node h=Q.top(); Q.pop(); flag[h.s]=2;
        getstr(h.s);
        if(h.s==0)
        {
            ans=1; int now=h.s;
            while(1)
            {
                if(path[now].from==-1) return;
                S.push(path[now].dir);
                now=path[now].from;
            }
        }

        int a=h.p/3,b=h.p%3;

        for(int i=0;i<4;i++)
        {
            int x=a+dir[i][0],y=b+dir[i][1];
            if(!g(x,y)) continue;
            int newpos=3*x+y;

            swap(t[h.p],t[newpos]);
            int news=getnum();
            swap(t[h.p],t[newpos]);

            if(flag[news]==0||(flag[news]==1&&h.g+1<G[news]))
            {
                flag[news]=1; G[news]=h.g+1;
                path[news].from=h.s;
                path[news].dir=i;
                Q.push(Node(news,newpos,h.g+1,H(news)));
            }
        }
    }
}
int main()
{
    c[0]=1; for(int i=1;i<=8;i++) c[i]=c[i-1]*i;
    while(~scanf("%s",op))
    {
        read();
        int sum=0;
        for(int i=0;t[i];i++)
        {
            if(t[i]=='x') continue;
            for(int j=0;j<i;j++)
            {
                if(t[j]=='x') continue;
                if(t[i]<t[j]) sum++;
            }
        }

        if(sum%2==1)
        {
            printf("unsolvable\n");
            continue;
        }
        init();
        for(int i=0;i<9;i++)
            if(input[i]=='x'){A_star(getnum(),i); break; }

        while(!S.empty()){printf("%c",d[S.top()]); S.pop();}
        printf("\n");
    }
    return 0;
}

境界八、IDA*+曼哈顿距离(HDU 2106ms,POJ 79ms)

  IDA*即迭代加深的A*搜索,实现代码是最简练的,无须状态判重,无需估价排序。那么就用不到哈希表,堆上也不必应用,空间需求变的超级少。效率上,应用了曼哈顿距离。同时可以根据深度和h值,在找最优解的时候,对超过目前最优解的地方进行剪枝,这可以导致搜索深度的急剧减少,所以,这,是一个致命的剪枝!因此,IDA*大部分时候比A*还要快,可以说是A*的一个优化版本!
采用迭代深搜的思想,利用A*估价函数剪枝。
第一次允许搜索到的深度为limit,如果有解,直接输出。
在最大值深度为limit的情况下,如果无解,则将limit++,继续从头开始搜索。直到搜到解。
剪枝:if(deep+h(s)>limit) return; deep表示这一次搜索达到此状态的操作次数,h(s)为估价函数。
IDA*策略占用空间极小,一般情况下效率会比A*高。
至于为什么我的IDA*比A*慢…..估计我这种写法常数太大了吧……

#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#include<string>
#include<algorithm>using namespace std;
char input[1000],t[1000],op[5];int c[10];int dir[4][2] = { { -1,0 },{ 1,0 },{ 0,-1 },{ 0,1 } };string d = "udlr";int flag,limit,length;int path[1000];
int getnum()
{
    int res=0;
    for(int i=0;t[i];i++)
        for(int j=i+1;t[j];j++)
            if(t[j]<t[i]) res=res+c[8-i];
    return res;
}
void getstr(int val)
{
    int tmp[10],flag[10];
    memset(flag,0,sizeof flag);
    for(int i=0;i<9;i++) tmp[i]=val/c[8-i],val=val%c[8-i];
    for(int i=0;i<9;i++)
    {
        int num=0;
        for(int j=0;j<9;j++)
        {
            if(flag[j]==0) num++;
            if(num==tmp[i]+1)
            {
                t[i]=j+'0'+1; if(t[i]=='9') t[i]='x';
                flag[j]=1;break;
            }
        }
    }
}
bool g(int a, int b)
{
    if (a>=0 && a<=2 && b>=0 && b<=2) return 1;
    return 0;
}
void read()
{
    t[0]=op[0];
    for(int i=1;i<=8;i++) {scanf("%s",op); t[i]=op[0];}
    for(int i=0;i<=9;i++) input[i]=t[i];
}
int H(int val)
{
    char tmp[10];
    for(int i=0;i<=9;i++) tmp[i]=t[i];
    getstr(val); int res=0;

    for(int i=0;i<9;i++)
    {
        if(t[i]=='x') continue;

        int num=t[i]-'0'-1;
        int a=i/3,b=i%3;
        int x=num/3,y=num%3;
        res=res+abs(a-x)+abs(b-y);
    }

    for(int i=0;i<=9;i++) t[i]=tmp[i];
    return res;
}
void dfs(int pos,int s,int deep,int pre_dir)
{
    if(s==0)
    {
        length=deep;
        flag=1;return;
    }
    if(deep+H(s)>limit) return;

    char u[10]; getstr(s);
    for(int i=0;i<=9;i++) u[i]=t[i];

    int a=pos/3,b=pos%3;
    for(int i=0;i<4;i++)
    {
        if(pre_dir==0&&i==1) continue;
        if(pre_dir==1&&i==0) continue;
        if(pre_dir==2&&i==3) continue;
        if(pre_dir==3&&i==2) continue;

        int x=a+dir[i][0],y=b+dir[i][1];
        if(!g(x,y)) continue;

        int new_pos=3*x+y;

        swap(u[pos],u[new_pos]); for(int i=0;i<=9;i++) t[i]=u[i];
        int new_s=getnum(); path[deep]=i;
        dfs(new_pos,new_s,deep+1,i); if(flag) return;
        swap(u[pos],u[new_pos]);
    }

}
int main()
{
    c[0]=1; for(int i=1;i<=8;i++) c[i]=c[i-1]*i;
    while(~scanf("%s",op))
    {
        read();

        int sum=0;
        for(int i=0;t[i];i++)
        {
            if(t[i]=='x') continue;
            for(int j=0;j<i;j++)
            {
                if(t[j]=='x') continue;
                if(t[i]<t[j]) sum++;
            }
        }

        if(sum%2==1) { printf("unsolvable\n"); continue; }

        for(int i=0;i<9;i++)
        {
            if(input[i]!='x') continue;
            limit=H(getnum()); flag=0;
            while(!flag)
            {
                for(int i=0;i<=9;i++) t[i]=input[i];

                int ggg=getnum();
                dfs(i,ggg,0,-1);

                if(flag) break;
                limit++;
            }

            for(int i=0;i<limit;i++) printf("%c",d[path[i]]);
            printf("\n");
        }
    }
    return 0;
}

摘自:
http://blog.csdn.net/ouxijv/article/details/7203027

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值