[旧版][知识点]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*必须将估价函数定义得足够合理。下面具体介绍估价函数。
定义一个直角坐标系,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; }