八数码问题

          八数码问题

/*8数码问题,即在一个3×3的矩阵中有8个数(18)和一个空格,从一个状态转换到另一个状态,

每次只能移动与空格相邻的一个数字到空格当中

 

AOJ-417-8数码

 

http://icpc.ahu.edu.cn/OJ/Problem.aspx?id=417

 

这题是求转化的最少步数,可用BFS解决,共有9!=362880种情况,

关键是如何标记已经访问过的状态,保证每次搜索得到的状态都是最小的步数,

这里可将字符串转化成对应的整数来处理,可用康托展开来节省存储空间

 

康托展开: X=an*(n-1)!+an-1*(n-2)!+...+ai*(i-1)!+...+a2*1!+a1*0! 

ai为在当前未出现的数字中是排在第几个(0<=ai<i

例如3 5 7 4 1 2 9 6 8 展开为

X=2*8!+3*7!+4*6!+2*5!+0*4!+0*3!+2*2!+0*1!+0*0!=98884

 

*/

#include "iostream"
#include<string.h>  
using namespace std; 
int a[363000][9];  //共有9!=362880种情况,用char存储节省空间  
int goal[9];  
int visit[363000];  
int dis[363000];  
int dir[4][2]={{-1,0},{1,0},{0,1},{0,-1}};  
int step[363000];  //从上一步来的方向  
int pre[363000];  //从上一步来的状态    
char name[4][10]={"upper ","down ","right ","left "};  
int c[9]={1,1,2,6,24,120,720,5040,40320};  //  i! 
int find(int str[9]) //将字符串转换成一个整数  
{  
     int i,j,k;  
     int f[10];  
     int sum=0;  
     memset(f,0,sizeof(f));  
     for(i=0;i<9;i++)    //康托展开式  9个数字相加 所以9次循环 
     {  
         k=0;  
         for(j=0;j<8;j++) {  //最高是8! 所以8次循环 
         if(j<str[i]&&!f[j])  
         k++; 
  }
         f[str[i]]=1;  
         sum+=k*c[8-i];  
     }  
     return sum;  
}  
int bfs()  
{  
    int i,j,t,flag;  
    int head,tail;  
    int x,y,z;  
    int nx,ny,nz;  
    int p,q,temp;  
    string s="";  
    memset(dis,0,sizeof(dis));  //到每种状态的做小步数  
    memset(visit,0,sizeof(visit)); //标记过的点不能重复走  
    t=find(a[0]);  
    visit[t]=1;  
    head=0;  
    tail=1;  
     memset(step,0,sizeof(step)); 
 step[0]=pre[0]=-1;  
    while(head<tail)  
    {  
        flag=1;  
        for(i=0;i<9;i++)  
        if(a[head][i]!=goal[i])   //和目标状态相同即停止搜索  
        {  
            flag=0;  
            break;  
        }   
        
         if(flag)   //打印路劲  
        {  
          int h=head;
            temp=0;  
            while(head)  
            {  
                p=pre[head];  //前一步的状态  
                q=step[head];  //从上一步来的方向    for里面的i 
                
               s+=name[q];
                head=p;  
            }   
            cout<<s<<endl;  
            return dis[h];  //当前最短的次数   
        }  
        
        for(i=0;i<9;i++)  //找到0所在位置  
        if(a[head][i]==0)  
        {  
            x=i/3;  //x行y列 
            y=i%3;  
            z=i;    //找出原来空格的位置 存到z 
            break;  
        }  
        for(i=0;i<4;i++)  
        {  
            nx=x+dir[i][0];  
            ny=y+dir[i][1];  
            nz=nx*3+ny;  //重点记住  nz即为将赋予新的空格的位置 ,但是目前为非0 
            if(0<=nx&&nx<3&&0<=ny&&ny<3)  
            {  
                for(j=0;j<9;j++)  //枚举 1-9  进队列 
                a[tail][j]=a[head][j]; 
                //因为每次只交换2个,所以先把以前的复制给下一个,再进行交换 
                
                
                a[tail][z]=a[head][nz];  //做一次移动,即非0元素和0交换  
                a[tail][nz]=0;  
                t=find(a[tail]);  
                if(!visit[t])  //如果该状态已经走过,就不要进队了 
                {  
                    visit[t]=1;  
                    dis[tail]=dis[head]+1;  //有数组保存当前每一步的步数 
                    
                      pre[tail]=head; //从上一步来的状态  
                    //因为是队列 
//所以每次列入队列,并不是连续的 
                    
                    
                    step[tail]=i;  //从上一步来的方向  
                    tail++;  
                }  
            }  
        }  
        head++;  
    }  
    return -1;  
}  
int main()  
{  
    int i,ans;  
    for(i=0;i<9;i++)  
    cin>>a[0][i];
    for(i=0;i<9;i++)  
   cin>>goal[i];  
    ans=bfs();  
    if(ans==-1)  
   cout<<"Impossible\n";  
    else  
   cout<<ans;  
    return 0;   
}  


你可能感兴趣的:(八数码问题)