八数码问题

本文探讨了八数码问题,并将其转化为图上的最短路径问题。通过BFS算法解决该问题,介绍了三种避免重复状态的方法:编码解码、哈希技术和STL集合。最后给出了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

八数码问题:

编号为1~8的8个正方形滑块被摆成3行3列(有一个格子留空),每次可以把与空格相邻的滑块(又公共边才算相邻)移动到空格中,而他原来的位置就成了新的空格,给定初始局面和目标局面(0表示空格),你的任务时计算出最少移动的步数,无法达到输出-1.


样例输入:

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

31
分析:把八数码问题归结为图上的最短路径问题,图的“节点”就是9个格子中的滑块编号,(从上到下,从左到右把他们放到一个九个元素的数组中)
无权图上的最短路径问题可用BFS求解

还有就是重判的问题,如何重判呢?

第一种方法:把排列变成整数,然后只开一个一维数组,也就是说设计一套排列的编码和解码函数,把0~8的全排列和0~362879的整数意义一一对应起来。

时间效率高,但编码解码法适用范围并不大,如果隐士图总结点非常大,数组还是开不下。

int vis[362880], fact[9];
void init_lookup_table()
{
    fact[0]=1;
    for(int i=1;i<9;i++) fact[i]=fact[i-1]*i;
}
int try_to_insert(int s){
    int code=0;
    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++;
        code+= fact[8-i]*cnt;
    }
    if(vis[code]) return 0;
    return vis[code]=1;
}
第二种方法:把节点变成整数但不必是一一对应的,,换句话说,只需要设计一个所谓的哈希函数h(x),然后将任意节点的x映射到某个给定的范围[0,M-1]的整数即可,其中M是程序员根据可用内存大小自选的,在理想情况下,只需开一个大小为M的数组就能完成重判,但有时会有不同节点的哈希值相同,因此需要把哈希值相同的状态组织成链表。

const int hashsize = 1000003;
int head[hashsize], next[maxstate];
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];
    ///把9个数字组合成9位数
    return v % hashsize;
    ///确保hash函数值是不超过hash表的大小的非负整数
}
int try_to_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 = next[u];
    }
    next[s]=head[h];///插入链表
    head[h] = s;
    return 1;
}
需要注意:哈希表中对效率起作用的是哈希函数。

第三种:STL集合。

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;
    vis.insert(v);
    return 1;
}

下面贴上全部代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<set>
using namespace std;
typedef int State[9];
///定义状态类型
const int maxstate=1000000;
State st[maxstate], goal;
///状态数组,所有状态都保存在这里
int dist[maxstate];///距离数组
///如果需要打印方案,可以在这里加一个“父亲编号”数组
///int fa[maxstate]
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
///BFS,返回目标状态在st数组下标
///为了防止重判,但又不能开9维数组9的9次方是387420489
///第一种方法:把排列编程整数,只开一个一维数组,设计一套编码解码
///把0~8的全排列和0~362879的整数一一对应
///第一种方法:编码解码法
//int vis[362880], fact[9];
//void init_lookup_table()
//{
//    fact[0]=1;
//    for(int i=1;i<9;i++) fact[i]=fact[i-1]*i;
//}
//int try_to_insert(int s){
//    int code=0;
//    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++;
//        code+= fact[8-i]*cnt;
//    }
//    if(vis[code]) return 0;
//    return vis[code]=1;
//}
///第二种方法:hash技术
//const int hashsize = 1000003;
//int head[hashsize], next[maxstate];
//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];
//    ///把9个数字组合成9位数
//    return v % hashsize;
//    ///确保hash函数值是不超过hash表的大小的非负整数
//}
//int try_to_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 = next[u];
//    }
//    next[s]=head[h];///插入链表
//    head[h] = s;
//    return 1;
//}
///第三种方法STL集合t
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;
    vis.insert(v);
    return 1;
}
int bfs()
{
    init_lookup_table();       ///初始化查找表
    int front = 1, rear = 2;///不使用下标0,因为0被看作不存在

    while(front < rear){

        State& s=st[front];///引用简化代码
        if(memcmp(goal,s,sizeof(s))==0) return front;

        ///memcmp是比较内存区域goal和s的前count个字节
        ///找到目标状态,成功返回
        int z;
        for(z=0;z<9;z++) if(!s[z]) break;   ///找到0的位置
        int x=z/3,y=z%3;    ///获取行列编号(0~2)

        for(int d=0;d<4;d++){
            int newx = x + dx[d];
            int newy = y + dy[d];
            int newz = newx * 3 + newy;
            if(newx >= 0&&newx < 3&&newy >= 0&&newy < 3)
            ///如果移动合法
            {
                State& t = st[rear];
                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("-1\n");
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值