• 双向广搜


      Eight   

       题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1043

       讲到双向广搜,那就不能不讲经典的八数码问题,有人说不做此题人生不完整 。

       所谓双向广搜,就是初始结点向目标结点和目标结点向初始结点同时扩展,直至在两个扩展方向上出现同一个结点,搜索结束。它适用的问题是,扩展结点较多,而目标结点又处在深沉,如果采用单纯的广搜解题,搜索量巨大,搜索速度慢是可想而知的,同时往往也会出现内存空间不够用的情况,这时双向广搜的作用就体现出来了。双向广搜对单纯的广搜进行了改良或改造,加入了一定的“智能因数”,使搜索能尽快接近目标结点,减少了在空间和时间上的复杂度。

       当在讲题前,不得不先给大家补充一点小知识,大家都知道搜索的题目其中难的一部分就是事物的状态,不仅多而且复杂,要怎么保存每时刻的状态,又容易进行状态判重呢,这里提到了一种好办法   ------康托展开(只能针对部分问题)

     

    康托展开

    康托展开式:

      X=an*(n-1)!+an-1*(n-2)!+...+ai*(i-1)!+...+a2*1!+a1*0!

     其中,a为整数,并且0<=ai<i(1<=i<=n)。

    例:

    问:1324是{1,2,3,4}排列数中第几个大的数?

    解:第一位是1小于1的数没有,是0个 0*3! 第二位是3小于3的数有1和2,但1已经在第一位了,所以只有一个数2 1*2! 。第三位是2小于2的数是1,但1在第一位,所以有0个数 0*1! ,所以比1324小的排列有0*3!+1*2!+0*1!=2个,1324是第三个大数。

    好吧,先看下代码实现:

    int factory[]={1,1,2,6,24,120,720,5040,40320,362880}; // 0..n的阶乘

    int Gethash(char eight[])

    {

           int k=0;

           for(int i=0;i<9;i++)    // 因为它有八位数(针对八数码问题)

           {

                  int t=0;

                  for(int j=i+1;j<9;j++)

                         if(eight[j]<eight[i])

                                t++;

                  k+=(t*factory[9-i-1]);

           }

           return k;   // 返回该数是第几大

    }

    好的,现在再来看看双向广搜模版:

    void TBFS()

    {

           bool found=false;

           memset(visited,0,sizeof(visited));  // 判重数组

           while(!Q1.empty())  Q1.pop();   // 正向队列

           while(!Q2.empty())  Q2.pop();  // 反向队列

           //======正向扩展的状态标记为1,反向扩展标记为2

           visited[s1.state]=1;   // 初始状态标记为1

           visited[s2.state]=2;   // 结束状态标记为2

           Q1.push(s1);  // 初始状态入正向队列

           Q2.push(s2);  // 结束状态入反向队列

           while(!Q1.empty() || !Q2.empty())

           {

                  if(!Q1.empty())

                         BFS_expand(Q1,true);  // 在正向队列中搜索

                  if(found)  // 搜索结束 

                         return ;

                  if(!Q2.empty())

                         BFS_expand(Q2,false);  // 在反向队列中搜索

                  if(found) // 搜索结束

                         return ;

           }

    }

    void BFS_expand(queue<Status> &Q,bool flag)

    {  

           s=Q.front();  // 从队列中得到头结点s

          Q.pop()

          for( 每个s 的子节点 t )

         {

                 t.state=Gethash(t.temp)  // 获取子节点的状态

                 if(flag)   // 在正向队列中判断

                 {

                          if (visited[t.state]!=1)// 没在正向队列出现过

                        {

                               if(visited[t.state]==2)  // 该状态在反向队列中出现过

                              {

                                     各种操作;

                                     found=true;

                                     return;

                               }

                                visited[t.state]=1;   // 标记为在在正向队列中

                                Q.push(t);  // 入队

                           }

                 }

                 else    // 在正向队列中判断

                 {

                          if (visited[t.state]!=2) // 没在反向队列出现过

                        {

                               if(visited[t.state]==1)  // 该状态在正向向队列中出现过

                               {

                                      各种操作;

                                      found=true;

                                      return;

                                }

                                 visited[t.state]=2;  // 标记为在反向队列中

                                 Q.push(t);  // 入队

                           }

                 }             

    }                     

    好的,现在开始说说八数码问题

    其实,Eight有一个很重要的判断,那就是逆序数的判断。如果i>j,并且ai<aj,那么定义(i,j)为一个逆序对,而对于一个状态排列中所含的逆序对的个数总和就是逆序数。而本题的逆序数的奇偶性的判断是至关重要的:

    如果x在同一行上面移动那么1~8的逆序数不变

    如果x在同一列上面移动,每次逆序数增加偶数个或者减少偶数个

    因为目标结点的状态的逆序数为0,为偶数,所以每次访问到的状态的逆序数也必须为偶数,保持奇偶性性质,否则就不必保存该状态。

    #include<iostream>

    #include<queue>

    using namespace std;

    #define N 10

    #define MAX 365000

    char visited[MAX];

    int father1[MAX];  // 保存正向搜索当前状态的父亲状态结点

    int father2[MAX];  // 保存反向搜索当前状态的父亲状态结点

    int move1[MAX];    // 正向搜索的方向保存

    int move2[MAX];   //  反向搜索的方向保存

    struct Status   // 结构

    {

           char eight[N];  // 八数码状态

           int space;     // x 位置

           int state;    // hash值,用于状态保存与判重 

    };

    queue<Status> Q1;  // 正向队列

    queue<Status> Q2;  // 反向队列

    Status s,s1,s2,t;

    bool found;  // 搜索成功标记

    int state;   // 正反搜索的相交状态

    int factory[]={1,1,2,6,24,120,720,5040,40320,362880};  // 0..n的阶乘

    int dir[4][2]={{-1,0},{1,0},{0,-1},{0,1}};

    int Gethash(char eight[])  // 康托展开(获取状态,用于判重)

    {

           int k=0;

           for(int i=0;i<9;i++)

           {

                  int t=0;

                  for(int j=i+1;j<9;j++)

                         if(eight[j]<eight[i])

                                t++;

                  k+=(t*factory[9-i-1]);

           }

           return k;

    }

    int ReverseOrder(char eight[])  // 求状态的逆序数

    {

           int i,j,num=0;

           for(i=0;i<9;i++)

           {

                  for(j=0;j<i;j++)

                  {

                         if(int(eight[i])==9)

                         {

                                break;

                         }

                         if(int(eight[j])==9)

                                continue;

                         if(int(eight[j])>int(eight[i]))

                                num++;

                  }

           }

           num=num%2;

           return num;

    }

    void BFS_expand(queue<Status> &Q,bool flag)  // 单向广度搜索

    {

           int k,x,y;

           s=Q.front();

           Q.pop();

           k=s.space;

           x=k/3;

           y=k%3;

           for(int i=0;i<4;i++)

           {

                  int xx=x+dir[i][0];

                  int yy=y+dir[i][1];

                  if(xx>=0 && xx<=2 && yy>=0 && yy<=2)

                  {

                         t=s;

                         t.space=xx*3+yy;   // 计算x位置

                         swap(t.eight[k],t.eight[t.space]);  // 交换两个数位置

                         t.state=Gethash(t.eight);

                         if(flag)  // 在正向队列中判断

                         {

                                if(visited[t.state]!=1 && ReverseOrder(t.eight)==0)  // 未在正向队列出现过并且满足奇偶性

                                {

                                       move1[t.state]=i;  // 保存正向搜索的方向

                                       father1[t.state]=s.state; // 保存正向搜索当前状态的父亲状态结点

                                       if(visited[t.state]==2)   //  当前状态在反向队列中出现过

                                       {

                                              state=t.state;  // 保存正反搜索中相撞的状态(及相交点)

                                              found=true;    // 搜索成功

                                              return;

                                       }

                                       visited[t.state]=1;   // 标记为在正向队列中

                                       Q.push(t);  // 入队

                                }

                         }

                         else  // 在反向队列中判断

                         {

                                if(visited[t.state]!=2 && ReverseOrder(t.eight)==0)   // 未在反向队列出现过并且满足奇偶性

                                {

                                       move2[t.state]=i;  // 保存反向搜索的方向

                                       father2[t.state]=s.state; // 保存反向搜索当前状态的父亲状态结点

                                       if(visited[t.state]==1)  //  当前状态在正向队列中出现过

                                       {

                                              state=t.state;  // 保存正反搜索中相撞的状态(及相交点)

                                              found=true;   // 搜索成功

                                              return;

                                       }

                                       visited[t.state]=2;  // 标记为在反向队列中

                                       Q.push(t);   // 入队

                                }

                         }

                  }

           }

           return ;

    }

    void TBFS()            // 双向搜索

    {

           memset(visited,0,sizeof(visited));

           while(!Q1.empty())

                  Q1.pop();

           while(!Q2.empty())

                  Q2.pop();

           visited[s1.state]=1;   // 初始状态

           father1[s1.state]=-1;

           visited[s2.state]=2;   // 目标状态

           father2[s2.state]=-1;

           Q1.push(s1);

           Q2.push(s2);

           while(!Q1.empty() || !Q2.empty())

           {

                  if(!Q1.empty())

                         BFS_expand(Q1,true);

                  if(found)

                         return ;

                  if(!Q2.empty())

                         BFS_expand(Q2,false);

                  if(found)

                         return ;

           }

    }

    void PrintPath1(int father[],int move[])   // 从相交状态向初始状态寻找路径

    {

           int n,u;

           char path[1000];

           n=1;

           path[0]=move[state];

           u=father[state];

           while(father[u]!=-1)

           {

                  path[n]=move[u];

                  n++;

                  u=father[u];

           }

           for(int i=n-1;i>=0;--i)

           {       

                  if(path[i] == 0)           

                         printf("u");       

                  else if(path[i] == 1)           

                         printf("d");       

                  else if(path[i] == 2)           

                         printf("l");       

                  else           

                         printf("r");   

           }

    }

    void PrintPath2(int father[],int move[])   // 从相交状态向目标状态寻找路径

    {

           int n,u;

           char path[1000];

           n=1;

           path[0]=move[state];

           u=father[state];

           while(father[u]!=-1)

           {

                  path[n]=move[u];

                  n++;

                  u=father[u];

           }

           for(int i=0;i<=n-1;i++)

           {       

                  if(path[i] == 0)           

                         printf("d");       

                  else if(path[i] == 1)           

                         printf("u");       

                  else if(path[i] == 2)           

                         printf("r");       

                  else           

                         printf("l");   

           }

    }

    int main()

    {

           int i;

           char c;   

           while(scanf(" %c",&c)!=EOF)

           {

                  if(c=='x')

                  {

                         s1.eight[0]=9;

                         s1.space=0;

                  }

                  else

                         s1.eight[0]=c-'0';

                  for(i=1;i<9;i++)

                  {

                         scanf(" %c",&c);

                         if(c=='x')

                         {

                                s1.eight[i]=9;

                                s1.space=i;

                         }

                         else

                                s1.eight[i]=c-'0';

                  }

                  s1.state=Gethash(s1.eight);

                  for(int i=0;i<9;i++)

                         s2.eight[i]=i+1;

                  s2.space=8;

                  s2.state=Gethash(s2.eight);

                  if(ReverseOrder(s1.eight)==1)

                  {

                         cout<<"unsolvable"<<endl;

                         continue;

                  }

                  found=false;

                  TBFS();

                  if(found)   // 搜索成功

                  {

                         PrintPath1(father1,move1);

                         PrintPath2(father2,move2);

                  }

                  else

                         cout<<"unsolvable"<<endl;

                  cout<<endl;

           }

           return 0;

    }

  • 相关阅读:
    小笔记——a标签触发file
    错误笔记——MVC自带验证
    错误笔记——表达式树不能包含动态操作(aspx)的解决
    囫囵吞枣——Bootstrap
    闪回技术
    RAC架构中各种日志所在路径
    行迁移与行链接
    BBED制作空块并用rman恢复(修改坏块前一定要做rman备份!!!)
    死锁处理
    更改一字段的全部字节的大小写
  • 原文地址:https://www.cnblogs.com/csgc0131123/p/5290276.html
Copyright © 2020-2023  润新知