八数码问题多种解法比较(poj1077宽搜,双向宽搜,A*,IDA*+扩展)(持续更新)

有个小的优化,逆序数(除去x)的奇偶性相同,那就一定可以达到,不同一定不可以达到。
推荐: 八数码的八种境界—写的不错
需要指出上文中说(境界2)单向宽搜+哈希会超时,但事实证明能ac过。(4608KB,688ms)
(hdu1043上是同时多组输入,POJ是单组输入。
两个限时不同。HDU 上反向搜索,把所有情况打表出来。
POJ上正向搜索。)
hash需要用到康托展开(—-维基百科)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define msc(X) memset(X,-1,sizeof(X))
#define ms(X) memset(X,0,sizeof(X))
typedef long long LL;
using namespace std;
const int MAXN=400000;
int fac[]={1,1,2,6,24,120,720,5040,40320,362880};//康托展开
//         0!1!2!3! 4! 5! 6!  7!    8!      9!
bool vs[MAXN];
typedef struct _Node
{
    int s[9];
    int loc;//0的位置,把x当成0
    int sts;//康托展开的hash值
    string path;//路径
}Node;
string path;
int aim=46234;//12345780对应的hash值
int move[4][2]={{-1,0},{1,0},{0,-1},{0,1}};//u,d,l,r
char indexs[5]="udlr";
Node ncur;
int cantor(int s[])
{
    int sum=0;
    for(int i=0;i<9;i++)
    {
        int tmp=0;
        for(int j=i+1;j<9;j++)
            if(s[j]9-i-1]);
    }
    return sum+1;
}
bool BFS(void)
{
    queue q;
    while(!q.empty()) q.pop();
    ms(vs);
    Node cur,next;
    q.push(ncur);
    while(!q.empty()){
        cur=q.front();
        q.pop();
        for(int i=0;i<4;i++)
        {
            int tx=cur.loc/3+move[i][0],
            ty=cur.loc%3+move[i][1];
            if(tx<0||tx>2||ty<0||ty>2) continue;
            next=cur;
            next.loc=tx*3+ty;
            next.s[cur.loc]=cur.s[next.loc];
            next.s[next.loc]=0;
            next.sts=cantor(next.s);
            if(!vs[next.sts]){
                vs[next.sts]=true;
                next.path+=indexs[i];
                if(next.sts==aim) {
                    path=next.path;
                    return true;
                }
                q.push(next);
            }
        }
    }
    return false;
}
int main(int argc, char const *argv[])
{
    char ch;
    for(int i=0;i<9;i++)
    {
        scanf(" %c",&ch);
        if(ch=='x'){
            ncur.s[i]=0;
            ncur.loc=i;
        }
        else ncur.s[i]=ch-'0';  
    }
    ncur.sts=cantor(ncur.s);
    if(ncur.sts==aim) putchar('\n');
    else if(BFS()){
        cout<else puts("unsolvable");
    return 0;
}
/*
 1 2 3 x 4 6 7 5 8
*/

我的双向宽搜超时了,依然在debug中,补。。。。

A*算法
f (n)=g(n) +h(n) ,且满足以下限制
g(n)是从s0到n的真实步数(未必是最优的),因此:
g(n)>0 且g(n)>=g*(n)
h(n)是从n到目标的估计步数。估计总是过于乐观的,即 h(n)<= h*(n)
且h(n)相容,则A算法转变为A* 算法。A*正确性证明略。

h(n)的相容:
如果h函数对任意状态s1和s2还满足:
h(s1) <= h(s2) + c(s1,s2)

启发式h函数的距离与所允许的移动方式相匹配:
在正方形网格中,允许向4邻域的移动,使用曼哈顿距离(L1)。
在正方形网格中,允许向8邻域的移动,使用对角线距离(L∞)。
在正方形网格中,允许任何方向的移动,欧几里得距离(L2)可能适合,但也可能不适合。如果用A* 在网格上寻找路径,但你又不允许在网格上移动,你可能要考虑用其它形式表现该地图。
在六边形网格中,允许6个方向的移动,使用适合于六边形网格的曼哈顿距离。

在这道题中启发式函数有两种选择,一种是不在应该在的位置上数的个数,一种是不在应该在的位置的数字到应该在的位置的哈弗曼距离和,(均不计x)
显然后者优于前者。
A*的还没写。。。。。。

IDA*
即迭代加深搜索与A*结合,但其实写起来比A *好写。迭代加深搜索核心思想就是用了一个变量mLimit限制了搜索的深度,若成功则肯定是最优的,若不成功,则增加mLimit直到有解。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define mabs(X) ((X)>0?(X):(-(X)))
#define msc(X) memset(X,-1,sizeof(X))
#define ms(X) memset(X,0,sizeof(X))
typedef long long LL;
using namespace std;
bool Inv(const int *s)
{
    int iv=0;
    for(int i=0;i<9;i++)
        {
            if(s[i]==9) continue;
            for(int k=0;kif(s[k]==9) continue;
                if(s[k]>s[i]) iv++;
            }
        }
    return iv%2==0;
}
bool Isans(const int *s)
{
    for(int i=0;i<8;i++)
        if(s[i]+1!=s[i+1]) return false;
    return true;
}
struct _Node
{
    int loc;
    int s[9];
};
int h(const int *s)
{
    int cost=0;
    for(int i=0;i<9;i++)
    {
        if(s[i]==9) continue;
        int x=(s[i]-1)/3,y=(s[i]-1)%3;
        cost+=mabs(i/3-x)+mabs(i%3-y);
    }
    return cost;
}
struct _Node ncur;
int pathLimit;
char path[400000];
const int move[4][2]={{-1,0},{0,-1},{0,1},{1,0}};
char indexs[5]="ulrd";
bool IDAst(struct _Node cur,int len,int pre)
{
    if(len==pathLimit){
        if(Isans(cur.s)==1){
            path[len]='\0';
            puts(path);
            return true;
        }
        else return false;
    }
    struct _Node next;
    for(int i=0;i<4;i++)
    {
        if(pre+i==3) continue;//前一步和当前所选的步反方向,重要剪枝
        int tx=cur.loc/3+move[i][0],
            ty=cur.loc%3+move[i][1];
        if(tx<0||tx>2||ty<0||ty>2) continue;
        next=cur;
        next.loc=tx*3+ty;
        next.s[cur.loc]=cur.s[next.loc];
        next.s[next.loc]=9;
        path[len]=indexs[i];
        if(len+h(next.s)1,i))//重要剪枝
            return true;
    }
    return false;
}
int main(int argc, char const *argv[])
{
    char ch;
    for(int i=0;i<9;i++)
    {
        scanf(" %c",&ch);
        if(ch=='x') ncur.loc=i,ncur.s[i]=9;
        else ncur.s[i]=ch-'0';
    }
    if(!Inv(ncur.s)) puts("unsolvable");
    else{
        pathLimit=h(ncur.s);
        while(!IDAst(ncur,0,-1)) pathLimit++;     
    }
    return 0;
}
/*
 1 2 3 x 4 6 7 5 8
 2 3 4 1 5 x 7 6 8
*/

uva10181
题目说不超过45步,所以刚开始直接IDAstar搜索,但是在搜到30层左右的时候基本就跑不动了,所以多加了一个直接判断是否存在解的is_ok函数,居然过了,说明这道题的数据没有那种要到40步左右才能完成的。
如果空格左右移动它的逆序数是不变的,但是如果上下移动的话逆序数奇偶性改变,所以只需判断现在状态下的逆序数状态和空格所在行数的奇偶性是否相同,如果相同(目标状态的逆序数为0,空格所在行数为3,奇偶性不同)无解,不同必有解。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define msc(X) memset(X,-1,sizeof(X))
#define ms(X) memset(X,0,sizeof(X))
typedef long long LL;
using namespace std;
#define mabs(X) ((X)>0?(X):(-(X)))
int h(const int *s)
{
    int rt=0;
    for(int i=0;i<16;i++)
    {
        if(s[i]==0) continue;
        int x=(s[i]-1)/4,y=(s[i]-1)%4;
        rt+=mabs(x-i/4)+mabs(y-i%4);
    }
    return rt;
}
bool is_ok(const int *s)
{
    int num=0,l;
    for(int i=0;i<16;i++)
    {
        if(s[i]==0) l=i/4;
        else {
            for(int j=i+1;j<16;j++)
             if(s[j]&&s[i]>s[j])
                num++;
            }
    }
    return num%2!=l%2;
}
struct _Node
 {
    int num[17];
    int pos;
 }tcur;
int maxdep; 
char path[55];
const int movest[5][2]={{-1,0},{0,-1},{0,1},{1,0}};
const char Indexs[6]="ULRD";
bool IDAS(struct _Node cur,int d,int pre)
{
    if(d==maxdep){
        if(h(cur.num)==0) {
            path[d]='\0';
            puts(path);
            return true;
        }
        else return false;
    }
    struct _Node tmp;
    for(int i=0;i<4;i++)
    {
        if(i+pre==3) continue;
        int tx=cur.pos/4+movest[i][0],ty=cur.pos%4+movest[i][1];
        if(tx<0||tx>3||ty<0||ty>3) continue;
        tmp=cur;
        tmp.pos=tx*4+ty;
        tmp.num[cur.pos]=cur.num[tmp.pos];
        tmp.num[tmp.pos]=0;
        path[d]=Indexs[i];
        if(d+h(tmp.num)1,i))
            return true;
    }
    return false;
}
int main(int argc, char const *argv[])
{
    int n;
    scanf("%d",&n);
    while(n--){
        for(int i=0;i<16;i++)
        {
            scanf("%d",&tcur.num[i]);
            if(tcur.num[i]==0)
                tcur.pos=i;
        }
        if(!is_ok(tcur.num)) puts("This puzzle is not solvable.");
        else {
            maxdep=h(tcur.num);
            while(!IDAS(tcur,0,-1)) maxdep++; 
        }
    }
    return 0;
}
/*
2
2 3 4 0
1 5 7 8
9 6 10 12
13 14 11 15
13 1 2 4
5 0 3 7
9 6 10 12
15 8 11 14

1
13 1 2 4
5 0 3 7
9 6 10 12
15 8 14 11

1
1 2 3 4
5 6 7 8
9 10 11 12
13 14 0 15
*/

推荐,也是自己要补的题poj3131(立体八数码),hdu3567(加强点的八数码)

你可能感兴趣的:(acm之路,dfs/bfs)