八数码问题(typedef int state[9];)(多种解法)

题目描述
八数码问题,即在一个3×3的矩阵中有8个数(1至8)和一个空格,现在要你从一个状态转换到另一个状态,每次只能移动与空格相邻的一个数字到空格当中,问题是要你求从初始状态移动到目标状态所需的最少步数。如下图所示。
123 123
804 784
765 065
初始状态 目标状态
如上图所示的数据以矩阵形式给出。现在给出分别代表初始状态和目标状态的两个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

分析:八数码问题可以归结为图上的最短路问题,图的“结点”就是9宫格中的编号。这是无权图上的最短路问题用BFS求解。
9个数的位置状态一共有9!中,那么这道题的状态就需要用二维数组来储存,而这里用得比较好的是typedef int state[9];这表示state a;和int a[9];等价.
然后根据BFS的模式来求解最少步数,但在这里 关键在于判重 因为图的最短路问题不判重就会出现重复,时间和空间都会产生很大的浪费! 那么如何判重呢? 和以前写过的题一样,设置一个数组(九维)。这样会空间会有大量的浪费。存不下。 转化为9位数再设置标记数组? 还是空间问题。。
那么根据这些,就有三种相应的方法解决这个问题(详解参见紫书P201)

代码主体:

#include<cstdio>
#include<cstring>
#include<set>
using namespace std;
#define N 362885
typedef int state[9];
state st[N],goal;
int d[N],fact[9],vis[N];
int dxy[4][2]={0,1,1,0,0,-1,-1,0};
bool in(int x,int y)///判断坐标(x,y)是否合法
{
    if(x>=0&&x<3&&y>=0&&y<3)
        return true;
    return false;
}
int bfs()
{
    int front=1,rear=2,z;
    init();
    while(front<rear)///队列非空
    {
        state& s=st[front];///取队头元素
        if(!memcmp(s,goal,sizeof(s)))///到达目标状态
            return front;
        for(z=0; z<9; z++)
            if(!s[z]) break;///找0所在的位置
        int x=z/3,y=z%3;//(x,y)为0的坐标
        for(int i=0; i<4; i++)
        {
            int nx=x+dxy[i][0],ny=y+dxy[i][1],nz=nx*3+ny;//(nx,ny)是新的坐标,nz是新的位置
            if(in(nx,ny))
            {
                state& t=st[rear];
                memcpy(&t,&s,sizeof(s));///扩展新结点
                t[nz]=s[z];///这两行实现0的位置的移动
                t[z]=s[nz];///
                d[rear]=d[front]+1;///更新新节点的距离值
                if(Insert(rear))///如果成功插入,队列元素+1
                    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) printf("%d\n",d[ans]);
    else printf("Impossible\n");
    return 0;
}

1.哈希技术(用数组表示链表即无指针)

const int hasize=1000003;///哈希表的大小
int head[hasize],ne[N];
void init()///表头的初始化
{
    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%hasize;///避免越界
}
int Insert(int s)
{
    int h=hash(st[s]);///找表头
    int u=head[h];///从表头开始查找链表
    while(u)
    {
        if(memcmp(st[u],st[s],sizeof(st[s]))==0) return 0;///若表中已经存在,插入失败
        u=ne[u];///顺着链表继续查找
    }
    ne[s]=head[h];///插入到链表中
    head[h]=s;
    return 1;///插入成功
}

用有指针的链表
const int hasize=100003;
struct adj
{
    int jd;
    adj *next;
};
adj *head[hasize];
void init()
{
    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%hasize;
}
int Insert(int s)
{
    int h=hash(st[s]);
    adj *u=head[h];
    while(u)
    {
        if(memcmp(st[u->jd],st[s],sizeof(st[s]))==0) return 0;
        u=u->next;
    }
    adj *p=new(adj);///头插法插入
    p->jd=s;
    p->next=head[h];
    head[h]=p;
    return 1;
}

2.将排列“变成”整数,开一个一位数组

void init()///fact[i]表示i的阶乘
{
    for(int i=fact[0]=1; i<9; i++)
        fact[i]=fact[i-1]*i;
}
int Insert(int s)
{
    int cd=0;///把st[s]映射到整数cd
    for(int i=0;i<9;i++)
    {
        int cnt=0;///按升序全排列时,求比当前编码小的编码书,即当前编码额编号
        for(int j=i+1;j<9;j++)
            if(st[s][j]<st[s][i])
            cnt++;
        cd+=fact[8-i]*cnt;
    }
    if(vis[cd]) return 0;///若此编号存在,插入失败
    return vis[cd]=1;//插入成功并标记此编号
}

3.STL中的集合set

#include<set>
set<int>vis;
void init()
{
    vis.clear();
    for(int i=fact[0]=1; i<9; i++)
        fact[i]=fact[i-1]*i;
}
int Insert(int s)
{
    int v=0;
    for(int i=0; i<9; i++)
        v=v*10+st[s][i];///将当前状态转化为9位数
    if(vis.count(v)) return 0;///集合中不存在
    vis.insert(v);///加入集合
    return 1;
}

4.A*算法

#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#include<algorithm>
using namespace std;
struct Node
{
    int st[9],bh;
    int cnt;
    int g,h,f;
    bool operator < (const Node &k) const    ///重载比较运算符
    {
        if(f==k.f)
            return g>k.g;  ///若f值相同,则g的最小值优先
        return f > k.f;  ///若f值不相同,则f的最小值优先
    }
} s,t;
priority_queue<Node> que;   ///最小优先队列
int goal[9];
int vis[362885],fact[9]; 
int ans;            ///最少移动次数ans
int dxy[4][2]= {{-1,0},{1,0},{0,-1},{0,1}}; ///4个移动方向
void init()
{
    fact[0]=1;
    for(int i=1; i<9; i++)
        fact[i]=fact[i-1]*i;
}
int BH(int s[9])///求当前状态数的编号
{
    int cd=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++;
        cd += fact[8-i]*cnt;
    }
    return cd;
}
int WPNum(int a[9])   ///求不在位的数字个数
{
    int cnt=0;
    for(int i=0; i<9; i++)
        if(a[i]!=goal[i]) cnt++;
    return cnt;
}
void Astar()///A*算法
{
    Node t,t1;
    while(!que.empty())///若最小优先队列que不为空
    {
        t=que.top();
        que.pop();///取队列首元素(f值最小)
        if(memcmp(goal,t.st,sizeof(t.st))==0)
        {
            ans=t.cnt;     ///找到目标状态,成功返回
            break;
        }
        int z;
        for(z=0; z<9; z++)  ///找0的位置
            if(!t.st[z]) break;
        int x=z/3,y=z%3;
        for(int i=0; i<4; i++)
        {
            int nx=x+dxy[i][0];
            int ny=y+dxy[i][1];
            int nz=nx*3+ny;
            if(nx>=0&&nx<3&&ny>=0&&ny<3)
            {
                memcpy(&t1,&t,sizeof(t));
                t1.st[nz]=t.st[z];
                t1.st[z]=t.st[nz];
                t1.bh=BH(t1.st);///状态t->t1后,计算状态t1的编号,用于判重
                if(!vis[t1.bh])///若状态t1未访问过
                {
                    t1.g = t.g + 1;///g表示从起始状态到当前状态t1的代价,用结点在搜索树中的深度来表示
                    t1.h = WPNum(s.st);///h表示状态t1中不在位的方块数。若所有h=0,则退化成BFS
                    t1.f = t1.g + t1.h;
                    t1.cnt = t.cnt + 1;
                    que.push(t1);///状态t1入最小优先队伍que
                    vis[t1.bh]=true;///并按状态t1的编号置访问标记
                }
            }
        }
    }
}
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()
{
    init();
    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))///若两状态的奇偶排列属性不同   位异或运算,相同为0,不同为1
    {
        printf("Impossible\n");
        return 0;
    }
    memset(vis,false,sizeof(vis));
    s.g=s.cnt=0,s.bh=BH(s.st),s.h=WPNum(s.st),s.f=s.g+s.h;
    while(!que.empty()) 
        que.pop();
    que.push(s);
    vis[s.bh]=true;
    ans=0;
    Astar();
    printf("%d\n",ans);
    return 0;
}

5.双向BFS算法
。。。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值