题目描述
八数码问题,即在一个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;
}