寻路算法广度优先搜索与迪杰斯特拉算法

Posted 古怪便利店

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了寻路算法广度优先搜索与迪杰斯特拉算法相关的知识,希望对你有一定的参考价值。

我小时候特别喜欢画迷宫玩,有时候偷偷在课堂上画一幅超大的迷宫,然后给小伙伴们玩。不过可惜的是我只擅长(瞎)画迷宫,但不擅长解迷宫,所以自己画的迷宫到底有几条出口、哪条是最短的其实我自己也不知道。


后来有段时间玩梦幻西游,游戏有个找 NPC 的功能,选择一个 NPC,角色就自己往 NPC 那边跑了,小时候特别好奇,路上那么多房子和死胡同,他怎么就知道要绕路,还能找到一条几乎是最短的路出来的?后来才明白,原来这里面涉及到一些 寻路算法


今天介绍一下两种常见的寻路算法,分别是 广度优先算法(Breadth First Search) 和 迪杰斯特拉算法(Dijkstra's Algorithm)。另外一种更广为人知的 A* 寻路算法 我们下期再说。


抛开游戏里面精致的画面,其实寻路算法本质上是一种  的搜索算法,游戏地图里面哪些可以走、哪些是墙、哪些走起来会慢一点,这些都可以抽象成图的一个个节点。我们先用一个最简单的数组来表示:点 . 代表该位置可走,W 代表该位置不可走。



可以想象这是一个 5 * 5 的迷宫,每个节点可以通过上下左右四个方向移动一个距离,大致就像下面这样,我们以左上角为坐标轴的原点来描述。


寻路算法(一)广度优先搜索与迪杰斯特拉算法


广度优先算法(Breadth First Search)


广度优先算法是最简单也是最直观的寻路算法,顾名思义,尽可能广地寻找终点,与其说是寻路算法,不如说只是从起点开始遍历地图来找到终点。


寻找终点


首先我们需要做的是把上面代表迷宫的数据解析为一个图结构。每一个格子代表一个节点,这个图提供一个方法 getNeighbors,用来获取指定格子周围的 邻居,邻居代表可以移动的格子,所以不会返回墙。


我们通过维护一个队列 frontier,来存放将要进行探索的节点。从起点开始,如果他的邻居有还没有探索过的节点,就将该邻居放进队列,直到所有可达的节点探索完。


寻路算法(一)广度优先搜索与迪杰斯特拉算法


寻路算法(一)广度优先搜索与迪杰斯特拉算法


完整代码见 https://github.com/sunhengzhe/path-finding/blob/master/Breadth-First-Search/a.graph-search.js


寻找路线


由此迷宫的所有 可达 的格子都会被遍历一遍,只要终点可达,那么一定会存放在 visited 字典里。那么怎么求出起点到终点的路线呢?既然我们能从起点到达终点,那我们当然能存放中间经过的节点了。


这里利用链表或者字典都可以,这里使用了字典来存放,comeFrom 字典的 key -> value 代表 key 节点是从 value 节点到达的。所以我们只需要在往 frontier 中入列的时候,顺便把节点间的关系存放一下就行了。


寻路算法(一)广度优先搜索与迪杰斯特拉算法


寻路算法(一)广度优先搜索与迪杰斯特拉算法


完整代码见 https://github.com/sunhengzhe/path-finding/blob/master/Breadth-First-Search/b.find-path.js


提前结束


至此路线也能求出来了,但是有个小问题,当我们找到终点的时候,遍历还在继续,剩余的地方实际上没有必要继续探索了,因为我们已经拿到了最短路径。需要判断一下,当当前节点为终点时终止循环。


寻路算法(一)广度优先搜索与迪杰斯特拉算法


迪杰斯特拉算法(Dijkstra's Algorithm)


广度优先算法虽然简单,但是局限性也比较大,特别是它假定了路和路之间的代价是相同的。但在游戏中我们一般会有很多种地形,比如在石板路上走路是最快的,在草地上可能慢一点,在山地、沼泽里最慢。那么这里不能简单实用 BFS 来寻路了,我们需要引入 权重 来代表每一种地形的代价。


首先我们使用 / 来表示山地,在 . 中移动需要花费 1 的代价,但是在 / 中需要花费 5


寻路算法(一)广度优先搜索与迪杰斯特拉算法


寻路算法(一)广度优先搜索与迪杰斯特拉算法


我们引入一个 costSoFar 字典,key -> value 代表到达节点 key 需要花费 value 的代价。


另外我们需要走得快的格子要先走,如果还是简单地把所有邻居同时推到队列里,那么有山地的地方可能提前到达终点,反而让代价少的路没有走到。所以我们需要引入 优先队列 的数据结构。


优先队列在普通队列的先进先出的基础上增加了 优先级 的概念,优先级高的元素会先出列。优先队列的实现是另外一个话题,在此不详细阐述。因为我们希望代价低的路先走,所以我们使用 最小堆 来实现优先队列,具体代码可以参考 https://github.com/sunhengzhe/path-finding/blob/master/Dijkstra's-algorithm/priorityQueue.js


改写关键代码如下:



最后形成的图如下,节点的数字代表到达节点的最小代价。



启发式寻路算法


还有像 A* 寻路算法 之类的算法属于启发式算法,我们下次再聊。

以上是关于寻路算法广度优先搜索与迪杰斯特拉算法的主要内容,如果未能解决你的问题,请参考以下文章

迪杰斯特拉Dijkstra算法介绍

图论——迪杰斯特拉算法和最小生成树

数据结构之最短路径 [迪杰斯特拉算法]

最短路径---迪杰斯特拉算法[图中一个顶点到其他顶点的最短距离]

迪杰斯特拉算法_优化版

求多重邻接表的迪杰斯特拉算法