一、基础:二叉树的遍历->图的遍历
提到搜索算法,就不得不说两个最基础的思想:
BFS(Breadth First Search)广度优先搜索
DFS(Depth First Search)深度优先搜索
刚开始是在二叉树遍历中接触这两个算法,从中文名字上很好理解,广度优先就是优先搜索横向结点,深度优先就是优先纵向搜索根结点。用一个示意图Fg.1和Fg.2来表示
BFS输出:0 1 2 3 4 5
DFS输出:0 1 3 4 2 5
从二叉树过渡到图有两个变化,以有向图为例子:每个节点的子节点不一定是两个,节点间有方向性(如Fg.3)。
图的遍历要注意不要重复遍历节点。依然可以用BFS和DFS来做,用一个数组来储存每个节点是否被遍历(比如已经遍历置为true,没有遍历的为false)。BFS使用队列,DFS使用栈,详细的在这里不多说。
二、进阶:最短路径Dijkstra算法
如果我们在考虑最优路径的问题,图比Fg.3的有向图要复杂一些。
比如一个城市有很多镇,每个镇之间有多条路径可以到达,A镇到D镇没有直达路,要从A走到D,可能要经过B,也可能经过C,我们通常会选择最短的距离作为最优路径,这就要求我们在图中引入权值,且对于路径问题来说,权值一定为正数(如Fg.4)。
Dijkstra可以算出起始点到任意点的最短距离。它其实是BFS的加权版,引入了贪心算法的思想,从起始位置开始遍历子节点,每次都要选最短的路径走,直到遍历到目标位置。从局部最优到全局最优。
Dijkstra需要维护一个从起点到终点到路径及距离(权值)表格,可以用二维数组来存储。因为每个点都需要遍历到其他点的路径来更新表格,所以算法复杂度O(n2)。
三、启发式的搜索:最佳优先搜索 & A*算法
现实中找路也许更加复杂。假设我是一个路痴(其实我就是),我不知道A镇到D镇要走哪个方向,难道要一圈一圈扩大搜索范围来找吗?这时候如果使用Dijkstra算法,我需要从红色起点向周围扩展8个格子,再分别考虑这8个格子周围的8个格子,太慢了! 所以启发式搜索登场了。
如Fg.5所示,我站在红色的起点,但我不是茫然无措的,我还知道终点在哪,所谓启发,就是我的搜索是有方向性的。在此介绍最佳优先搜索(Greedy Best First Search)和A*算法。
那么我到底要往哪里走?最佳优先搜索和A*算法都引入了一个启发函数。
最佳优先算法:
F
=
H
最佳优先算法:F=H
最佳优先算法:F=H
A
∗
算法:
F
=
G
+
H
A*算法:F=G+H
A∗算法:F=G+H
两种算法每次都是选取F最小的值。在这个等式中,G是到起点的距离,为确定值;H是到目标点的距离,为预测值。为了方便计算,我们将这些值扩大10倍。
G比较好理解,比如从起点向右走一格,G值为10(即1x10),向斜上方走一格,G值为14(即
2
\sqrt{2}
2x10)。
H即当前位置到终点的距离,有多种预测方法。在此我们展示Manhattan方法(使用曼哈顿距离),即忽略障碍物,考虑起点到终点的最短位移(只能往水平或竖直方向走),再扩大10倍得到H预测值。起点周围的方格的H值如Fg.6所示。
以A*算法为例,最后我们就可以计算出F值,如Fg.7所示,起点正右侧的格子F=G+H=10+50=60
它也是当前检索方块中F值最小的方块,就加入到路径中,如图Fg.8所示,然后再去探索当前粉色位置周围的F值(如果遇到障碍物,则忽略障碍物格子)。依次检索,直到终点。
可以看到最佳优先算法和A算法都是对Dijkstra算法的优化,引入启发函数使计算量大大减少,当H值为0的时候,最佳优先算法和A就退化成了Dijkstra算法。
小结:最佳优先算法和A算法是需要先验知识的,即起点和终点并距离信息,这些信息可以通过路径规划前的扫描建图来获取。A是目前比较优秀的最短路径搜索算法。