[旧版][知识点]A*搜索(启发式搜索)

// 此博文为迁移而来,写于2015年4月4日,不代表本人现在的观点与看法。原始地址:http://blog.sina.com.cn/s/blog_6022c4720102vwud.html

Update - 20200517

该博文内容现已更新并迁移至:

[知识点] 3.2 五种改进搜索算法 https://www.cnblogs.com/jinkun113/p/12889652.html

 
1、前言
       一般的搜索多为DFS和BFS。A*算法,其实说得简单一点,就是聪明些的搜索;说复杂些,A*算法的前途和作用是非常大的——他甚至要涉及到人工智能(AI)。所以,A*是很有前途的。
 
2、概念
       A*同样是以BFS为框架进行搜索的。其实就是加强版的BFS。。他需要定义一个函数,设为H(x),称作估价函数。估价函数的定义有很多种形式。先举一个很简单的例子:
 // 标准图已丢失
       定义一个直角坐标系,S为起点,T为终点。中间深蓝色的地方为不可移动的高地。我们先来分析一下使用DFS或BFS的效率:从S开始,它需要遍历整个地图,可见是很慢的。在这种情况下,用A*搜索绝对是快一些的,但是A*必须将估价函数定义得足够合理。下面具体介绍估价函数。
 
3、估价函数
       搜索时,对于距离的定义普遍可以表示为:
 
        F=G+H   (G为搜索时起点到该点的深度,H为估价函数)
 
       在上图中,用到的方法称为Manhattan method(曼哈顿距离),就是从考虑的点通过水平和垂直移动达到目标点的移动步数。注意只是水平和垂直移动,不走斜线。并且忽略图中的障碍物。
-----------------------------------------------------------------------------------------------------
int getVal(int x,int y,int dep) { return (abs(x-tx)+abs(y-ty)+dep); } // abs为绝对值
-----------------------------------------------------------------------------------------------------
       注意,不是所有A*搜索的估价函数是一样的,比如“八数码难题”、“靶形数独”所需的估价函数都是不一样的!而且,这里只介绍最普通的估价函数,具体题目要具体分析,需要自己一步一步去试最合适的估价函数。
 
4、搜索
       根据上述估价函数,可以得到目前的状态:
// 标准图已丢失

       初始化的时候,我们将节点S保存在一个数组(其使用普通的数组是不行的,后面会讲如何实现)。节点S开始搜索周围8个节点,得到F值。本轮搜索结束。下一轮的时候,将所有还没有向下遍历的节点(即之前已经保存的节点)中找出F值最小的,记为minNode,将minNode从数组里删除。


       从minNode开始继续搜索,得到更多的节点保存。反复搜索,直到数组里没有任何节点(注意!搜到了终点不能直接退出!想一想,为什么),搜索结束。
 
 
5、存储及遍历的方式
       如上所述,数组很明显是不行的。我们需要先开一个结构体,用以保存每个节点的x,y,val,dep。val记录F值。
遍历的时候,可以看出,需要使用队列进行出队入队。但是,每次我们需要找到的节点是F值最小的啊!而不是先进先出。所以,之前所提到的“堆”(http://blog.sina.com.cn/s/blog_6022c4720102vss0.html)就有作用啦,而不是只能进行堆排序。堆,在这里其实用它另一个名字更加合适——优先队列。每次,我们从队列中选出F值最小的,很容易就实现了。为了“标新立异”,在Cab的带领下,我用了系统优先队列(这是我第一次用STL中的数据结构。。。。)
-----------------------------------------------------------------------------------------------------
priority_queue<node,vector<node>,cmp> q; 
-----------------------------------------------------------------------------------------------------
       这是优先队列的定义方式,需要queue库,vector库。这里用的是结构体优先队列,之间的“vector<node>”是默认的格式,具体的原因我也不知道。。反正就这么打就可以了,如果需要定义整型的优先队列,把node改成int就行了。cmp就是优先队列比较方式,要自己写,类似于sort(str+1,str+n+1,cmp)。使用方式类似于普通的队列,具体的自己查吧。。。我觉得挺好理解的。STL就是有各种局限。
 
6、代码
       最简单的直角坐标系跑最短路。输入0-1地图以及起点终点。求出需要走多少步。
 
  UPDATE(20180805):今天看了一眼,以前写的是什么东西啊。
 
#include <cstdio>
#include <queue>
#include <cstdlib>
using namespace std;

#define MAXN 105
#define INF 0x3f3f3f3f

int n, m, a[MAXN][MAXN], sx, sy, tx, ty, ans = INF;

struct node {
    int x, y, w, d;
};

struct cmp {
    bool operator () (node a, node b) {
        return a.w < b.w;
    }
};

priority_queue <node, vector<node>, cmp> Q;

int min(int a, int b) {
    return a < b ? a : b; 
}

int abs(int o) {
    return o > 0 ? o : -o;
}

int get(int x, int y) {
    return (abs(x - tx) + abs(y - ty));
}

bool chk(int x, int y) {
    return (x > 0 && y > 0 && x <= n && y <= m && !a[x][y]);
}

void search() {
    Q.push((node) {sx, sy, get(sx, sy), 0}), a[sx][sy] = 1;
    while (!Q.empty()) {
        node o = Q.top();
        if (o.x == tx && o.y == ty) printf("%d", o.d), exit(0);
        for (int vx = -1; vx <= 1; vx++)
            for (int vy = -1; vy <= 1; vy++)
                if (vx != 0 || vy != 0) {
                    int nx = o.x + vx, ny = o.y + vy, nd = o.d + 1;
                    if (chk(nx, ny)) Q.push((node) {nx, ny, get(nx, ny) + nd, nd}), a[nx][ny] = 1;
                }
        Q.pop();
    }
}

int main() {
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) scanf("%d", &a[i][j]);
    scanf("%d %d %d %d", &sx, &sy, &tx, &ty);
    search();
    return 0;
} 
原文地址:https://www.cnblogs.com/jinkun113/p/4678852.html