双向广搜解决八数码问题

双向广搜:所谓双向搜索指的是搜索沿两个方向同时进行:正向搜索:从初始结点向目标结点方向搜索;逆向搜索:从目标结点向初始结点方向搜索;当两个方向的搜索生成同一子结点时终止此搜索过程。

通常有两种实现方法:

1、用一个队列来储存子状态,起点和终点先后入队,正向搜索和逆向搜索交替进行,两个方向的搜索交替扩展子状态。直到两个方向的搜索产生相同的子状态结束。

2、两个方向的搜索虽然是交替扩展子状态的。但是两个方向生成的子状态的速度不一定平衡。所以,可以每次选择子状态数较少的那个方向先进行扩展。这样就不会出现两个方向生成子状态的速度的不平衡,可以明显的提高效率。

下面给出双向广搜模版


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);  // 入队
                       }
             }             
}

下面介绍八数码问题

C - Eight

The 15-puzzle has been around for over 100 years; even if you don't know it by that name, you've seen it. It is constructed with 15 sliding tiles, each with a number from 1 to 15 on it, and all packed into a 4 by 4 frame with one tile missing. Let's call the missing tile 'x'; the object of the puzzle is to arrange the tiles so that they are ordered as:

 1  2  3  4 

 5  6  7  8 

 9 10 11 12 

13 14 15  x 


where the only legal operation is to exchange 'x' with one of the tiles with which it shares an edge. As an example, the following sequence of moves solves a slightly scrambled puzzle:

 1  2  3  4    1  2  3  4    1  2  3  4    1  2  3  4 

 5  6  7  8    5  6  7  8    5  6  7  8    5  6  7  8 

 9  x 10 12    9 10  x 12    9 10 11 12    9 10 11 12 

13 14 11 15   13 14 11 15   13 14  x 15   13 14 15  x 

           r->           d->           r-> 


The letters in the previous row indicate which neighbor of the 'x' tile is swapped with the 'x' tile at each step; legal values are 'r','l','u' and 'd', for right, left, up, and down, respectively.

Not all puzzles can be solved; in 1870, a man named Sam Loyd was famous for distributing an unsolvable version of the puzzle, and
frustrating many people. In fact, all you have to do to make a regular puzzle into an unsolvable one is to swap two tiles (not counting the missing 'x' tile, of course).

In this problem, you will write a program for solving the less well-known 8-puzzle, composed of tiles on a three by three
arrangement.

Input

You will receive a description of a configuration of the 8 puzzle. The description is just a list of the tiles in their initial positions, with the rows listed from top to bottom, and the tiles listed from left to right within a row, where the tiles are represented by numbers 1 to 8, plus 'x'. For example, this puzzle

 1  2  3 

 x  4  6 

 7  5  8 


is described by this list:

 1 2 3 x 4 6 7 5 8 

Output

You will print to standard output either the word ``unsolvable'', if the puzzle has no solution, or a string consisting entirely of the letters 'r', 'l', 'u' and 'd' that describes a series of moves that produce a solution. The string should include no spaces and start at the beginning of the line.

Sample Input

 2  3  4  1  5  x  7  6  8 

Sample Output

ullddrurdllurdruldr

 题意:八数码问题也称为九宫问题。在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。棋盘上还有一个空格,与空格相邻的棋子可以移到空格中。要求解决的问题是:给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。

下面给出双向广搜解决八数码问题的代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<vector>
#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;
}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值