0.问题引入
在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765)。
找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。
八数码问题有三种解决方法:
1. 广度优先遍历(BFS)
八数码问题难就难在状态的表示和状态的转移过程。
每个状态都是一个3 * 3的矩阵,我们最终要寻找到达一个目标矩阵的最小步数,也就是说只要我们能够找到一个能够表示当前状态的步数和矩阵的方法的话,就可以解决这个问题。
实际上可以从输入的地方下手,和他给的数据一样,我们可以把一个二维的矩阵转换成一个一维的矩阵,同样也可以再把一个一维的矩阵转换成一个二维的矩阵,然后用一个字符串来表示这个一维矩阵,同时开一个hash表来记录到当前状态的最短步数。
那么我们就只差一个问题了,就是怎么样进行状态的转移,我们可以思考一下,一个矩阵最多转移到四个矩阵,就是空位的四周都是数字的时候,也就是说,我们可以把空位和它的上下左右四个地方的数字(如果存在)进行交换,得到一个新的矩阵,然后步数是上一个矩阵的步数+1,这样就完成了状态的转移。
代码如下:
#include <bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
#define fi first
#define se second
#define endl '\n'
#define IOS std::ios::sync_with_stdio(false),cin.tie(0), cout.tie(0)
using namespace std;
const int N = 1e6 + 10;
unordered_map<string, int> d; // hash表存当前字符串离目标字符串距离,同时可以当标记用
string a;
int bfs()
{
string end = "12345678x";
queue<string> Q;
int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};
Q.push(a);
d[a] = 0;
while(Q.size())
{
a = Q.front();
Q.pop();
int dis = d[a];
int t = a.find('x');
int x = t / 3, y = t % 3; //一维数组下标转换成二维数组下标的方法!
for(int i = 0; i < 4; i ++)
{
int xx = x + dx[i], yy = y + dy[i];
if(xx < 0 || xx >= 3 || yy < 0 || yy >= 3) continue;
if(a == end) return dis;
int k = xx * 3 + yy; //二维数组下标转换成一维数组下标的方法!
swap(a[t], a[k]);
if(!d.count(a)) //如果hash表里没有过a这个字符串的记录,就放入队列
Q.push(a), d[a] = dis + 1;
swap(a[t], a[k]); //还原的目的是为了继续向其他方向扩展
}
}
return -1;
}
int main()
{
IOS;
for(int i = 0; i < 9; i ++)
{
char c;
cin >> c;
a += c;
}
cout << bfs() << endl;
return 0;
}
但是需要注意状态的总数是比较少的,相当于9的全排列, 9!个状态,差不多30多W。但是像16数码问题,状态就多达16!个,暴力广搜的状态会超级多,只能用双向广搜或者启发式搜索优化。