将Dijkstra和A*算法结合起来介绍,因为他们之间的关联性比较强。简单来说A*算法就是在Dijkstra算法的基础上增加了启发式函数。
怎么做?(实现原理)
首先从广度优先(BFS)算法开始,从程序执行的角度来解释BFS算法的原理,在地图中,维护一个未探索队列queue<location> frontier,和一个已达的unordered map<location, bool> reached,如果队列frontier非空,则进入循环:frontier出队一个元素,在四个方向上计算该元素的邻居(计算过程中对边界及其他约束条件进行判断),如果符合邻居条件则入队frontier,同时为该位置的reached添加<location , true>键值对,表示已访问,防止接下来的计算邻居重复计算相同的位置。该循环执行直至遍历完所有可访问的位置。
queue<location> frontier
unordered map<location, bool> reached
BFS扩展至Dijkstra算法
BFS算法只是无脑的把地图内所有可达的点都走一遍,那如果我们有一个确定的起始点和目标点,需要找到其最短路径,需要在BFS的基础上,为地图添加一个权重(或者叫损失(cost)),每移动相邻一格,权重(损失)为 1。 这样就将问题转移为找一条从起始点到目标点损失和(cost)最小的路径。另外,需要记录路径,用到unordered_map<location, location> came_from 来记录到达的每一个位置是从哪个位置来的。这样就能在到达目标点时,从目标点不断回溯came_from来找到一条从起始点到目标点的路径。前面还提到BFS无脑搜索所有可达位置,我们需要在其到达目标点后即刻停止搜索,因此简单添加一个识别条件(if (current == goal)break;)跳出循环。
unordered_map<location, location> came_from
unordered_map<location, double> cost_so_far
移动成本的计算表示为一个函数cost(from_node, to_node),一般两个相邻的可达位置之间移动成本为1,也可设置一些复杂环境(移动成本不为1)。添加一个unordered_map<Location, double>& cost_so_far,来记录每一个位置的总移动成本。在每一次移动中,我们需要查找移动成本最低的一个相邻位置进行移动,即从frontier队列中,找一个能使cost(current,frontier)中的位置)返回值最小的位置。可以用到priority_queue这一容器,使用自定义的判别规则,使cost_so_far最小的位置按照判别规则入队时在队首,执行.top()即可取出最小成本的位置,然后在该位置除对周围邻居位置,遍历判断新的位置的成本(是否比cost_so_far中存储的键值对更小 || 是否存在在cost_so_far中),有则更新cost_so_far键值对,更新came_from键值对,并将该位置加入frontier队列中。循环直到到达目标点。
Priority_queue<loaction,double> frontier
Dijkstra算法扩展至A*算法
Dijkstra算法中已经包含了几乎所有路径规划需要的数据结构和容器,A*只是在其基础上添加了一个启发式函数heuristic(next,goal)。Dijkstra中frontier存储的成本为cost_so_far。A*中将frontier存储的成本值改为
double Priority = cost_so_far+heuristic(next,goal)
//Priority值存入frontier中 代替cost_so_far进行比较
Priority_queue<loaction,double> frontier
。该启发式函数为当前点next到目标点goal的曼哈顿距离。
为什么?(三者的区别,为何做出这些改进?)
在怎么做部分简单说了一下三者的递进关系。首先BFS是基础,无脑搜索整个地图,Dijkstra加入权重,存储一个名为cost_so_far 的unordered_map<location, double>。对已达的位置的cost和cost_so_far进行判断,更新新的位置。但总的来说,Dijkstra的新方向还是依据最小成本更新,仿佛是一个低着头的人,只将目光聚焦于周围的格子,只朝着最好走的格子前进。A*算法引入了启发式算法,使路径搜索更具目标性,就像是低着头走的人把头抬了起来,看见了目标点,每次走都朝着目标点的方向。