8数码问题-搜索-双向BFS/A*算法

1849: 【基础算法】8数码问题版本一

题目描述
在一个3*3的九宫格棋盘里,放有8个数码,数码的数字分别是1~8等8个数字。可以通过在九宫格里平移数码来改变状态。数码在任何情况下都不能离开棋盘。给出8个数码的初始状态(没放数码的空格用0表示)和目标状态,问从初始状态到目标状态,最少需要经过多少次移动操作。

例如,初始状态为:

2 6 4
1 3 7
0 5 8

目标状态是:

8 1 5
7 3 6
4 0 2

最少的移动步数为31步。
输入
第1行:9个空格分开的整数,表示初始状态

第2行:9个空格分开的整数,表示目标状态

整数都在0~8范围内,且不重复

输出
第1行:1个整数,表示从初始状态到目标状态的最少移动步数。如果无解,输出-1

样例输入
Copy (如果复制到控制台无换行,可以先粘贴到文本编辑器,再复制)

2 6 4 1 3 7 0 5 8
8 1 5 7 3 6 4 0 2
样例输出
31

解一:单向BFS
好慢的吧

解二:双向BFS
模型:当知道开始状态和结束状态,需要求一条从s到t的一条最短路径
当单向搜索的时候,搜索的隐式树呈爆炸式增长,好恐怖的。。
不过如果双向搜,会避免掉很多,时间复杂度由K^L变为2*(K^(L/2)),将大大减少。(K为度数,L为层数)
比如说单向是一个大三角,双向就是大三角中的一个小“菱形”(还是比较像..)

实现的时候用一个队列即可,先把s和t压入queue,挨着广搜。由于是广搜,s和t的子状态都是“一块一块的”,不会混在一起。

代码比较丑:

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
#define MAXN 9
#define MAXKT 362880
struct node{
    char s[MAXN+10];
    int step;
    bool flag;
    int blank;

};
int pos,steps[MAXKT+10],w[MAXN+10];
int dir[4+5]={1,-1,3,-3};
bool vis[2+5][MAXKT+10];
char s[MAXN+10],t[MAXN+10];
void read()
{
    int a;
    for(int i=0;i<9;i++){
        scanf("%d",&a);
        s[i]=a+'0';
    }
    for(int i=0;i<9;i++){
        scanf("%d",&a);
        t[i]=a+'0';
    }
    w[8]=0; w[7]=1;
    for(int i=6;i>=0;i--) w[i]=w[i+1]*(8-i);
}
int kangtuo(char *x)
{
    int ret=0;
    for(int i=0;i<9;i++){
        if(x[i]=='0'){
            pos=i; continue;
        }
        for(int j=i+1;j<9;j++)
            if(x[j]<x[i])
                ret+=w[i];
    }
    return ret;
}
void double_BFS()
{
    queue<node> que;
    int num=kangtuo(s);
    node start; memcpy(start.s,s,sizeof s);start.step=0,start.flag=0,start.blank=pos;
    que.push(start);
    vis[0][num]=true;
    num=kangtuo(t);
    node end; memcpy(end.s,t,sizeof s);end.step=0,end.flag=1,end.blank=pos;
    que.push(end);
    vis[1][num]=true;

    while(!que.empty()){
        node cur=que.front(); que.pop();
        node next;
        num=kangtuo(cur.s);
        char state[MAXN+10];
        memcpy(state,cur.s,sizeof cur.s);
        for(int i=0;i<4;i++){
            int newpos=cur.blank+dir[i];
            if(newpos>=0&&newpos<9&&((newpos/3==cur.blank/3)||(newpos%3==cur.blank%3))){
                //同一列||同一行
                swap(state[cur.blank],state[newpos]);
                int newnum=kangtuo(state);
                if(vis[!cur.flag][newnum]){
                    printf("%d\n",cur.step+steps[newnum]+1);
                    return ;
                }
                else if(!vis[cur.flag][newnum]){
                    vis[cur.flag][newnum]=true;
                    steps[newnum]=cur.step+1;
                    memcpy(next.s,state,sizeof state);next.step=cur.step+1,next.flag=cur.flag,next.blank=newpos;
                    //node(state,cur.step+1,cur.flag,newpos)
                    que.push(next);
                }
                swap(state[cur.blank],state[newpos]);//还原state 
            }
        }
    }
    printf("-1\n");
    return ;
}
int main()
{
    read();
    double_BFS();
    return 0;
}

还是来个好看点的(同学的。。):
速度应该蛮快

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
int posx;
struct node
{
    char s[10];
    int hash;
    int id;
    int f;
    node(const char *a,const int &b,const int &d,const int &e){
        strcpy(s,a);
        id=b;
        f=d;
        hash=e;
    }
};
bool f[10],vis[2][370000];
int bv[10]={1,1,2,6,24,120,720,5040,40320},map[370000];
int order(char *t)
{
    int ans=0;
    for(int i=0;i<9;i++)
    {
        int tmp=0;
        for(int j=i+1;j<9;j++)
        {
            if(t[i]>t[j])
                tmp++;
        }
        ans+=tmp*bv[8-i];
    }
    return ans;
}
char a[10],b[10];
int dd[4]={-3,1,-1,3};
queue<node>q;
void BFS()
{
    while(!q.empty())
    {
        int pos=q.front().id,k=q.front().f,hash=q.front().hash;
        char*a=q.front().s;
        for(int i=0;i<4;i++)
            if(pos+dd[i]>=0&&pos+dd[i]<9&&(pos/3==(dd[i]+pos)/3||pos%3==(pos+dd[i])%3))
            {
                swap(a[pos],a[dd[i]+pos]);
                int num=order(a);
                if(!vis[k^1][num]&&!vis[k][num])
                {
                    q.push(node(a,dd[i]+pos,k,num));
                    vis[k][num]=1;
                    map[num]=map[hash]+1;
                }
                else if(vis[k^1][num])
                {
                    printf("%d\n",map[hash]+map[num]+1);
                    return;
                }
                swap(a[pos],a[dd[i]+pos]);
            }
        q.pop();
    }
    printf("-1");
}
int main()
{
    int t;
    for(int i=0;i<9;i++)
    {
        scanf("%d",&t);
        a[i]=t+'0';
        if(!t)posx=i;
    }
    int num=order(a);
    q.push(node(a,posx,0,num));
    vis[0][num]=1;
    for(int i=0;i<9;i++)
    {
        scanf("%d",&t);
        b[i]=t+'0';
        if(!t)posx=i;
    }
    num=order(b);
    q.push(node(b,posx,1,num));
    vis[1][num]=1;
    int cnt1,cnt2;
    cnt1=cnt2=0;
    for(int i=1;i<9;++i)
        for(int j=0;j<i;++j)
        {
            if(a[i]!='0'&&a[i]<a[j])cnt1++;
            if(b[i]!='0'&&b[i]<b[j])cnt2++;
        }
    if((cnt1^cnt2)&1){printf("-1");return 0;}
    BFS();
}

然后呢。。。。。。。。。

解三:A*算法
先介绍一下,类似DFS和BFS的结合版,但他充分运用贪心的思想,尽可能的在最有限的搜索展开中,找到最优解。
有一个叫估值函数的东西:f[i]=g[i]+h[i]
f[i]:从当前到目标的估计距离
g[i]:从s到当前的真实距离
h[i]:从当前到目标的估计距离。

必须保证h[i]<=h*[i] !!!
(h*[i]为从当前到目标的真是距离)
如此才能保证A*算法的正确性,运用反证法可证的,此处略过

就本题而言,h[]是所有点Manhattan Distance的和(虽然我并不知道如何证明此时的h[]<=h*[],而且感觉有点危险),但这毕竟较当前状态和目标状态的相同位置上不同数的个数当估值函数要高效的多,所以,我们无语的忽略掉证明的问题,如有兴趣也可以自己证明。。。

不得不说代码很丑,而且效率较低,但我也不知道哪儿写渣了。。。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
using namespace std;
#define MAXN 9
#define MAXKT 362880
struct node{
    char s[MAXN+10];
    int step,blank,f;
    bool operator<(const node& y) const{
        return f>y.f;
    }
};
int pos,w[MAXN+10];
int dir[4+5]={1,-1,3,-3};
char s[MAXN+10],t[MAXN+10];
int f[MAXKT+10],h[MAXKT+10];
void read()
{
    int a;
    for(int i=0;i<9;i++){
        scanf("%d",&a);
        s[i]=a+'0';
    }
    for(int i=0;i<9;i++){
        scanf("%d",&a);
        t[i]=a+'0';
    }
    w[8]=0; w[7]=1;
    for(int i=6;i>=0;i--) w[i]=w[i+1]*(8-i);
    memset(f,0x3f,sizeof(f));
}
int kangtuo(char *x)
{
    int ret=0;
    for(int i=0;i<9;i++){
        if(x[i]=='0'){
            pos=i; continue;
        }
        for(int j=i+1;j<9;j++)
            if(x[j]<x[i])
                ret+=w[i];
    }
    return ret;
}
int Manhattan_Dist(char *x,char *y)
{
    int ret=0;
    for(int i=0;i<9;i++){
        if(x[i]=='0') continue;
        for(int j=0;j<9;j++)
            if(x[i]==y[j]){
                ret+=(abs(j-i)/3)+(abs(j-i)%3); ///need to rethink of it
                break;
            }
    }
    return ret;
}
void Astar()
{
    priority_queue<node> que;
    int num=kangtuo(s);
    node start; memcpy(start.s,s,sizeof s);start.step=0,start.blank=pos,start.f=0;
    que.push(start);
    while(!que.empty()){
        node cur=que.top(); que.pop();
        char state[MAXN+10];
        memcpy(state,cur.s,sizeof cur.s);
        num=kangtuo(state);
        if(cur.f>f[num]) continue;
        for(int i=0;i<4;i++){
            int newpos=cur.blank+dir[i];
            if(newpos>=0&&newpos<9&&((newpos/3==cur.blank/3)||(newpos%3==cur.blank%3))){
                swap(state[cur.blank],state[newpos]);
                if(!strcmp(state,t)){
                    printf("%d\n",cur.step+1);
                    return ;
                }
                int more_step=Manhattan_Dist(state,t);
                num=kangtuo(state);
                if(more_step+cur.step<f[num]){
                    f[num]=more_step+cur.step+1;
                    node next; memcpy(next.s,state,sizeof state);next.step=cur.step+1,next.blank=newpos,next.f=f[num];
                    que.push(next);
                }
                swap(state[cur.blank],state[newpos]);
            }
        }
    }
    printf("-1\n");
    return ;
}
int main()
{
    read();
    Astar();
    return 0;
}

经过艰苦卓绝的奋斗,是不是觉得自己升华了呢?不过还有8数码问题版本二(他们居然还有版本二!!!)

Eight
Time Limit: 10000MS Memory Limit: 32768KB

Status

Description
Scenario

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
5 6 7 8
9 x 10 12
13 14 11 15
r->
1 2 3 4
5 6 7 8
9 10 x 12
13 14 11 15
d->
1 2 3 4
5 6 7 8
9 10 11 12
13 14 x 15
r->

1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 x

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, several descriptions of configuration of the 8 puzzle. One 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. Do not print a blank line between cases.

Sample Input
2 3 4 1 5 x 7 6 8

Sample Output
ullddrurdllurdruldr

记录有点麻烦。。实在想不到,请教了一下大神。。
据说是这样的:
用四进制存方向
next.path=now.path*4+dir
最后找到解ans后
for(int i=1;i<=ans.step;i++)
res[++rescnt]=ans.path%4,ans.path/=4;
然后倒序输出就行了

想法很好

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
using namespace std;
#define MAXN 9
#define MAXKT 362880
struct node{
    char s[MAXN+10];
    int step,blank,f,num;
    long long path;
    node(){}
    node(char *a,int b,int c,int d,long long e){
        strcpy(s,a);
        step=b;
        blank=c;
        f=d;
        path=e;
    }
    bool operator < (const node& y) const{
        return f>y.f;
    }
};
int pos,w[MAXN+10],route[MAXKT+10];
int dir[4+5]={1,-1,3,-3};
char s[MAXN+10],t[MAXN+10];
char dire[4+5]={'r','l','d','u'};
int f[MAXKT+10];
void prepare()
{
    for(int i=0;i<8;i++) t[i]=i+1+'0';
    t[8]='0';
    w[8]=1;
    for(int i=7;i>=0;i--) w[i]=w[i+1]*(8-i);
    memset(f,0x3f,sizeof(f));
}
int kangtuo(char *x)
{
    int ret=0;
    for(int i=0;i<9;i++){
        if(x[i]=='0'){
            pos=i; continue;
        }
        for(int j=i+1;j<9;j++)
            if(x[j]<x[i])
                ret+=w[i];
    }
    return ret;
}
int Manhattan_Dist(char *x,char *y)
{
    int ret=0;
    for(int i=0;i<9;i++){
        if(x[i]=='0') continue;
        for(int j=0;j<9;j++)
            if(x[i]==y[j]){
                ret+=(abs(j-i)/3)+(abs(j-i)%3); ///need to rethink of it
                break;
            }
    }
    return ret;
}
void print(long long last_path,int tot_step)
{
    for(int i=1;i<=tot_step;i++){
        route[i]=last_path%4;
        last_path/=4;
    }
    for(int i=tot_step;i>=1;i--)
        printf("%c",dire[route[i]]);
    puts("");
}
void Astar()
{
    char state[MAXN+10];
    priority_queue<node> que;
    int num=kangtuo(s);
    que.push(node(s,0,pos,0,0));
    while(!que.empty()){
        node cur = que.top(); que.pop();
        memcpy(state,cur.s,sizeof cur.s);
        num = kangtuo(state);
        if(cur.f>f[num]) continue;
        for(int i=0;i<4;i++){
            int newpos=cur.blank+dir[i];
            if(newpos>=0&&newpos<9&&((newpos/3==cur.blank/3)||(newpos%3==cur.blank%3))){
                swap(state[cur.blank],state[newpos]);
                if(!strcmp(state,t)){
                    print(cur.path*4+i,cur.step+1);
                    return ;
                }
                int more_step=Manhattan_Dist(state,t);
                num=kangtuo(state);
                if(more_step+cur.step+1<f[num]){
                    f[num]=more_step+cur.step+1;
                    que.push(node(state,cur.step+1,newpos,f[num],cur.path*4+i));
                }
                swap(state[cur.blank],state[newpos]);
            }
        }
    }
    printf("unsolvable\n");
    return ;
}
int main()
{
    char a[2+10];
    while(scanf("%s",a)!=EOF){
        s[0]=a[0];
        for(int i=1;i<9;i++){
            scanf("%s",a);
            if(a[0]!='x') s[i]=a[0];
            else s[i]='0';
        }
        prepare();
        Astar();
    }
    return 0;
}

虽然A得了,但ZOJ上的数据之难。。它并不能胜任

写个题解写了二十几分钟,也是醉掉了。。

貌似还有版本三,做了来更新。。。

转载于:https://www.cnblogs.com/katarinayuan/p/6572875.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值