八数码简单题题解

前言

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

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

好啦,回归正题。

双向广搜

什么是双向广搜?

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

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

怎么实现双向广搜?

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

2.确定初始状态( s t a r t start start ) 与目标状态( e n d end end ) 。

3.开始搜索

​ <1> 首先将 s t a r t start start 放入 q s qs qs 中, e n d end end 放入 q e qe qe 中。

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

​ 若 q s qs qs 不为空,取出队头元素,若队头状态 v e ve ve 中已经存在,证明已经找到,退出。

​ 否则,进行扩展,将可达的状态放入 q s qs qs 队尾中,同时在 v s vs vs 数组中标记

​ 若 q e qe qe 不为空,取出队头元素,若队头状态 v s vs vs 中已经存在,证明已经找到,退出。

​ 否则,进行扩展,将可达的状态放入 q e qe qe 队尾中,同时在 v e ve ve 数组中标记

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

划重点

1. 双向广搜适用于有确定的初始状态和目标状态的搜索。
2. 初始状态到达目标状态最好有解,否则双向广搜 甚至 \color{red}\huge{\textsf{甚至}} 甚至 不如单向广搜。
3. q s qs qs q e qe qe 扩展判重时,一定要分别判重 (代码中有解释)。
综上所述,用双向广搜来写八数码,是比较合适的。(不存在的情况可以用逆序对的奇偶性排除掉)。

代码如下

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值