BFS 八数码问题 typedef int State[9]; (BFS A*算法与优先队列)

题目描述

八数码问题,即在一个3×3的矩阵中有8个数(1至8)和一个空格,现在要你从一个状态转换到另一个状态,每次只能移动与空格相邻的一个数字到空格当中,问题是要你求从初始状态移动到目标状态所需的最少步数。如下图所示。

1

2

3

 

1

2

3

8

0

4

 

7

8

4

7

6

5

 

0

6

5

                   初始状态                                 目标状态

如上图所示的数据以矩阵形式给出。现在给出分别代表初始状态和目标状态的两个3*3的矩阵,请给出两个矩阵相互转化的最少步数。

输入

第1行-第3行:3个用空格分隔的整数,代表初始状态相应位置的数字,0代表空格
第4行-第6行:3个用空格分隔的整数,代表终止状态相应位置的数字,0代表空格

输出

第1行:一个整数,最小转换步数,如不能到相互转化则输出”Impossible”

样例输入

1 2 3
8 0 4
7 6 5
1 2 3
7 8 4
0 6 5

样例输出

2 可以通过BFS DFS来做
《BFS 八数码问题 typedef int State[9]; (BFS A*算法与优先队列)》
《BFS 八数码问题 typedef int State[9]; (BFS A*算法与优先队列)》

《BFS 八数码问题 typedef int State[9]; (BFS A*算法与优先队列)》

通过比较后发现我们采用BFS比较合理,因为DFS深度搜索下限不好设定,BFS按层搜索,只要不到目标状态就一直进行

如何表示状态呢? 将空格用0代替,然后9宫格成为了一串数字共有9!=362880

注意BFS的状态空间,有很多重复的状态,这时候就需要判重!!! 判重方法: 1)采用C++ STL中 set的特性,count来判断是否已经存在这样的状态,存在为1,不存在为0。但STL版本打时间效率最低

#include <cstdio>
#include <cstring>
#include <set>
using namespace std;
typedef int State[9];//State类型的
const int MAXSTATE=1000000;
State st[MAXSTATE],goal;// st为二维数组,goal为一维
int dist[MAXSTATE]={0,0};
set<int>vis;
void init_lookup_table() {vis.clear();}
int try_to_insert(int s)
{
    int v=0;
    for(int i=0;i<9;i++)
      v=v*10+st[s][i];
    if(vis.count(v)) return 0; //重复则为1,返回0
    vis.insert(v);  //不重复,插入并返回1
    return 1;
}
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
int BFS()
{
    init_lookup_table();//初始化判重的set
    int front=1,rear=2;
    while(front<rear){
        State &s=st[front]; //得到队头的9个数,组成的数组
        if(memcmp(goal,s,sizeof(s))==0) return front;
        int z;
        for(z=0; z<9; z++)
            if(!s[z]) break;  //寻找z,z为空格
        int x=z/3,y=z%3;  //将z从一维转换为二维
        for(int d=0;d<4;d++){ //空格向四个方向扩展
           int newx=x+dx[d];
            int newy=y+dy[d];
            int newz=newx*3+newy;//得到新的一维的z
            if(newx>=0&&newx<3&&newy>=0&&newy<3){// 移动合法
            State & t=st[rear];  //得到队尾的9个数u,组成的数组
            memcpy(&t,&s,sizeof(s)); //扩展新状态
            t[newz]=s[z];  //具体值变空格
            t[z]=s[newz];  //空格变具体值
            dist[rear]=dist[front]+1; //更新 新状态的距离值
            if(try_to_insert(rear)) rear++; //如果不重复,则修改队尾指
           }
        }
        front++; //扩展完毕,修改队首指针
    }
    return 0;
}
int main()
{
    for(int i=0 ;i<9;i++)
        scanf("%d",&st[1][i]);
    for(int i=0; i<9;i++)
        scanf("%d",&goal[i]);
    int ans=BFS();
    if(ans>0) printf("%d\n",dist[ans]);
        else printf("Impossible\n");
    return 0;
}

2)哈希表,设计一个哈希函数,然后将任意结点Xx映射到某个给定范围[0,M-1],M是程序员根据内存大小自己选用的,理想状态下,只要一个M大的数组即可完成判重,但此时会有不同的结点的哈希值相同,这种情况下把哈希值相同的状态转换成链表。(可以理解为通过哈希函数建立了很多条链表,有的链表上只有一个节点,有的却有多个)

#include <cstdio>
#include <cstring>
#include <set>
using namespace std;

typedef int State[9];
const int MAXSTATE = 1000000;
State st[MAXSTATE],goal;
int dist[MAXSTATE];
const int MAXHASHSIZE = 1000003;
int head[MAXHASHSIZE],next[MAXHASHSIZE];
void init_lookup_table()
{
    memset(head,0,sizeof(head));
}
int hash(State &s)
{
    int v=0;
    for(int i=0;i<9;i++)
        v=v*10+s[i];
    return v%MAXHASHSIZE;

}

int try_to_insert(int s)
{
    int h=hash(st[s]); //多条链表,进行定位是第几条链表
    int u=head[h];   //链表的状态 0为链表为空  其他的则为已经创建进入循环判断
    while(u){       //u=0链表为空则直接跳过,否则从表头开始查找链表,(针对于产生冲突的)
      if(memcmp(st[u],st[s],sizeof(st[s])==0))  //找到与之相同的,则插入失败
         return 0;

        u=next[u];     //顺着链表继续查找
    }
    next[s]=head[h];   //插入到链表当中,
    head[h]=s;
    return 1;
}
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
int BFS()
{

    init_lookup_table(); //初始化
    int front=1,rear=2;  //定义头尾
    while(front<rear){
     State &s =st[front]; //取队头的9宫格
     if(memcmp(goal,s,sizeof(s))==0) //判断是否找到目标
     return front;
     int z;
     for(z=0;z<9;z++)
        if(!s[z]) break; //寻找队头9宫格的空格
     int x=z/3,y=z%3; //将一维的z转换成二维的
     for(int d=0;d<4;d++) //向四个方向扩展
     {
         int newx=x+dx[d];
         int newy=y+dy[d];
         int newz=newx*3+newy; //得到了扩展之后的z(空格)
         if(newx>=0&&newx<3&&newy>=0&&newy<3){
          State &t=st[rear];//找个变量并且改变了尾指针指向的九宫格,下面把s的赋给了t
          memcpy(&t,&s,sizeof(s));//构建变换之后的九宫格
          t[newz] = s[z];  //把原先的具体值的位置换成空格
          t[z]=s[newz];   //把原先的空格换成具体值
          dist[rear]=dist[front]+1;//更新 新状态的具体值
          if(try_to_insert(rear)) rear++;  //判断如果不重复,则入队列
        }

     }
     front++;  //一个九宫格的扩展完成,出队列进行下一个
    }
}

int main() {
  for(int i = 0; i < 9; i++)
    scanf("%d", &st[1][i]);
  for(int i = 0; i < 9; i++)
    scanf("%d", &goal[i]);
  int ans = BFS();
  if(ans > 0) printf("%d\n", dist[ans]);
  else printf("-1\n");
  return 0;
}

参考《算法竞赛入门经典》 P199-202 代码值得学习的还有”引用”与”自定义”,使得代码量减少 typedef int State[9];

State &s =st[front]; //取队头的9宫格

A*算法也可以应用到此题目,下面给出代码的思想: 这个题属于单个的起始状态到目标状态应用BFS,但是这里应用了A*算法+优先队列(BFS的优化  启发式算法),优先处理最有可能到达目标状态的9宫格 启发式算法的估价函数 1:

《BFS 八数码问题 typedef int State[9]; (BFS A*算法与优先队列)》

启发式算法的估价函数2:

《BFS 八数码问题 typedef int State[9]; (BFS A*算法与优先队列)》

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
using namespace std;
struct eight
{
    int st[9],bh;
    int step; //step表示步数
    int g;    //g表示从起始状态到当前状态的代价
    int h;    //h表示状态中不在位的方块数,若所有h=0,则为BFS
    int f;
    bool operator <(const eight &k)const{
      if(f==k.f)
         return g>k.g;  //若f值相同,g值小的优先
      return f>k.f;     //若f值不同,则f小的优先
    }
}s,t;
priority_queue<eight> que;
int goal[9];
int visited[362885],fact[9];
int ans;
int dirs[4][2]={{-1,0},{1,0},{0,1},{0,-1}};
int bianHao(int s[9])
{
    fact[0]=1;
    for(int i=1;i<9;i++)
      fact[i]=fact[i-1]*i;
    int code=0;
    for(int i=0;i<9;i++){
     int  cnt=0;
     for(int j=i+1;j<9;j++)
        if(s[j]<s[i])
          cnt++;
    code+=fact[8-i]*cnt;
    }
    return code;
}
int wrongPlaceNum(int a[9])
{
    int cnt=0;
    for(int i=0;i<9;i++)
      if(a[i]!=goal[i])
        cnt++;
    return cnt;
}
void Astar()
{
    eight t,t1;
    while(!que.empty())
    {
        t=que.top(); que.pop();//取F值最小的队列元素
        if(memcmp(goal,t.st,sizeof(t.st))==0){ans=t.step; break;} //找到目标状态返回
        int z;
        for(z=0;z<9;z++)
            if(!t.st[z]) break;
        int x=z/3,y=z%3;
        for(int i=0;i<4;i++)
        {
            int newx=x+dirs[i][0];
            int newy=y+dirs[i][1];
            int newz=newz*3+newy;
            if(newx>=0&&newx<3&&newy>=0&&newx<3)
            {
                memcpy(&t1,&t,sizeof(t));
                t1.st[newz]=t.st[z];//9宫格的状态的变化
                t1.st[z]=t.st[newz];
                t1.bh=bianHao(t1.st);//计算编号用于判重
                if(!visited[t1.bh]) //r若状态t1未访问过
                {
                    t1.g=t.g+1;
                    t1.h=wrongPlaceNum(s.st);
                    t1.f=t1.g+t1.h;
                    t1.step=t.step+1;
                    que.push(t1);//压入优先队列
                    visited[t1.bh]=true;//标记
                }
            }
        }

    }
}
int jopl(int s[9])//检查奇偶形
{
   int cnt,total=0;
   for(int i=0;i<9;i++)
   {
       cnt=0;
       if(s[i]==0) continue; //忽略空格
       for(int j=0;j<i;j++)
       {
           if(s[j]==0) continue; //忽略空格
           if(s[j]<s[i]) cnt++;
       }
       total += cnt;
   }
    return total%2;
}
int main()
{
    for(int i=0;i<9;i++)
        scanf("%d",&s.st[i]);
    for(int i=0;i<9;i++)
        scanf("%d",&goal[i]);
    if(jopl(s.st)^jopl(goal)) //若两状态的奇偶排列属性不同
    {
        printf("Impossible\n");
        return 0;
    }
    memset(visited,false,sizeof(visited));
    s.g=s.step=0,s.bh=bianHao(s.st),s.h=wrongPlaceNum(s.st),s.f=s.g+s.h;
    while(!que.empty()) que.pop();
        que.push(s); visited[s.bh]=true;
        ans=0;
        Astar();
        printf("%d\n",ans);
    return 0;
}

    原文作者:BFS
    原文地址: https://blog.csdn.net/deepseazbw/article/details/75270078
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞