Loading

八数码问题——隐式图遍历

隐式图

显式图是指有一张图的实体,你需要使用各种遍历技术从中找到一个路径或者什么答案。而隐式图是指没有这个图的实体,你需要在遍历的过程中动态生成一些图。

隐式图中把每个图看作一个状态,有多少个图就有多少种状态。光说太抽象了,看看题目。

八数码问题

八数码问题。编号为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;
}
posted @ 2020-12-02 17:14  yudoge  阅读(435)  评论(0编辑  收藏  举报