题解 P1746 【离开中山路】

· · 题解

此题好像还没有启发式搜索A Star算法题解,我就水一发.

以下废话:

两三天我为了学A Star算法,随便找了这道BFS伪模板水题来练习,当时对A Star了解的不够深刻,因为自己的问题,只能拿40分.

经过三天的更深层研究后,终于找到了问题所在,修改代码后AC.

以下是A Star简介:

A Star算法通常用来寻路,是启发式搜索的一种,它与DFS,BFS搜索方式略不相同.如果你熟悉BFS就很好理解,因为BFS可以算是A Star算法中极端的一种.所以我打算将BFS与A Star进行比较说明.

在BFS算法中,每一个格子向外扩展的优先级就是自己的步数,因为步数少的先进入搜索队列.在A Star算法中,每一个格子向外扩展的优先级需要计算,通过估步函数后,每个格子就带上了权值,代表它的优先级.

再来看BFS,通常扩展后的格子放在普通的队列中,队列是先进先出的,所以先搜到的格子先进行扩展.而A Star算法一般使用优先队列(二叉堆),所以后进去的格子,有可能因为优先级高排在在队列前端,很快就开始扩展此格子,来搜索下一步.

说到估步函数,一般叫评价函数,就是计算某个格子优先级的函数.在这个题中,要计算平面最短路径,所以f(优先级)=step(到达此格子的最小步数)+h(与终点的曼哈顿距离)

先放上部分代码,再继续讲解:

bool closelist[1002][1002], openlist[1002][1002];
//关闭列表和开放列表
struct node {
    int  x, y, step, f, h;
    void init(int _x, int _y, int _step) {
        x= _x;
        y= _y;
        step= _step;
        h= abs(x - wx) + abs(y - wy);
        //曼哈顿距离
        f= step + h;
        //f为已走步数+到达目标需要距离
    }
    void update(int _step) {
        step= _step;
        f= step + h;
    }
    int abs(int _a) {
        return _a > 0 ? _a : -_a;
    }
} newn[1002][1002];
struct nodecmp {
    bool operator()(node *&a, node *&b) const {
        if(a->f == b->f) return a->h > b->h;
        return a->f > b->f;
        //小根堆,f越低在小根堆里越靠前,f一样就选离目标近的
    }
};
int  lx, ly, lstep, newx, newy;
void Astar() {
    priority_queue<node *, vector<node *>, nodecmp> q;
    //优先队列存放的是指针,存放指针后默认是指针的从小到大排序
    //因此不能重载<运算符,要把比较函数写到一个类里
    newn[bx][by].init(bx, by, 0);
    q.push(&newn[bx][by]);
    while(!q.empty()) {
        lx= q.top()->x;
        ly= q.top()->y;
        lstep= q.top()->step;
        q.pop();
        closelist[lx][ly]= true;
        //将此点放到关闭列表,进行扩展
        for(int i= 0; i < 4; i++) {
            newx= lx + moves[i][0], newy= ly + moves[i][1];
            if(tmap[newx][newy] == '1' || closelist[newx][newy]) continue;
            if(newx == wx && newy == wy) {
            //到达目标结束搜索
                ans= lstep + 1;
                return;
            }
            if(openlist[newx][newy]) {
            //如果扩展后的点已经存在于开放列表
                if(lstep < newn[newx][newy].step) {
                //比较步数,更新扩展后的点
                    newn[newx][newy].update(lstep + 1);
                }
            }
            else {
            //如果扩展后的点不存在于开放列表
                newn[newx][newy].init(newx, newy, lstep + 1);
                q.push(&newn[newx][newy]);
                openlist[newx][newy]= true;
                //将扩展后的点放入优先队列和开放列表中
            }
        }
    }
    return;
}

我的代码还是可以优化的,有些地方为了写着容易而耗费了运行时间空间.

A Star在实现时有两个bool二维数组,一个优先队列(可以手写会更快).

这两个bool二维数组分别是开放列表(OpenList)和关闭列表(CloseList),分别代表某个方格是否已经在队列中,某个方格是否已经向外扩展过

我在代码中用优先队列存指针,不存指针而是存元素也可以,时间空间差不了太多,貌似是不用指针好,不用指针的话还可以省略更新节点的操作,只不断向优先队列push就够了(我感觉指针省空间,但结果相反)

对于这个题,别的就没什么了,学会BFS搜索后,这种较为普通的A Star寻路只不过多了优先级.而对于更高深的启发式搜索题,才是A Star算法的真面目(好奇怪).

若想更好了解学习A Star,可以试试 【模板】k短路([SDOI2010]魔法猪学院),就是有一个点卡A Star会MLE.

下面附上完整代码:

#include <iostream>
#include <stdio.h>
#include <queue>
#include <math.h>
#include <stdlib.h>
#include <limits.h>
using namespace std;
int  n, bx, by, wx, wy, moves[4][2]= {{-1, 0}, {0, -1}, {0, 1}, {1, 0}}, ans;
bool closelist[1002][1002], openlist[1002][1002];
char tmap[1002][1002];
struct node {
    int  x, y, step, f, h;
    void init(int _x, int _y, int _step) {
        x= _x;
        y= _y;
        step= _step;
        h= abs(x - wx) + abs(y - wy);
        f= step + h;
    }
    void update(int _step) {
        step= _step;
        f= step + h;
    }
    int abs(int _a) {
        return _a > 0 ? _a : -_a;
    }
} newn[1002][1002];
struct nodecmp {
    bool operator()(node *&a, node *&b) const {
        if(a->f == b->f) return a->h > b->h;
        return a->f > b->f;
    }
};
int  lx, ly, lstep, newx, newy;
void Astar() {
    priority_queue<node *, vector<node *>, nodecmp> q;
    newn[bx][by].init(bx, by, 0);
    q.push(&newn[bx][by]);
    while(!q.empty()) {
        lx= q.top()->x;
        ly= q.top()->y;
        lstep= q.top()->step;
        q.pop();
        closelist[lx][ly]= true;
        for(int i= 0; i < 4; i++) {
            newx= lx + moves[i][0], newy= ly + moves[i][1];
            if(tmap[newx][newy] == '1' || closelist[newx][newy]) continue;
            if(newx == wx && newy == wy) {
                ans= lstep + 1;
                return;
            }
            if(openlist[newx][newy]) {
                if(lstep < newn[newx][newy].step) {
                    newn[newx][newy].update(lstep + 1);
                }
            }
            else {
                newn[newx][newy].init(newx, newy, lstep + 1);
                q.push(&newn[newx][newy]);
                openlist[newx][newy]= true;
            }
        }
    }
    return;
}
int main() {
    cin >> n;
    for(int i= 0; i <= n + 1; i++) {
        for(int j= 0; j <= n + 1; j++) {
            if(i == 0 || j == 0 || i == n + 1 || j == n + 1)
                tmap[i][j]= '1';
            else
                cin >> tmap[i][j];
        }
    }
    cin >> bx >> by >> wx >> wy;
    Astar();
    cout << ans << endl;
    return 0;
}