八数码问题——隐式图遍历
隐式图
显式图是指有一张图的实体,你需要使用各种遍历技术从中找到一个路径或者什么答案。而隐式图是指没有这个图的实体,你需要在遍历的过程中动态生成一些图。
隐式图中把每个图看作一个状态,有多少个图就有多少种状态。光说太抽象了,看看题目。
八数码问题
八数码问题。编号为1~8的8个正方形滑块被摆成3行3列(有一个格子留空),如图7-14所示。每次可以把与k格相邻的滑块(有公共边才算相邻)移到空格中,而它原来的位置就成为了新的空格。给定初始局面和目标局面(用0表示空格),你的任务是计算出最少的移动步数。如果无法到达目标局面,则输出-1。
输入输出示例
样例输入:
2 6 4 1 3 7 0 5 8
8 1 5 7 3 6 4 0 2
样例输出:
31
思路
这题的思路就是把整个局面看成一个图,把滑块看作图中的节点,每次向四个方向滑动为0的滑块可以生成4张(有时不到四张)图,这些图就是状态。这样,我们用BFS遍历所有的状态并记录每个状态走了几步,如果某一个状态和目标状态相同就代表找到了一个最少移动的方式,如果穷尽了所有状态也没找到,那就证明没有办法移动到另一个状态。
说起来简单,但是有很多细节需要注意,比如一共有多少个状态,状态怎么表示,BFS中怎么状态去重。
首先我们先解决状态怎么表示的问题,一共有0~8,9个格子,我们使用一个长度为9的一维数组表示即可,这和输入样例相吻合,而且无论是对比和表示都很方便。
这样一维二维坐标的转换方式就如下
1d to 2d
x = i / 3 , y = i % 3
2d to 1d
i = 3 * x + y
一共有多少个状态?只需要一次全排列,\(P_9^9 = 362880\)。在暴力相关的问题中不算太多。
如何状态去重又是个大问题,如果按之前的vis
数组的办法,就得使用9维数组来记录这个状态并判断是不是之前已经计算过相应情况了。这好吗?这不好!
可以考虑使用一个把全排列一一映射到长度为362880的一维vis数组中即可。
也可以使用hash算法,这里直接用了紫书里给的代码,或者用STL中的hash_map。
代码
#include "iostream"
#include "cstdio"
#define MAX 9
#define MAX_STATES 1000000
using namespace std;
typedef int State[MAX];
State states[MAX_STATES], goal;
int dist[MAX_STATES];
const int hashsize = 1000010;
int book[hashsize], _next[hashsize];
void init(){
memset(book,0, sizeof(book));
memset(_next,0, sizeof(_next));
}
int _hash(State& s)
{
int v = 0;
for (int i = 0; i < 9; i++) v = v * 10 + s[i];
return v % hashsize;
}
int try_to_insert(int s)
{
int h = _hash(states[s]);
int u = book[h];
while (u)
{
if (memcmp(states[s], states[u], sizeof(states[s])) == 0) return 0;
u = _next[u];
}
_next[u] = book[h];
book[h] = s;
}
int dx[] = { 1,-1,0,0 }, dy[] = { 0,0,1,-1 };
int bfs() {
init();
int f = 1, t = 2;
while (f < t) { // 只有当状态穷尽了,f才可能追上t
State &s = states[f]; // 当前状态
if (memcmp(s, goal, sizeof(goal)) == 0) return dist[f]; // 通过对比发现当前状态等于目标状态,返回距离
int x, y, zi;
for (zi = 0; zi < MAX && s[zi]; zi++) {} // 找到当前状态中0所在位置
x = zi / 3; y = zi % 3; // 把一维线性的下标转换成二维坐标
for (int i = 0; i < 4; i++) { // 零的上下左右抽取一位幸运格子移动向零
int new_x = x + dx[i] , new_y = y + dy[i];
if (new_x >= 0 && new_x < 3 && new_y >= 0 && new_y < 3) {
int new_z = new_x * 3 + new_y;
State& new_g = states[t];
memcpy(&new_g, &s, sizeof(s));
new_g[zi] = new_g[new_z]; // 把新格子移动向零
new_g[new_z] = 0; // 新格子置为0
dist[t] = dist[f] + 1; // 更新距离
if (try_to_insert(t))t++; // 判重
}
}
f++;
}
return -1;
}
int main() {
for (int i = 0; i < MAX; i++) scanf("%d", &states[1][i]);
for (int i = 0; i < MAX; i++) scanf("%d", &goal[i]);
memset(dist, 0, sizeof(dist));
int ans = bfs();
printf("%d\n", ans > 0 ? ans : -1);
return 0;
}