前言

就是想练习一下双向广搜,然后就加强了这一题的数据。

我也不知道其他方法能不能过(实际上就是太懒了,不想写其他方法。。。)

好啦,回归正题。

双向广搜

什么是双向广搜?

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

画个图就是这样(图片来自网络)

八数码简单题题解

怎么实现双向广搜?

1.需要两个队列 (废话)分别简记为 qsqs qeqe,两个标记数组 vs ve (分别用来记录两边搜索达到的状态)。

2.确定初始状态( startstart ) 与目标状态( endend ) 。

3.开始搜索

​ <1> 首先将 startstart 放入 qsqs 中,endend 放入 qeqe 中。

​ <2> 若两队列有一个不为空进行一下循环

​ 若 qsqs 不为空,取出队头元素,若队头状态 veve 中已经存在,证明已经找到,退出。

​ 否则,进行扩展,将可达的状态放入 qsqs 队尾中,同时在 vsvs 数组中标记

​ 若 qeqe 不为空,取出队头元素,若队头状态 vsvs 中已经存在,证明已经找到,退出。

​ 否则,进行扩展,将可达的状态放入 qeqe 队尾中,同时在 veve 数组中标记

​ <3> 到达此处说明无解,搜索结束。

划重点

1. 双向广搜适用于有确定的初始状态和目标状态的搜索。

2. 初始状态到达目标状态最好有解,否则双向广搜 甚至\color{red}\huge{\textsf{甚至}} 不如单向广搜。

3. qsqsqeqe 扩展判重时,一定要分别判重 (代码中有解释)。

综上所述,用双向广搜来写八数码,是比较合适的。(不存在的情况可以用逆序对的奇偶性排除掉)。

代码如下

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#define MAXN 362887
using namespace std;

int fact[11]={0,1,2,6,24,120,720,5040,40320,362880,3628800};
struct node{
    int k[10],step;
    int t;
}st,en;
node qs[MAXN],qe[MAXN];            //两个队列
int ve[MAXN],vs[MAXN];             //状态数组,记录到达每种状态所需的最小步数

inline void read(int &x){          //快读,没什么说的,习惯而已
    char ch=getchar();x=0;int fl=1;
    while(ch<'0'||ch>'9'){if(ch=='-') fl=-fl;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
    x*=fl;
}

inline int KT(node x){             //康拓展开,将每种状态转换成 1~10! 之间的数字。类似于状态压缩
    int res=0;
    for(int i=1;i<=9;++i){
        int tmp=0;
        for(int j=i+1;j<=9;++j){
            if(x.k[j]<x.k[i]) ++tmp;
        }
        res+=(tmp*fact[9-i]);
    }
    return res+1;
}

inline bool isOK(int ind,int opt){ //判断0往opt这个方向走合不合法
    switch(opt){
        case 1:if(ind%3==1){return 0;}else{return 1;} break;
        case 2:if(ind%3==0){return 0;}else{return 1;} break;
        case 3:if(ind<=3){return 0;}else{return 1;} break;
        case 4:if(ind>6){return 0;}else{return 1;} break;
    }
    return 0;
}

inline node change(node x,int opt){ //如果合法,则进行扩展,并返回新的状态
    int ind=x.k[0];
    switch(opt){
        case 1:swap(x.k[ind],x.k[ind-1]);x.k[0]=ind-1;return x;
        case 2:swap(x.k[ind],x.k[ind+1]);x.k[0]=ind+1;return x;
        case 3:swap(x.k[ind],x.k[ind-3]);x.k[0]=ind-3;return x;
        case 4:swap(x.k[ind],x.k[ind+3]);x.k[0]=ind+3;return x;
    }
    return x;
}

inline void dbfs(){                //双向广搜
    memset(ve,0,sizeof(ve));       //标记数组清零
    memset(vs,0,sizeof(vs));
    int ls=0,rs=1,le=0,re=1;       //队列清空
    qs[1]=st,qe[1]=en;             //推入初状态与末状态
    vs[st.t]=1,ve[en.t]=1;         //进行标记
    while(ls<=rs||le<=re){
        if(ls<=rs){
            ++ls;                  //循环队列的操作,下同
            if(ls>362880) ls=1;
            node x=qs[ls];
            if(ve[x.t]!=0){        //如果从末状态已经搜索到了,意味着找到了解,退出
                printf("%d\n",ve[x.t]+vs[x.t]-2);
                return;
            }
            for(int i=1;i<=4;++i){ //扩展新的状态
                if(isOK(x.k[0],i)){
                    node y=change(x,i);
                    y.t=KT(y);
                    if(vs[y.t]||x.step>2500) continue;  //若从初始状态扩展时已经有了这种状态
                    y.step=x.step+1;					//跳过
                    vs[y.t]=y.step;
                    ++rs;
                    if(rs>362880) rs=1;
                    qs[rs]=y;
                }
            }
        }
        if(le<=re){
            ++le;
            if(le>362880) le=1;
            node x=qe[le];
            if(vs[x.t]!=0){        //如果从末状态已经搜索到了,意味着找到了解,退出
                printf("%d\n",ve[x.t]+vs[x.t]-2);
                return;
            }
            for(int i=1;i<=4;++i){ //扩展新的状态
                if(isOK(x.k[0],i)){
                    node y=change(x,i);
                    y.t=KT(y);
                    if(ve[y.t]||x.step>2500) continue; //若从末状态扩展时已经有了这种状态
                    y.step=x.step+1;                   //跳过
                    ve[y.t]=y.step;
                    ++re;
                    if(re>362880) re=1;
                    qe[re]=y;
                }
            }
        }
    }
    printf("-1\n");
}

inline bool jud(node x){  //暴力求逆序对,判断是否可以到达
    int res=0;
    for(int i=1;i<=9;++i){
        if(x.k[i]==0) continue;
        for(int j=i+1;j<=9;++j){
            if(x.k[j]==0) continue;
            if(x.k[i]>x.k[j]) ++res;
        }
    }
    return res%2;
}

inline void init(){
    while(~scanf("%d",&st.k[1])){
        if(st.k[1]==0) st.k[0]=1;
        for(int i=2;i<=9;++i){
            read(st.k[i]);
            if(st.k[i]==0) st.k[0]=i;
        }
        for(int i=1;i<=9;++i){
            read(en.k[i]);
            if(en.k[i]==0) en.k[0]=i;
        }
        if(jud(st)!=jud(en)){
            printf("-1\n");
            continue;
        }
        st.step=1,en.step=1;
        st.t=KT(st),en.t=KT(en);
        dbfs();
    }
}

int main(){
    init();
    return 0;
}

相关文章:

  • 2022-12-23
  • 2021-07-26
  • 2022-02-12
  • 2021-04-17
  • 2022-12-23
  • 2021-08-26
  • 2021-06-04
猜你喜欢
  • 2020-04-29
  • 2021-11-06
  • 2021-09-12
  • 2018-09-20
  • 2021-06-25
  • 2021-05-20
  • 2021-12-26
相关资源
相似解决方案