zoukankan      html  css  js  c++  java
  • PKU1077 八数码,双向广搜+康托展开

    http://poj.org/problem?id=1077

    思路并不难,只是调的过程中有些问题,懒得写,从高手那转来的

    双向广度优先搜索算法是对广度优先算法的一种扩展。广度优先算法从起始节点以广度优先的顺序不断扩展,

    直到遇到目的节点;而双向广度优先算法从两个方向以广度优先的顺序同时扩展,一个是从起始节点开始扩展,另

    一个是从目的节点扩展,直到一个扩展队列中出现另外一个队列中已经扩展的节点,也就相当于两个扩展方向出现了

    交点,那么可以认为我们找到了一条路径。双向广度优先算法相对于广度优先算法来说,由于采用了从两个跟开始扩展

    的方式,搜索树的深度得到了明显的减少,所以在算法的时间复杂度和空间复杂度上都有较大的优势!双向广度优先算

    法特别适合于给出了起始节点和目的节点,要求他们之间的最短路径的问题。另外需要说明的是,广度优先的顺序能够保证

    找到的路径就是最短路径!

             基于以上思想,我们给出双向广度优先算法编程的基本框架如下:

    数据结构:

    Queue q1, q2; //两个队列分别用于两个方向的扩展(注意在一般的广度优先算法中,只需要一个队列)

    int head[2], tail[2]; //两个队列的头指针和尾指针

    算法流程:

    一、主控函数:

    void solve()

    {

    1. 将起始节点放入队列q1,将目的节点放入队列q2

    2. 当 两个队列都未空时,作如下循环

              1) 如果队列q1里的未处理节点比q2中的少(即tail[0]-head[0] < tail[1]-head[1]),则扩展(expand())队列q1

              2) 否则扩展(expand())队列q2 (即tail[0]-head[0] >= tail[1]-head[1]时)

    3. 如果队列q1未空,循环扩展(expand())q1直到为空

    4. 如果队列q2未空,循环扩展(expand())q2知道为空

    }

    二、扩展函数:

    int expand(i) //其中i为队列的编号(表示q0或者q1)

    {

              取队列qi的头结点H

              对头节点H的每一个相邻节点adj,作如下循环

                    1 如果adj已经在队列qi之前的某个位置出现,则抛弃节点adj

                    2 如果adj在队列qi中不存在[函数 isduplicate(i)]

                          1) 将adj放入队列qi

                          2)    如果adj 在队列(q(1-i)),也就是另外一个队列中出现[函数 isintersect()]

                                          输出 找到路径 

    }

    三、判断新节点是否在同一个队列中重复的函数

    int isduplicate(i, j) //i为队列编号,j为当前节点在队列中的指针

    {

                遍历队列,判断是否存在【线性遍历的时间复杂度为O(N),如果用HashTable优化,时间复杂度可以降到O(1)]

    }

    四、判断当前扩展出的节点是否在另外一个队列出现,也就是判断相交的函数:

    int isintersect(i,j) //i为队列编号,j为当前节点在队列中的指针

    {

              遍历队列,判断是否存在【线性遍历的时间复杂度为O(N),如果用HashTable优化,时间复杂度可以降到O(1)]

    }

    Source Code
    
    Problem: 1077        User: 297752873
    Memory: 10984K        Time: 16MS
    Language: C        Result: Accepted
    Source Code
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #define QueueSize 400000
    int f[8]={1,2,6,24,120,720,5040,40320};
    int dir[4]={-3,3,-1,1};
    int visited[1000000],ans[1000000];
    int found;
    struct eight
    {
        char str[10];//目前的状态 
        int k;//空格的位置 
        int fa;//父节点,便于输出 
        int fx;//从上个结点过来的方向0上,1下,2左,3右 
    }s[1000000],b;//所有的标志,以及存储刚开始的状态
     
    int get(char str[]) //计算全排列哈希值 
    {  
        int i, j;  
        int ret = 0, cnt;  
        for(i = 1; i < 9; ++i)  
        {  
            cnt = 0;  
            for(j = 0; j < i; ++j)  
                if(str[j] > str[i])  
                    cnt++;  
            ret += cnt*f[i-1];  
        }  
        return ret;  
    }  
    
    typedef struct
    {
        int front;
        int rear;
        int count;
        int data[1000000];
    }CirQueue;
    CirQueue qs,qe;//使用队列,队列里保存的是各状态的下标 
    
    void InitQueue(CirQueue *Q)//建队 
    {
        Q->front=Q->rear=0;
        Q->count=0;
    }
    int QueueEmpty(CirQueue *Q)//判队是否空 
    {
        return Q->count==0;
    }
    void EnQueue(CirQueue *Q,int x)//入队 
    { 
        
            Q->count++;
            Q->data[Q->rear]=x;
            Q->rear=(Q->rear+1)%QueueSize;
    
    }
    int DeQueue(CirQueue *Q)//出队,并删除队首元素 
    {
        int temp;
            temp=Q->data[Q->front];
            Q->count--;
            Q->front=(Q->front+1)%QueueSize;
            return temp;
    }
    
    void print(int a,int b,int flag,int fx)//a是正向左边的点,b是反向右边的点,不相连 ,flag时是正向搜到的 
    {
         int top=0;
         if(flag)//正向搜索时就将要输出的那个方向放到栈里 
         {
             ans[top++]=fx;
             while(a!=s[a].fa)
             {
              ans[top++]=s[a].fx;
              a=s[a].fa;
             }
             while(top--)
             {
                switch(ans[top])
                {
                  case 0:printf("u");break;
                  case 1:printf("d");break;
                  case 2:printf("l");break;
                  case 3:printf("r");break;
                  default:break;
                }
             }
             while(b!=s[b].fa)
             {
               switch(s[b].fx)
                {
                  case 0:printf("d");break;
                  case 1:printf("u");break;
                  case 2:printf("r");break;
                  case 3:printf("l");break;
                  default:break;
                }
                b=s[b].fa;
             }
         }
         else {//方向搜索时就将下一个方向反向直接输出 
             while(a!=s[a].fa)
             {
              ans[top++]=s[a].fx;
              a=s[a].fa;
             }
             while(top--)
             {
                switch(ans[top])
                {
                  case 0:printf("u");break;
                  case 1:printf("d");break;
                  case 2:printf("l");break;
                  case 3:printf("r");break;
                  default:break;
                }
             }
             switch(fx)
                {
                  case 0:printf("d---");break;
                  case 1:printf("u");break;
                  case 2:printf("r");break;
                  case 3:printf("l");break;
                  default:break;
                }
             while(b!=s[b].fa)
             {
               switch(s[b].fx)
                {
                  case 0:printf("d");break;
                  case 1:printf("u");break;
                  case 2:printf("r");break;
                  case 3:printf("l");break;
                  default:break;
                }
                b=s[b].fa;
             }
         }
         printf("
    ");
    }
    void BFS_expand(CirQueue *Q,int flag)
    {  
          int ss=DeQueue(Q),i,x,k;//ss是队首元素 
          char a[10],temp;
    
          for(i=0;i<4;i++)
         {
                 if(i==0&&s[ss].k<3)continue;
                 if(i==1&&s[ss].k>=6)continue;
                 if(i==2&&s[ss].k%3==0)continue;
                 if(i==3&&s[ss].k%3==2)continue; //因为这个本应二维存储的图案,要处理好边缘问题,这里纠结了好久
                 k=s[ss].k+dir[i];//方向 
                 if(k<0||k>8) continue;//如果超出范围,就找寻下个结点 
                 strcpy(a,s[ss].str);
                 temp=a[k];a[k]=a[s[ss].k];a[s[ss].k]=temp;  // 获取子节点的状态
                 x=get(a);//子节点的哈希值 
                 if(flag)   // 在正向队列中判断
                 {
                          if(visited[x]!=1)// 没在正向队列出现过
                        {
                               if(visited[x]==2)  // 该状态在反向队列中出现过
                              {
                                     print(ss,x,flag,i);
                                     found=1;
                                     return ;
                               }
                                visited[x]=1;   // 标记为在在正向队列中
                                s[x].k=k;
                                s[x].fx=i; 
                                strcpy(s[x].str,a);
                                s[x].fa=ss;
                                EnQueue(Q,x);// 入队
                        }
                 }
                 else    // 在反向队列中判断
                 {
                          if (visited[x]!=2)// 没在反向队列出现过
                        {
                               if(visited[x]==1)  // 该状态在正向队列中出现过
                              {
                                     print(x,ss,flag,i);
                                     found=1;
                                     return ;
                               }
                                visited[x]=2;   // 标记为在在反向队列中
                                s[x].k=k;
                                s[x].fx=i; 
                                strcpy(s[x].str,a);
                                s[x].fa=ss;
                                EnQueue(Q,x);// 入队
                        }
                 } 
         }   
    }
    
    void DBFS()//双广 
    {
           int x=get(b.str),y=get("123456780"); //x是正向,输入的,y则是目的状态,固定的 
           if(strcmp(b.str,"123456780")==0)
           {
              printf("lr
    ");
              found=1;
              return ;
           }
           memset(visited,0,sizeof(visited));  // 判重数组,初始化为0 
           InitQueue(&qs);
           InitQueue(&qe);
           //======正向扩展的状态标记为1,反向扩展标记为2
           visited[x]=1;   // 初始状态标记为1
           visited[y]=2;   // 结束状态标记为2
           s[x].k=b.k;strcpy(s[x].str,b.str);s[x].fa=x;
           s[y].k=8;strcpy(s[y].str,"123456780");s[y].fa=y;
           EnQueue(&qs,x);// 初始状态入正向队列
           EnQueue(&qe,y);// 结束状态入反向队列
           while(!QueueEmpty(&qs)&&!QueueEmpty(&qe))
           {
                  if(qs.count>qe.count)//队中结点少的先行,可提高效率
                         BFS_expand(&qe,0); // 在正向队列中搜索
                  else   BFS_expand(&qs,1); 
                  if(found)  // 搜索结束 
                         return ;
           }
           while(QueueEmpty(&qs)==0)
           {
                BFS_expand(&qs,1);
                if(found)return;
           }
            while(0==QueueEmpty(&qe))
           {
                BFS_expand(&qe,0);
                if(found)return;
           }
    }
    
    int main()
    {
        int i,m,j;
        char a[100];
        gets(a);
        j=0;
        for(i=0;i<strlen(a);i++)
        {
           if(a[i]==' ')continue;
           if(a[i]=='x')
           {
              a[j]='0';
              m=j;
              j++;
           }else a[j++]=a[i];
        }
           a[j]='';
        strcpy(b.str,a);
        b.k=m;
        found=0;//标志变量初始化 
        //printf("m=%d %s
    ",b.k,b.str);
        DBFS();
           if(found==0)
             printf("unsolvable
    ");
        
        //system("pause");
        return 0;
    }
    View Code
  • 相关阅读:
    学生数据增删改查--顺序表
    应用3+2mvc第一次作业
    双色球随机选【代码】
    字符串穷举
    使用nuget发布自己的包
    VS CODE中配置JAVA格式化细节
    反射的理解(含一点xml)
    UdpClient实现udp消息收发
    c#背包问题代码
    利用TcpClient,简单的tcp消息收发
  • 原文地址:https://www.cnblogs.com/huzhenbo113/p/3231224.html
Copyright © 2011-2022 走看看