A*算法是用于寻找两点之间的最短路径,同时它也是一种静态路网中求解最短路最有效的直搜索方法,公式f(n)=h(n)+g(n)给出了邻居节点到目标节点所需要的总消耗成本,h(n)是当前节点到该邻居节点的所消耗的成本,g(n)是该邻居节点到目标节点的估计消耗成本,比较常用的估计方法是欧几里得方法和曼哈顿方法。
A*算法首先要准备两个列表,一个开启列表,一个关闭列表,开启列表存储还未走的节点(但不是一开始就把所有节点加入开启列表),关闭列表存储走过的节点,将起始节点加入到开启列表,在开启列表不为空时,取出开启列表中最小消耗成本的节点,判断是否是目标节点,是就直接结束A*,取得它的周围邻居节点(通常是8个),判断邻居节点是否在关闭列表中,在关闭列表中,不做处理,不在关闭列表中,再判断是否在开启列表中,若已在开启列表中,计算它的f(n),是否比以前的f(n)小,小就更新它的f(n)并将它的父节点设为当前节点。大的话就不做处理,若不在开启列表中,计算它的f(n),更新f(n)将父节点设为当前节点。处理完它的周围邻居节点后,一轮循环结束,再移除开启列表中的当前节点,放在关闭列表中,因为已经走过这个节点。然后在开启列表不为空时继续上述循环,直到找到目标节点,或者找遍所有节点都找不到目标节点。C++代码如下(相关函数并未实现,视实际情况而定):
1 typedef struct node 2 { 3 float currentCost;//h(n) 4 float toTalCost;//f(n) 5 node * parent;//父节点 6 }Node; 7 Vector<Node> OpenList;//开启列表 8 Vector<Node> CloseList;//关闭列表 9 10 Node AStar(Node start,Node Goal){ 11 OpenList.Add(start); 12 while(OpenList.Length!=0) 13 { 14 Node* minNode= OpenLIst.Min();//Min()选出最小消耗成本节点 15 if(minNode == Goal) 16 { 17 return minNode; 18 } 19 Vector<Node> Neighbors;//邻居列表 20 GetNeighBors(Neighbors,minNode);/*得到最小消耗成本节点的周围邻居节点*/ 21 for(int i=0;i<NeighBors.Count;i++) 22 { 23 if(Neighbors[i].obstacle)//如果邻居节点被标志障碍物,跳过。 24 { 25 continue;/*也可以在GetNeighBors函数里进行处理,障碍物节点不加入邻居列表*/ 26 } 27 if(CloseList.Contain(Neighbors[i]))//节点已经走过不作处理 28 continue; 29 float currentCost=CacluateDistance(minNode,Neighbors[i])+minNode.currentCost; 30 float totalCost = currentCost+CacluateDistance(Neighbors[i],Goal); 31 if(OpenList.Cotain(Neighbors[i]))//如果开启列表包含该邻居节点 32 { 33 34 if(totalCost<Neighbors[i].totalCost) 35 { 36 Neighbors[i].totalCost=totalCost; 37 Neighbors[i].currentCost =currentCost; 38 Neighbors[i].parent=minNode; 39 } 40 } 41 else 42 { 43 Neighbors[i].totalCost=totalCost; 44 Neighbors[i].currentCost =currentCost; 45 Neighbors[i].parent=minNode; 46 } 47 } 48 OpenList.Remove(minNode); 49 CloseList.Add(minNode); 50 } 51 return NULL;//找不到目标节点,返回空 52 }
若成功返回一个节点,那么可以不断往回迭代父节点得到一条逆向的最短路径。A*的思想是为每个节点都估价消耗成本(沿当前所走的路径到目标节点的消耗成本),选择最小消耗成本的节点优先计算,就使节点搜索沿目标节点那边搜索,也被称之为启发式搜索。而开启列表得到最小的消耗成本节点或添加节点,应该用二叉堆(最小堆)插入,这样取最小节点时间复杂度为O(1),添加节点时间复杂度为O(log(n)),比另一种遍历取节点O(n),加节点O(1)好一些。