一、前言
在正式介绍这篇寻路算法文章之前,我想先简单讲讲两个经典的算法:深度优先搜索算法和广度优先搜索算法。
二、深度优先搜索
深度优先搜索(Depth-First-Search,DFS),是一种用于遍历搜索树或图的算法。这个算法会尽可能深的搜索树的分支。我们一般使用堆数据结构来辅助实现 DFS 算法。
我们来看看 wiki 上的定义:
这个算法会尽可能深的搜索树的分支。当节点
v的所在边都已被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。
简单来说,
- 访问顶点
v - 依次从
v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问 - 若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止
如下:
1
2 3
4 5 6 7
8
根据搜索规则,我们首先访问顶点 1,然后寻找其邻接点 2 ,再寻找 2 的邻接点 4,依次类推。当找到 8 时,没有邻接点,这时就返回到上一节点 4 查找是否还有未访问到的节点,没有则再往上查找 2 是否有未被访问的节点。5 则被访问到。最后的搜索路径为:1 —> 2 —> 4 —> 8 —> 5 —> 3 —> 6 —> 7。
如下图所示:
备注:上图中的箭头有一定的误导性,晚点再改一下(可以根据上面提供的数字顺序做对照)。
三、广度优先搜索
广度优先搜索(Breadth-First-Search, BFS),是一种图形搜索算法。根据它的特点,我们一般使用队列来辅助实现 BFS 算法。
它的主要实现思路为:
- 将根节点放入队列中
- 从队列中取出第一个节点,并检验它是否为目标。如果找到目标则返回搜索结果。否则将它所有尚未检验过的直接子节点加入到队列中
- 若队列为空,则返回
- 重复步骤 2
其实它就是我们熟知的横向优先搜索。它的搜索路径如下:
BFS 有非常多的应用,在 这篇文章中 ,winter 老师也使用它来搜索计算过浏览器中有多少 JavaScript 固有对象。
四、寻路算法
看完了前面两个算法的介绍相信你对深度和广度优先搜索算法已经有了大概的了解了。对于我们的寻路问题,我们需要"就近"的去查找是否有符合要求的点,所以我们选择的是广度优先搜索。
具体查找思路为:
- 指定某一起始点
v,放入队列 - 从队列中取出第一个节点,检验它是否为目标节点。如果是,则返回搜索结果。否则查找它的"上"、"下"、"左"、"右"节点是否为目标点。
- 如果队列为空,则返回
- 重复步骤 2
根据以上搜索方法,我们可以查询到指定目标节点。以下是查询到的路径图:
从上图的路径中我们可以看出,这种广度优先搜索是一个非常"笨"的搜索方法。它不会去根据某种策略来规划路线,导致它总会花费非常多的时间搜索没有必要的区域。如果我们的搜索算法算法可以根据起始点和终止点之间的距离直接计算出路径路径就好了。
(1)启发式(A*)寻路
我们在广度优先算法中进行改造一下:我们让2. 从队列取出第一个节点 这一步在取出时拿到的值为队列中最小的那个,每次拿值的时候都这样做。这样就能保证我们查询的路径能"有策略"的寻路。
如果你还没有明白,没关系,我们来举个栗子:
我们现在要从下图的 a 点走到 b 点。那寻路的步骤就是:
- 首先,将 a 节点放入队列中
- 取出 a 节点,a 不是目标节点,则将 a 的"子节点" "上" "下" "左" "右" 分别放入队列中
- 取出某一节点,该节点为离 b 最近的节点。所以是图中的节点 "1"。
- 重复上述步骤
最后的路径就如下图(绿色为其判断的子节点,紫色为路径):
相比于广度优先搜索,其实启发式寻路就是在此基础上完善了一下获取值的方法:每次拿到的点都是给定两点最短距离的点。当然,代码中给出的方法仅仅是其中一条思路,我们还可以使用其它不同的方法来做最短路径判断,比如最小二叉堆。
好了,本文的核心的内容到这里就结束了。欢迎你和我一起交流讨论~
OS:macOS Catalina 10.15.7
Browser Version: Chrome 87.0.4280.88(正式版本) (x86_64)