作为程序员的我们可能经常会听到 A*搜索算法这个的词,听起来非常高大上,腻害,但是具体是什么呢?
引用 Wiki 上的说法就是:
A* 搜索算法(A* search algorithm)是一种在图形平面上,有多个节点的路径,求出最低通过成本的算法。常用于游戏中的NPC的移动计算,或网络游戏的BOT的移动计算上。
该算法综合了最良优先搜索和Dijkstra算法的优点:在进行启发式搜索提高算法效率的同时,可以保证找到一条最优路径(基于评估函数)。
在此算法中,如果以 g ( n ) g(n) g(n) 表示从起点到当前顶点 n n n 的实际距离, h ( n ) h(n) h(n)表示当前顶点 n n n 到目标顶点的估算距离(根据所采用的评估函数的不同而变化),那么A*算法的估算函数为:
f ( n ) = g ( n ) + h ( n ) f(n)=g(n)+h(n) f(n)=g(n)+h(n)
f ( n ) f(n) f(n):代表了该节点的综合预估值,值越小,到达目标的成本就越小,所以访问的时候尽量优先考虑最小的。
这里的 h ( n ) h(n) h(n)值可以采用 欧几里得距离、曼哈顿距离、切比雪夫距离等公式计算而来。
具体分析如下:
如图:
这里以 绿色(2,1) 为起点, 红色(2,5) 为终点, 蓝色 格子为障碍物,这里的邻居节点
只考虑上下左右4个四个方向。
路径搜索过程如下:
1. 首先需要创建两个集合,一个存储待访问
的节点(nodeLists),一个存储已经访问过
的节点(visitedNodeLists)
2. 添加 起点(2,1)
到 nodeLists
列表,并且计算该点的预估值
。
这里就需要用到上面讲到的预估函数了,
f
(
n
)
=
g
(
n
)
+
h
(
n
)
f(n)=g(n)+h(n)
f(n)=g(n)+h(n)
我们可以很清楚的看到,起点到当前节点的距离为0,即:
g
(
n
)
=
0
g(n)=0
g(n)=0。
而当前顶点(2,1)到目标顶点(2,5)的距离为4,即:
h
(
n
)
=
4
h(n)=4
h(n)=4。
这里的h值可以采用 曼哈顿距离公式 计算得来:
h
=
∣
x
1
−
x
2
∣
+
∣
y
1
−
y
2
∣
h = |x_1-x_2|+|y_1-y_2|
h=∣x1−x2∣+∣y1−y2∣
这里需要注意的是:
h
h
h 值是在不考虑障碍
的情况下计算的
3. 查找nodeLists 里预估值最小的节点,作为当前访问的节点,并且从nodeLists 删除 该节点。
4. 获取 当前节点 的 邻居节点,计算出他们的预估值 并且添加到nodeLists列表中。
如图:
这里只考虑上下左右4个方位,并且分别计算了他们的各个预估值,
右下角:
h
(
n
)
h(n)
h(n)值
左下角:
g
(
n
)
g(n)
g(n)值
左上角:
f
(
n
)
f(n)
f(n)值
除了计算预估值,我们还需要把当前节点作为每个邻居节点的父节点,以便为了后面确定最终的路线。
这里需要注意的是:我们在添加到 待访问列表之前需要处理一下边界问题,并且判断一下是否是障碍物,如果是就不需要添加到列表中。
5. 把当前节点添加到visitedNodeLists中,代表已经访问过了。
6. 重复以上步骤 3 - 5 ,直到找到目标节点位置为止。
7. 循环输出 最终节点的父节点,就是我们需要的路径了。
以上就是该算法的大致流程。
伪代码如下:
function startSearch(){
//把起点添加到nodeLists
nodeLists.push(startNode);
//循环
while (nodeLists.length > 0) {
//查找预估值最小的节点
let currentNode = findMinNode();
//获取并且添加邻居节点到待访问列表
findAndAddNeighborNode(currentNode);
//把当前节点添加到已访问列表
visitedNodeLists.push(currentNode);
//判断是否是目标节点
if (this.isTarget(currentNode)) {
return currentNode
}
}
return null
}