【题目链接】

CodeVS 1225 八数码难题

【题目描述】

在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。


【解题思路】

不难把本题归结为图上的最短路问题,图中的结点表示状态,每个状态中的空位向四个方向移动可以得到新的后继状态,只需要找到一条从初始状态到目标状态的最优路径即可。 无权图上的最短路可以选择BFS求解,那么问题来了,该如何判重呢? 显然开9维数组是行不通的,借助STL中的set倒是可以,但效率上就低了。 这里我们借助一种叫做 康托展开 的东西来进行~~(玄学的)~~编码,可以实现0~8的全排列与0~9!的整数一一对应,事实上这也就是一个完美哈希。 实现上,将棋盘上数字从上到下,从左到右排列,用一9个元素的数组来代表状态。 搜索算法采取单向BFS,除此之外,如果数据不是那么简单(水),可以选择双向BFSA*算法,以后会加以实现。


【AC代码】

#include <cstdio>
#include <queue>
#include <cstring>

#define MAXN 1000000
#define STATE_SIZE (sizeof(int) * 9)

typedef int State[9];

struct Point{
    int x, y;
    int pos;

    Point(int x, int y, int pos) : x(x), y(y), pos(pos) {}

    Point turn(int dx, int dy){
        return Point(x + dx, y + dy, pos + 3 * dx + dy);
    }

    bool invalid(){
        return !(0 <= x && x < 3 && 0 <= y && y < 3);
    }
};

struct Node{
    int dist;
    bool visited;
    State state;

    Node(){
        visited = false;
    }

    Node(State s, Node *father = NULL){
        memcpy(state, s, STATE_SIZE);
        visited = true;
        if(father) dist = father->dist + 1;
        else dist = 0;
    }

    Point GetVaconcy(){
        int pos;
        for(pos = 0; pos < 9; pos++) if(state[pos] == 0) break;
        return Point(pos / 3, pos % 3, pos);
    }
} vs[MAXN];

int fact[9] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320 }; // 阶乘表,编码时要用到

Node* map(State s){
    int code = 0;
    for(int i = 0; i < 9; i++){
        int count = 0;
        for(int j = i + 1; j < 9; j++) if(s[j] < s[i]) count++;
        code += fact[8 - i] * count;
    }
    return vs + code;
}
//  这就是那个玄学的编码

Node* turn(Node* v, int dx, int dy){
    Point vacancy = v->GetVaconcy();
    Point swapPlace = vacancy.turn(dx, dy);

    if(swapPlace.invalid()) return NULL;

    State s;
    memcpy(s, v->state, STATE_SIZE);
    std::swap(s[vacancy.pos], s[swapPlace.pos]);

    Node *target = map(s);
    if(target->visited) return NULL;
    else{
        *target = Node(s, v);
        return target;
    }
}

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

inline int BFS(Node *s, Node *t){
    std::queue<Node*> Q;

    Q.push(s);
    s->dist = 0;
    s->visited = true;

    while(!Q.empty()){
        Node *v = Q.front(); Q.pop();
        if(v == t){
            return v->dist;
        }
        for(int i = 0; i < 4; i++){
            Node *vi = turn(v, dx[i], dy[i]);
            if(vi) Q.push(vi);
        }
    }

    return -1;
}

int main(){
    State beginState, targetState = { 1, 2, 3, 8, 0, 4, 7, 6, 5 };
    for(int i = 0; i < 9; i++) beginState[i] = getchar() - '0';
    Node *s = map(beginState), *t = map(targetState);
    *s = Node(beginState);
    int ans = BFS(s, t);
    printf("%d", ans);
    return 0;
}

就是这样啦