问题描述

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

A*思路

A*算法是对BFS的一种优化。

问题可以看作初始状态为$s$,终点状态为$t$,在状态空间内寻找一个从$s\to t$的最短路径。

我们设从起点到终点的实际为距离$f_{*}(x)$,从起点到该状态的实际距离为$g_{*}(x)$,从该状态到终点的实际距离为$h_{*}(x)$,由此我们可以得到$f_{*}(x)=g_{*}(x)+h_{*}(x)$。

但是很明显,我们很难去得到实际距离,所以我们设$f(x),g(x),h(x)$为上述三个值的预估值,即估价函数。

在八数码问题中,我们可以把$g(x)$设计为移动的步数,$h(x)$设计为不在目标位置的数字的数目,由此预估到终点状态的距离。

当$h(x)\leq h_{*}(x)$时,A*总能求出最优解,此时我们称$h$是可接纳的。上述条件下如果$h$满足三角形不等式,则算法不会重复地将节点加入队列。

我们通过建立优先队列,将各状态按照$f(x)$大小建立小根堆。不断拓展$f(x)$最小的$x$,并将合法状态加入队列,直到取出目标状态$t$。

参考代码

#include <bits/stdc++.h>
using namespace std;

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

struct Matrix {
    int d[5][5];

    bool operator < (Matrix x) const {
        for ( int i = 1; i <= 3; ++i )
            for ( int j = 1; j <= 3; ++j )
                if ( d[i][j] != x.d[i][j] )
                    return d[i][j] < x.d[i][j];

        return false;
    }
} fi, st;

int h(Matrix x) {
    int ret = 0;
    for ( int i = 1; i <= 3; ++i )
        for ( int j = 1; j <= 3; ++j )
            if ( x.d[i][j] != st.d[i][j] )
                ++ret;

    return ret;
}

struct Node {
    Matrix a;
    int t;
    bool operator < (Node x) const { 
        return t + h(a) > x.t + h(x.a); 
    }
} x;

int fx, fy;
priority_queue<Node> q;
set<Matrix> s;

int main(void) {
    st.d[1][1] = 1;
    st.d[1][2] = 2;
    st.d[1][3] = 3;
    st.d[2][1] = 8;
    st.d[2][2] = 0;
    st.d[2][3] = 4;
    st.d[3][1] = 7;
    st.d[3][2] = 6;
    st.d[3][3] = 5;

    for ( int i = 1; i <= 3; ++i )
        for ( int j = 1; j <= 3; ++j ) {
            fi.d[i][j] = getchar() - '0';
        }

    q.push({fi, 0});

    while ( !q.empty() ) {
        x = q.top();
        q.pop();
        
        if ( !h(x.a) ) {
            cout << x.t;

            return 0;
        }

        for ( int i = 1; i <= 3; ++i )
            for ( int j = 1; j <= 3; ++j )
                if ( !x.a.d[i][j] ) {
                    fx = i;
                    fy = j;
                }

        for ( int i = 1; i <= 4; ++i ) {
            int nx = fx + dx[i];
            int ny = fy + dy[i];

              if ( nx >= 1 && nx <= 3 && ny >= 1 && ny <= 3 ) {
                swap(x.a.d[fx][fy], x.a.d[nx][ny]);

                if ( !s.count(x.a) ) {
                    s.insert(x.a);
                    q.push({x.a, x.t + 1});
                }

                swap(x.a.d[fx][fy], x.a.d[nx][ny]);
            }
        }
    }

    return 0;
}
Last modification:November 21, 2019
博客维护不易,如果你觉得我的文章有用,请随意赞赏