八数码——路径寻找问题

#include <cstdio>
#include <cstring>
#include <set>

using namespace std;

// 状态类型,每一个状态都对应9个数
typedef int State[9];
const int maxState = 1000000;
// 状态数组
State st[maxState], goal;
// 距离数组
int dist[maxState];
// 上下左右
const int dx[] = {-1, 1,  0, 0};
const int dy[] = { 0, 0, -1, 1};
// 如果需要打印方案,可以添加一个父亲编号书组 int fa[maxState]

// 3种判断是否允许插入的方式各有优点
// 编码解码不适用于隐式图总结点非常大的情况
// hash法可能会有冲突问题
// STL最慢

// 编码解码法
// 访问数组和阶乘因子
// 0-8的全排列一共有362880个数
int vis[362880], fact[9];
void init_lookup_table() {
    fact[0] = 1;
    // 计算阶乘因子
    // 0->1 1->1 2->2 3->6...8->40320
    for(int i = 1; i < 9; i++) {
        fact[i] = fact[i - 1] * i;
    }
}

int try_to_insert(int s) {
    // 把st[s]映射到整数code
    int code = 0;
    for(int i = 0; i < 9; i++) {
        int cnt = 0;
        for(int j = i + 1; j < 9; j++) {
            // 求出i的逆序数
            if(st[s][j] < st[s][i]) {
                cnt++;
            }
        }
        // 举例
        // 8 7 6 5 4 3 2 1 0这样的序列其对应的code如下
        // 8*8!+7*7!+...+1*1!=362879,也就是最大的数
        // 通过这样巧妙的方式,实现序列到整数的一一映射
        code += fact[8 - i] * cnt;
    }
    if(vis[code]) {
        return 0;
    }
    return vis[code] = 1;
}


// hash表法
const int hashSize = 1000003;
// 头结点数组
int head[hashSize], next[maxState];
void init_lookup_table2() {
    memset(head, 0, sizeof(head));
}

int hash(const State &s) {
    int v = 0;
    for(int i = 0; i < 9; i++) {
        // 把9个数字组合成9位数
        v = v * 10 + s[i];
    }
    // 确保hash函数值是不超过hash表的大小的非负整数
    return v % hashSize;
}

int try_to_insert2(int s) {
    int h = hash(st[s]);
    // 从表头开始查找链表
    int u = head[h];
    while(u) {
        // 找到了,插入失败
        if(memcmp(st[u], st[s], sizeof(st[s])) == 0) {
            return 0;
        }
        // 顺着链表继续找
        u = next[u];
    }
    // 插入到链表中
    next[s] = head[h];
    head[h] = s;
    return 1;
}


// STL法
set<int> visited;
void init_lookup_table3() {
    visited.clear();
}

int try_to_insert3(int s) {
    int v = 0;
    for(int i = 0; i < 9; i++) {
        v = v * 10 + st[s][i];
    }
    if(visited.count(v)) {
        return 0;
    }
    visited.insert(v);
    return 1;
}

// 返回目标状态在st数组下标
int bfs() {
    // 初始化映射表
// init_lookup_table();
// init_lookup_table2();
    init_lookup_table3();

    // 不使用下标0,0被看做不存在
    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++) {
            // 获取0的位置
            if(!s[z]) {
                break;
            }
        }
        // 获取行列编号
        int x = z / 3;
        int y = z % 3;

        for(int d = 0; d < 4; d++) {
            int newx = x + dx[d];
            int newy = y + dy[d];
            int newz = newx * 3 + newy;
            // 移动合法
            if(newx >= 0 && newx < 3 && newy >= 0 && newy < 3) {
                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)) {
 // if(try_to_insert2(rear)) {
                if(try_to_insert3(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]);
    }
    // 返回目标状态的下标
    int ans = bfs();
    if(ans > 0) {
        printf("%d\n", dist[ans]);
    } else {
        printf("-1\n");
    }
    return 0;
}

你可能感兴趣的:(八数码,路径寻找问题)