八数码问题

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


如上图所示的数据以矩阵形式给出。现在给出分别代表初始状态和目标状态的两个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求解。而在搜索中,“判重”是很关键的步骤。我学习模仿了刘汝佳采用对排列编码的方法进行判重的做法。
关于其编码的方式,我是这样理解的:0~8的全排列一共有9!=362880种,我们把这362880种不同的排列方式分别与整数0~362879一一对应起来。这里可以利用康托展开,即计算当前排列是所有排列(字典序)中的第几个的数。比如说:
对于数组{1,2,3},它的全排列的字典序是 123,132,213,231,312,321. 请问321是第几个的数(把首排列123看作第0个数)。根据康托展开,其就是第2*2!+1*1!=5个数。因为从第一个数开始向后看,在3后面比3还小的数有2和1,那么分别以2和1开头的排列一共有2*2!=4种;而在2后面比2还小的数只有1,那么以31开头的排列就只有1*1!=1种,所以总数是4+1=5种,即321前面有5种排列方式,所以它是第五个数。
那么对于0~8的全排列就同理了,函数init_lookup_table()和try_to_insert()就是上述方法的实现。比如说012345678是第0个数,它对应的整数就是0;而876543210是第362879个数,它对应的整数就是362879。

代码如下:

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

typedef int State[9];       //定义‘状态’类型
const int maxstate = 1000000;
State st[maxstate], goal;   //状态数组。所有的状态都保存在这里
int vis[362880], fact[9];
int dist[maxstate];         //距离数组
char c[6];

const int dx[] = { -1,1,0,0 };
const int dy[] = { 0,0,-1,1 };

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;               //把st[s]映射到整数code
    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;
}

//BFS,返回目标状态在st数组的下标
int bfs()
{
    init_lookup_table();    //初始化查找表
    int front = 1, rear = 2;
    while (front < rear)
    {
        State& s = st[front];   //引用
        if (memcmp(goal, s, sizeof(s)) == 0) return front;
        int z;
        for (z = 0; z < 9; z++) if (!s[z]) break;   //找到0的位置
        int x = z % 3, y = z / 3;
        for (int i = 0; i < 4; i++)
        {
            int newx = x + dx[i];
            int newy = y + dy[i];
            if (newx >= 0 && newx <= 2 && newy >= 0 && newy <= 2)   //如果移动合法
            {
                int newz = newy * 3 + newx;
                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]);  //目标状态
    memset(dist, 0, sizeof(dist));
    int ans = bfs();                                    //返回状态的下标
    if (ans > 0) printf("%d\n", dist[ans]);
    else printf("Impossible\n");
    //system("pause");
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值