poj 1077 A* IDA* 八数码 康托展开

题意:3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘上还有一个空格,与空格相邻的棋子可以移到空格中。给出初始状态和目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动方法。

poj1077 hdu1043 zoj1217 uva652  sgu139 (15数码)

首先,将空格看成数字9,对于任意的布局S,定义perm(S)为从上到下从左到右的一个排列,i的初始位置为(x,y) 目标位置为(x',y'),定义距离dist(i)=|x'-x| + |y'-y|,

每移动一个棋子,dist(9)=dist(9)±1,dist(9)的奇偶性发生了改变,又因为交换两个元素,perm(S)的逆序数的奇偶性改变,所以perm(S) + dist(9) 奇偶性不变,始终为偶数。

若perm(S) + dist(9)为奇数,则无解。

证明:定义t(i)为 A的逆序数。考虑交换相邻元素 Ai 和 Aj   (i+1=j),若 Ai < Aj,交换后t(i) = t(i)+1, t(j)不变,若 Ai > Aj,交换后t(i) 不变,t(j) = t(j) - 1.所以奇偶性改变。

对于不相邻的元素 Ai 和 Aj   (i+k=j),    A依次和Ai+1、Ai+2、...Aj-1交换,一共交换了k-1次,然后和 A交换1次, A再依次和Aj-1、Aj-2...交换,也是k-1次,所以奇偶性一共改变了 2*(k-1) + 1 次,奇偶性改变。

 

 

A*启发函数h()为空格以外的所有棋子到目标位置的dist()之和。

状态的表示要用到康托展开,如 {1,2,3} 按从小到大排列一共6个:123 132 213 231 312 321分别对应数字1、2、3、4、5、6.

本题用1 ~ 9!( 362880 )就可以表示所有排列,若用十进制表示则需要 876543210 个 .

 

 

 

const int PermSize = 9;
int fac[] = { 0, 1, 2, 6, 24, 120,720, 5040, 40320, 362880 };
int Cantor(int buf[]) {//由排列得到序数
    int res = 1;//序数从1开始
    FOR(i, 0, PermSize-1) {
        int cnt = 0;
        FOR(j,i+1,PermSize)
            if(buf[i]>buf[j])
                cnt++;
        res += cnt * fac[PermSize - i - 1];
    }
    return res ;
}

int Cantor_2(int n, int num, int ans[]){//1~n的全排列   第num个
    int c, cnt, space, vis[15]={0};
    int pos=0; num--;  //序数从1开始
    FOD(i,n-1,0){
        c=num/fac[i]; num=num%fac[i]; cnt=0;
        FOE(j,1,n) {
            if(vis[j]) continue;
            if(cnt++ == c){
                vis[j]=1;
                ans[pos++]=j-1;
                if(j-1 == 8) space = pos - 1;
                break;
            }
        }
    }
    FOE(i,1,n) if(!vis[i]) {
        ans[pos++]=i-1;
        if(i-1 == 8) space = pos - 1;
        break;
    }
    return space;
}

//int dir[] = {-1,  3,  1, -3 };//Left down Right up
char di[] = {'l','d','r','u'};

const int M = 362895;
int a[9], space;
int g[M], f[M], h[M], ps[M], p[M];

int nex[9][4]={
    {-1, 3, 1,-1},
    { 0, 4, 2,-1},
    { 1, 5,-1,-1},
    {-1, 6, 4, 0},
    { 3, 7, 5, 1},
    { 4, 8,-1, 2},
    {-1,-1, 7, 3},
    { 6,-1, 8, 4},
    { 7,-1,-1, 5}
};

/*
bool is_ans(){  // 找到解
    FOR(i,0,9) if(a[i]!=i) return false;
    return true;
}
*/

void Print(int t){//输出解
    if(p[t]==-1)return ;
    Print(ps[t]);
    printf("%c",di[p[t]]);
}

int H(){    //估价函数
    int h=0;
    FOR(i,0,9) if(a[i]!=8)
        h += abs(i/3-a[i]/3) + abs(i%3-a[i]%3);
    return h;
}

struct cmp{
    bool operator()(int a,int b){
        return f[a] > f[b];
    }
};
priority_queue<int,vector<int>,cmp> q;//优先级队列

void solve(){
    int t = Cantor(a); g[t] = 0; h[t] = H(); p[t] = -1;
    f[t] = g[t] + h[t];
    while(!q.empty())q.pop();    q.push(t);
    while(!q.empty()){
        t = q.top(); q.pop();
        if(h[t] == 0){ Print(t); printf("\n"); return ; }
        space = Cantor_2(9, t, a);//将状态存入数组,同时记录空格的位置

        FOR(d,0,4){
            int next = nex[space][d];
            if(next == -1) continue;
            swap(a[space], a[next]);

            int t1 = Cantor(a);//新状态
            if(g[t1]==-1 || g[t1]>g[t]+1) {
                g[t1] = g[t] + 1;
                if(h[t1] == -1) h[t1] = H();
                f[t1] = g[t1] + h[t1];
                p[t1] = d; ps[t1] = t;
                q.push(t1);
            }
            if(h[t1] == 0){ Print(t1); printf("\n"); return ; }

            swap(a[space],a[next]);
        }
    }
}

bool readdata(){    //读入数据
    memset(g,-1,sizeof g);
    memset(h,-1,sizeof h);

    char ch;
    int i=0;
    while(i<9 && cin>>ch){
        if(ch=='x') {a[i]=8; space=i++;}
        else a[i++]=ch-'1';
    }
    if(i >= 9) return true;
    return false;
}

bool no_answer(){   //无解
    int s = 2-space/3 + 2-space%3;
    FOR(i,1,9) FOR(j,0,i) if(a[i]>a[j]) s++;
    if(s&1) return true;
    return false;
}

int main(){
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    #endif

    while(readdata()){
        if( no_answer() ) printf("%s\n","unsolvable");
        else solve();
    }
    return 0;
}

 

 

 

 

IDA*:每次控制搜索的深度limit,若找不到解,则limit++。

 

 

 

int dir[] = {-1,  3,  1, -3 };//Left down Right up
char di[] = {'l','d','r','u'};

int a[9], space;
int path[362895], length, limit;

int nex[9][4]={
    {-1, 3, 1,-1},
    { 0, 4, 2,-1},
    { 1, 5,-1,-1},
    {-1, 6, 4, 0},
    { 3, 7, 5, 1},
    { 4, 8,-1, 2},
    {-1,-1, 7, 3},
    { 6,-1, 8, 4},
    { 7,-1,-1, 5}
};

bool readdata(){    //读入数据
    char ch;
    int i=0;
    while(i<9 && cin>>ch ){
        //scanf("%c",&ch); while((ch<'1'||ch>'8') && ch!='x') scanf("%c",&ch);
                                     //cout<<i<<' '<<ch<<' ';
        if(ch=='x') { space=i; a[i++]=8;}
        else a[i++]=ch-'1';             //cout<<space<<endl;
    }
    if(i)return true;
    return false;
}

bool no_answer(){   //无解
    int s = 2-space/3 + 2-space%3;                  //cout<<s<<' '<<space<<' '<<endl;
    FOR(i,1,9)
        FOR(j,0,i)
            if(a[i]>a[j]) s++;
    if(s&1) return true;
    return false;
}

int h(){    //估价函数
    int h=0;
    FOR(i,0,9)
        if(a[i]!=8)
            h += abs(i/3-a[i]/3) + abs(i%3-a[i]%3);
    return h;
}

bool is_ans(){  // 找到解
    FOR(i,0,9) if(a[i]!=i) return false;
    return true;
}

bool IDA_star(int len, int space){
    if(len == limit){
        length = len;
        if(is_ans()) return true;
        return false;
    }

    FOR(d,0,4){
        int next=nex[space][d];
        if(next == -1) continue;
        if(len && abs(path[len-1] - d) == 2 ) continue;//相反方向

        swap(a[space], a[next]);
        path[len] = d;

        if(len + h() <= limit && IDA_star(len+1, next)) return true;
        swap(a[space],a[next]);
    }
    return false;
}

int main(){
    while(readdata()){
        if(no_answer()) {printf("%s\n","unsolvable"); continue;}

        limit = h();
        while(!IDA_star(0, space)) limit++;

        FOR(i,0,length)  printf("%c",di[path[i]]);
        printf("\n");
    }

    return 0;
}

 

 

 

 

posted @ 2013-05-01 13:39  心向往之  阅读(319)  评论(0编辑  收藏  举报