这一周的视频课主要讲述的是广度优先算法和 A * 算法。
广度优先遍历 (BFS)
以上图为例,要实现网格地图下最短路径的搜索,并且不能越过途中橙色区域的障碍物。
每一次从当前位置可以进行上下左右四个方向的运动(图中绿色部分),从起始点开始会有四个绿色方块,然后依照广度优先算法,会标记绿色区域为 “边界”,再以边界为当前位置,依次朝他们的上下左右方向探索。
以此类推,直至探索到目标点。在探索过程中,会记录下探索方块的来向,如图:
比如下图中,绿色方块上的白色箭头就代表了上一个方块的位置:
所有探索过的路径,我们会标记他为灰色,这样在进行算法实现的时候,我们就不会访问曾经已经探索过的区域:
广度优先算法显然可以帮我们找到最短路径,但有一些 “傻”,因为这样的路径探索是没有方向性的,在最坏的情况下,算法要跑完整个地图才能找到最短路径,如图:
A * 算法
与广度优先算法所不同的是,我们在探索地图的时候,不会去探索所有的” 边界方块 “,也就是上面图中的绿色方块,我们会去探索代价最低的方块:
这里的代价(其实和代价地图的代价的意思差不多)代表两个含义,或者说是由两部分代价组成的,一个是 F-cost(当前路径代价),比如你从起点出发,一共走过多少个格子,当前代价就为多少(这是在此例子中的一种简单表述),比如下图中箭头所指位置是走 3 个格子后到达的,当前路径代价就是 3:
另一部分的代价我们称为 G-cost(预估代价),他用来表示从当前方块到终点方块大概需要走多少步,就像名字一样,预估代价,这代表预估量并不是精确的数值,比如数值为 5,并不代表一定会走 5 步到达目标点,这只是一个大概的估计值,不过我们会用这个估计值去指导算法优先搜索更有希望的路径。最常用到的预估距离有【欧拉距离】,在视频课里也称为【欧几里得距离】,也就是我们通常认为的两者之间的直线距离:
还有更容易计算的曼哈顿距离,也就是两点在垂直方向和水平方向的距离总和。曼哈顿距离的计算不用开方,较为简便,我们先以他作为 “预估代价”:
以这个简单例子为例,在第一轮搜索中,算法对起点周围的四个方块进行探索,计算出” 当前路径代价 “和” 预估代价 “ (图中的 X+Y 形式),在右边的这个方块显然代价最低,1+4=5<1+6=7,在下一轮搜索中就会以他为目标进行搜索:
以此类推,会一直搜索代价最小的方块:
A * 代码实现部分
frontier = PriorityQueue () 存放这一轮中所有探索过的边界方块,也就是图中的绿色方块,另外他是一个优先队列,也就是说,他能够通过” 代价 “自动排序
frontier.put (start , 0) 存放起点
came_from = {} 这是从当前方块到之前方块的映射,代表路径的来向
cost_so_far = {} 代表方块的当前路径代价
came_from [start] = None 和 cost_so_far [start] = 0 将起点的路径来向置空,并将代价设置为 0;
接下来只要 frontier 队列不为空,循环就会一直进行下去,每一次循环优先抽取队列中代价最低的方块,然后检测这个方块是不是终点,如果是,算法结束,如果不是则继续下去,接下来会对这个方块的上下左右相邻块(也就是上图中的 next)进行如下操作:
1. 算法会先去计算这个 next 方块的新代价
2. 更新当前路径代价为之前的代价 + current 到 next 块的代价,以图中的方格地图为例,current 到 next 的代价就是 1
3. 然后只要 next 块没有被探测过,或当前路径代价比之前找到的还要低,就把他加入优先队列中,并且总代价等于当前路径代价 + 预估代价。
视频在某哔搜A*应该就能找到。
贴上能帮助理解的网站 https://www.redblobgames.com/pathfinding/a-star/introduction.html
说说个人理解 A * 里面关键的部分吧,如视频课程中所说 "代价" 其实分两部分组成,一个是 "当前代价" 一个是 "预估代价",而他们关键的作用是:"当前代价" 最为重要的一点是当搜索 "当前最优点" 附近的未被搜索过的节点数为 0 时,这时就该考虑这个 "当前最优点" 是否合适了,这时就以这个 "当前代价" 为参考标准,看是否有更小的 "当前代价" 值了,若有,则将这个点替换为 "当前最优点"