概述
其实A*算法用来尝试解决的问题很简单,就是从源点移动到目的地,所花费的代价最小,找到这样一条路径,这个代价可以是时间,距离,金钱等。我们找路径时,关注的重点不再只是,找他去终点的最短路径,而是要综合考虑,路径长度和路径算法两个方面,简单的讲就是路径排序的依据不仅仅是路径长度,而是依据 L + P
这里,
L = 源点到终点的移动成本,也就是最短路径长度。
P = 该点到终点的预估成本,也就是试探,为什么说是试探呢,因为这个P只是猜测,真正的距离只有我们真的去遍历之后才会知道,本文会介绍两种计算H的方法,当然你也可以去谷歌找到更多的方法。
启发式函数
P的求取,重点是选区优秀的启发式函数,启发式函数h,告诉了我们从任意结点n到目标结点的最小代价评估值,选择一个优秀的启发函数是A*算法的关键。
那么一般会用的启发式函数是什么呢?
-
曼哈顿距离
对于一般的网格来讲,曼哈顿距离是一个十分合适并且简单的启发函数,所谓曼哈顿距离,就是计算两点距离时,如果用一个直角三角形ABC, C为直角顶点来表示的话,欧几里得距离就是直角边c,曼哈顿距离就是两直角边之和(a + b),计算起来就仅仅是横坐标差的绝对值与纵坐标差的绝对值相加。 -
目标点到终点的最短路径长度
如果我们不在网格而是在图中,或者曼哈顿距离的准确度不够,我们可以试试用最短路径长度做启发式函数,也就是说我们要提前用一个函数计算出任意两顶点之间的最短路径,(不难发现这会浪费很多的时间),我们可以用SPFA,Floyd等算法。
这里我们也不难发现,如果启发式函数h = 0,那么A*算法也就退化成了简单的Dijkstra算法,所以说重点就是在启发式函数的选取,至于操作部分就和Dijkstra区别不大了。
操作
(1)初始化:S中只包含源点u,U中包含除源点外的所有点,dist中与u相连的边如<u,v>,dist[v]设置为<u,v>的权值 (dist[u] = 0, 其余未连接的顶点的dist设置为INF,即无穷大),path[u] = v(其它未连接的点path均设置为-1)。
(2)从S中选择一个顶点s,U中选择一个顶点p,使得<s,p>是最小的,然后将U中的p加入到S中。
(3)更新S中的点的最短距离,即更新dist, path数组,(注意dist,数组中记录的最短路径长度和path数组中记录的最短路径都是当前条件下的最短路径,并非全局的最短路径,随着U中新顶点的不断加入,不断更新dist,path中的最短路径,当遍历完图中所有顶点,得到的就是源点到其它顶点的最短路径)。
更新方法:因为<s,u>是我们找到的最短的那条边,所以我们只需要判断(<u,v>的权值 + h(u))与(<u,p> + h(u)) + (<p,v> + hp)的权值哪个更小,若新加入的p使得源点u到顶点v的距离缩短了,即(<u,p> + h(u) + <p,v> + hp) < (<u,v> + h(u)),那么我们就更新dist,path中关于v的数据。
(4)循环重复(2),(3)的操作,直到U中所有的顶点都加入到S中。
所以其实A*操作起来很简单,就是把Dijkstra里面的判断标准dist改为(dist + h)即可,读者可参照另一篇Dijkstra的博客
这里的代码则是基于BFS的一种思路。
代码实现
数据结构
typedef struct node {
int data;
int g, h;
int parent;
}node;
typedef struct {
int data;
int parent;
}QUERE1; //队列的数据结构
typedef struct ANode {
int adjvex;
struct ANode *nextarc;
int weight;
}ArcNode; //边结点的类型
typedef struct Vnode {
int info;
ArcNode *firstarc;
}VNode;
typedef struct {
VNode adjlist[MAXV];
int n, e;
}AdjGraph;
算法实现
void Astar(AdjGraph G, int start, int end) {
int i, w;
int path1[MAXV];
int count = 0;
ArcNode *p;
node qu[MAXV]; //基于BFS使用的队列
int front = -1; int rear = -1;
int visited_Astar[MAXV] = { 0 };
rear++;
qu[rear].data = start;
qu[rear].parent = -1;
visited_Astar[start] = 1;
while (front != rear) {
front++;
w = qu[front].data;
if (w == end) {
i = front;
while (qu[i].parent != -1)
{
path1[count] = qu[i].data;
count++;
i = qu[i].parent;
}
path1[count] = qu[i].data;
int m = count;
for (int k = 0; k < m; k++) {
path_Astar[k] = path1[count];
count--;
}
path_Astar[m] = end;
return;
}
p = G.adjlist[w].firstarc;
int minF = qu[p->adjvex].g + qu[p->adjvex].h;
while (p != NULL) {
if (visited_Astar[p->adjvex] == 0) {
visited_Astar[p->adjvex] = 1;
rear++;
qu[rear].data = p->adjvex;
qu[rear].g = 0;
qu[rear].g += p->weight;
qu[rear].parent = front;
qu[rear].h = SPFA(G, qu[rear].data, end);
}
p = p->nextarc;
}
for (int i = front + 1; i <= rear - 1; i++) {
if (qu[i].g + qu[i].h > qu[i + 1].g + qu[i + 1].h) {
node tmp = qu[i];
qu[i] = qu[i + 1];
qu[i + 1] = tmp;
}
}
}
}
关于代码中使用的启发式函数h,这里采用的SPFA算法,如果想使用曼哈顿距离或其它,只需修改代码中的spfa为h,h为自己编写的启发式函数。