A*寻路算法探究
A*算法常用在游戏的寻路,是一种静态网路中求解最短路径的搜索方法,也是解决很多搜索问题的算法。相对于Dijkstra,BFS这些算法在复杂的搜索更有效率。本文在U3D中进行代码的测试和验证。
1.原理:
A*通过开启集合和关闭集合对路径点收集并进行启发式函数的过滤和筛选以达到最优解的目的。
一般利用原理公式:f(n)=g(n)+h(n),其中 f(n) 是从初始经由状态n到目标状态的代价估计,g(n) 是在从初始状态到状态n的实际代价,h(n) 是从状态n到目标状态的最佳路径的估计代价。
当f(n)最小的时候则路径最短,为最优解,不排除两路径长度相等。G(n)可通过之前的点的距离累加获得,h(n)则可控制a*算法的筛选行为。
由于每个点到终点的估算费用h(n)是可以获取的,一般用曼哈顿算法。
1.1曼哈顿算法
根据毕达格拉斯(勾股)定理,或者向量理论,都可以知道用可以用直角边表达斜边的长度。
它计算从当前格到目的格之间水平和垂直的方格的数量总和,忽略对角线方向。然后把结果乘以10。这被成为曼哈顿方法是因为它看起来像计算城市中从一个地方到另外一个地方的街区数,在那里你不能沿对角线方向穿过街区。很重要的一点,我们忽略了一切障碍物。这是对剩余距离的一个估算,而非实际值,这也是这一方法被称为启发式的原因。
(为了节省工作量和优化速度:这里我们会假设一般水平或者垂直移动的耗费为10,对角线方向耗费为14。我们取这些值是因为沿对角线的距离是沿水平或垂直移动耗费的的根号2,而使用整数则能进一步减少运行的速度)
1.2极端情况的分析
一种极端情况,如果h(n)是0,则只有g(n)起作用,此时A*演变成Dijkstra算法,这保证能找到最短路径。
如果h(n)经常都比从n移动到目标的实际代价小(或者相等),则A*保证能找到一条最短路径。h(n)越小,A*扩展的结点越多,运行就得越慢。
如果h(n)精确地等于从n移动到目标的代价,则A*将会仅仅寻找最佳路径而不扩展别的任何结点,这会运行得非常快。尽管这不可能在所有情况下发生,你仍可以在一些特殊情况下让它们精确地相等(译者:指让h(n)精确地等于实际值)。只要提供完美的信息,A*会运行得很完美,认识这一点很好。
如果h(n)有时比从n移动到目标的实际代价高,则A*不能保证找到一条最短路径,但它运行得更快。
另一种极端情况,如果h(n)比g(n)大很多,则只有h(n)起作用,A*演变成BFS算法。
2代码:
1,从点A开始,并且把它作为待处理点存入一个“开启列表”。开启列表就像一张购物清单。尽管现在列表里只有一个元素,但以后就会多起来。你的路径可能会通过它包含的方格,也可能不会。基本上,这是一个待检查方格的列表。
2,寻找起点周围所有可到达或者可通过的方格,跳过有墙,水,或其他无法通过地形的方格。也把他们加入开启列表。为所有这些方格保存点A作为“父方格”。当我们想描述路径的时候,父方格的资料是十分重要的。后面会解释它的具体用途
3,从开启列表中删除点A,把它加入到一个“关闭列表”,列表中保存所有不需要再次检查的方格。
4,把它从开启列表中删除,然后添加到关闭列表中。
5,检查所有相邻格子。跳过那些已经在关闭列表中的或者不可通过的(有墙,水的地形,或者其他无法通过的地形),把他们添加进开启列表,如果他们还不在里面的话。把选中的方格作为新的方格的父节点。
6,如果某个相邻格已经在开启列表里了,检查现在的这条路径是否更好。换句话说,检查如果我们用新的路径到达它的话,G值是否会更低一些。如果不是,那就什么都不做。
关键代码:
void FindBestpath(Vector3 StartPos, Vector3 EndPos)
{
Node startnode = _grid.GetFromPosition(StartPos); Node EndNode = _grid.GetFromPosition(EndPos); List<Node> openset = new List<Node>();
HashSet<Node> closet = new HashSet<Node>();
openset.Add(startnode);
startnode.parent = null;
//对openset进行查询
while (openset.Count > )
{
Node currentNode = openset[];
//遍历openset
for (int i = ; i < openset.Count; i++)
{
////找到打开节点中最小的权重的节点
if (openset[i].fCost <= currentNode.fCost && openset[i].hCost < currentNode.hCost)
{
currentNode = openset[i];
}
}
//将这个权重最小的节点移除
openset.Remove(currentNode);
//将他添加到关闭节点中,因为下一个就要打开这个节点,这个节点就不计算了
closet.Add(currentNode); //若当前结点等于终点则结束
if (currentNode == EndNode)
{ GeneratePath(startnode,EndNode);
return;
}
//搜索当前节点走位可以走得节点,然后更新权重值 foreach (var node in _grid.GetNeibourhood(currentNode))
{
//判断是否可通过和不在关闭集合里
if (!node._canWalk || closet.Contains(node)) continue;
//获取Gcost到起点的距离
int newCost = currentNode.Gcost + GetDistantNodes(currentNode,node);
//若关闭集合包含了node,或者新的Gcost小于原本的gcost
if (newCost <= node.Gcost || !openset.Contains(node))
{
node.Gcost = newCost;
//获取hcost到终点的距离
node.hCost = GetDistantNodes(node, EndNode);
//将node的父节点给上一级
node.parent = currentNode;
if (!openset.Contains(node))
{ openset.Add(node);//open集合添加附近node节点 }
} }
}
}
3.关键实现要素点:
开关集合,分割地图的方式:(入门方块,可用其他数据结构替换,六边形,Waypoint等),消费预算(起点的消费和终点的消费)
4.代码实现效果和Demo下载链接:
http://pan.baidu.com/s/1pLLGaQf
5.优化方式:结合二叉树,结合群体人流方案进行变形。
6.参考链接和资料:
参考文章:
http://blog.csdn.net/b2b160/article/details/4057781
百度百科