双向广搜 8数码问题

 转载自:http://blog.sina.com.cn/s/blog_8627bf080100ticx.html 

 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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/321738.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用静态基类方案让 ASP.NET Core 实现遵循 HATEOAS Restful Web API

Hypermedia As The Engine Of Application State (HATEOAS)HATEOAS&#xff08;Hypermedia as the engine of application state&#xff09;是 REST 架构风格中最复杂的约束&#xff0c;也是构建成熟 REST 服务的核心。它的重要性在于打破了客户端和服务器之间严格的契约&…

U94222-循环往复【tarjan,DAGdp】

正题 题目链接:https://www.luogu.org/problem/U94222?contestId23574 题目大意 nnn个点若条有向边,求一条路径要求 经过一个酒店经过权值最大消费最小起点编号最小 按照顺序满足。 解题思路 将强连通分量缩成一个点&#xff0c;然后用gi,0/1g_{i,0/1}gi,0/1​到达第iii个…

16、java中的集合(3)

说一下双列集合&#xff0c;顶级接口是Map&#xff0c;实现类有HashMap、LinkedHashMap、TreeMap、HashTable等&#xff0c;使用键值对的格式存储数据&#xff0c;键不可以重复&#xff0c;值可以重复。接下来对实现类做一下详细介绍。 HashMap是最常用的Map集合&#xff0c;它…

搜索训练1 [8数码问题]

HDU1043、以及POJ1077上面都有这道题目&#xff0c;可以说是搜索里的非常经典的题目了。 poj上面的数据真的是弱&#xff0c;由于只有一组数据&#xff0c;简单bfs直接就可以过掉。 前前后后捣鼓了能有6个小时&#xff0c;才把这道题目在HDU上以4500ms的微弱优势通过。。。。…

【招聘(北京)】.NETCORE开发工程师(微服务方向)

组织&#xff1a;华汽集团北京研发中心位置&#xff1a;北京市朝阳区焦奥中心官网&#xff1a;www.sinoauto.com邮箱&#xff1a;taoxu.weisinoauto.com 项目&#xff1a;打造面向国内汽车后市场用户的一站式云服务平台&#xff08;华汽云&#xff09;&#xff0c;形态包括B2B、…

2017西安交大ACM小学期数据结构 [分块,区间修改,单点查询]

Problem A 发布时间: 2017年6月28日 09:29 最后更新: 2017年6月28日 13:03 时间限制: 1000ms 内存限制: 32M 描述 给定一个长度为n的序列a1, a2, ..., an给出q个操作, 操作分为两种 对于形如1xyz的操作, 将下标介于[x,y]的元素加上z, 满足1≤x≤y≤n, 1≤z≤105对于形如2…

17、java中的集合(4)

之前单列集合只说过了List系列的集合&#xff0c;接下来再说一下Set集合系列&#xff0c;Set集合是无序集合&#xff08;存取顺序不一致&#xff09;&#xff0c;不允许添加相同元素&#xff0c;Set的实现依赖于Map集合&#xff0c;可以将Set集合看作Map集合键的集合&#xff0…

U92904-画地为佬【二分,结论】

正题 题目链接:https://www.luogu.org/problem/U92904?contestId23574 题目大意 用mmm根长度为1的火柴求能够圈住的最多块的地。 解题思路 显然如果刚好能够围成一个正方形那么一定是最优的&#xff0c;那么我们先将能够围成的围成一个最大的正方形&#xff0c;然后剩下的在…

确保线程安全下使用Queue的Enqueue和Dequeue

场景是这样&#xff0c;假设有一台设备会触发类型为Alarm的告警信号&#xff0c;并把信号添加到一个Queue结构中&#xff0c;每隔一段时间这个Queue会被遍历检查&#xff0c;其中的每个Alarm都会调用一个相应的处理方法。问题在于&#xff0c;检查机制是基于多线程的&#xff0…

2017西安交大ACM小学期数据结构 [分块、二维矩阵]

Problem B 发布时间: 2017年6月28日 10:06 最后更新: 2017年6月28日 16:35 时间限制: 2000ms 内存限制: 32M 描述 给定一个nm的矩形, 其中第i行第j列的值为ai,j给出q个操作, 操作有两种 对于形如1x1y1x2y2z的操作, 将(x1,y1)-(x2,y2)这段矩形区域的所有元素加上z, 满足1≤…

18、java中的泛型

之前介绍集合时&#xff0c;可以看到有List<String>这样的写法&#xff0c;那么尖括号里的内容是什么呢&#xff1f;这是泛型&#xff0c;意思就是说声明的这个List集合只能存放String类型的元素。 泛型是什么&#xff1f; ‘泛’指一般、不深入&#xff0c;在这里可以认…

编写一个Java程序,其中包含三个线程: 厨师(Chef)、服务员(Waiter)和顾客(Customer)

编写一个Java程序&#xff0c;其中包含三个线程: 厨师(Chef)、服务员(Waiter)和顾客(Customer)。他们的行动如下: 厨师准备菜肴&#xff0c;每次准备一个。服务员等待菜肴准备好&#xff0c;然后将其送到顾客那里。顾客等待服务员送来菜看后才开始吃。所有三个角色应该循环进行…

U86650-群鸡乱舞【矩阵乘法】

正题 题目链接:https://www.luogu.org/problem/U86650?contestId23574 题目大意 第一年有nnn只鸡&#xff0c;每只大于等于两岁的鸡每年可以生一只&#xff0c;在ttt岁时不会生鸡而会暴毙。 现在给出每只鸡的年龄&#xff0c;求第mmm年鸡的总数量。 解题思路 用fif_{i}fi​…

2017西安交大ACM小学期数据结构 [线段树]

Problem B 发布时间: 2017年7月1日 02:08 最后更新: 2017年7月1日 02:10 时间限制: 1000ms 内存限制: 64M 描述 给定一个长度为n的序列a1, a2, ..., an, 满足这个序列是一个1~n的排列 如果一个序列满足: 将序列排序后, 任意两个相邻的元素的差为1, 那么就称这个序列为&qu…

19、java中枚举

枚举是什么&#xff1f; 枚举就是将一个有限集合中的所有元素列举出来&#xff0c;在java中使用可以使用enum关键字来声明一个枚举类。 为什么使用枚举&#xff1f; 之前当用到一些常量时&#xff0c;便临时声明一个&#xff0c;这样使得代码看起来很乱&#xff0c;这里一个…

Hangfire使用ApplicationInsigts监控

起因我司目前使用清真的ApplicationInsights来做程序级监控。&#xff08;ApplicationInsights相关文档: https://azure.microsoft.com/zh-cn/services/application-insights/ &#xff09;其实一切都蛮好的&#xff0c;但是我们基于Hangfire的Job系统却无法被Ai所监控到&#…

nssl1446-小智的旅行【dp】

正题 题目大意 求一条最大的权值严格上升的路径。 解题思路 将边权排序&#xff0c;然后从fxf_xfx​转移到fy1f_y1fy​1即可&#xff0c;要注意的是因为严格上升&#xff0c;所以此次转移用的fff不能是相同权值转移时转移的。 codecodecode #include<cstdio> #include…

2017西安交大ACM小学期数据结构 [树状数组]

Problem C 发布时间: 2017年6月28日 11:38 最后更新: 2017年6月28日 16:38 时间限制: 1000ms 内存限制: 32M 描述 给定一个长度为n的序列a1, a2, ..., an, 其中ai∈[1,10]给出q个操作, 操作分为两种 对于形如1xy的操作, 将ax改为y, 满足1≤x≤n, 1≤y≤10对于形如2xyz的操…

NET主流ORM框架分析

接上文我们测试了各个ORM框架的性能&#xff0c;大家可以很直观的看到各个ORM框架与原生的ADO.NET在境删改查的性能差异。这里和大家分享下我对ORM框架的理解及一些使用经验。ORM框架工作原理所有的ORM框架的工作原理都离不开下面这张图&#xff0c;只是每个框架的实现程度不同…

20、java中的类加载机制

1、类加载机制是什么&#xff1f; 类加载机制指的就是jvm将类的信息动态添加到内存并使用的一种机制。 2、那么类加载的具体流程是什么呢&#xff1f; 一般说类加载只有三步&#xff1a;加载、连接和初始化&#xff0c;其中连接包括验证、准备和解析&#xff0c;用于将运行时加…