回顾
BFS 能找到最短路径,而 DFS 却不能。
在效率上,DFS 对于稀疏的图形来说更糟糕。想象一下一个有 10000 个节点的图,所有节点都很稀疏。我们最终会进行 10000 次递归调用,这对空间很不利。
BFS 对于 "杂乱 "的图来说更糟糕,因为我们的队列会被大量使用。
但是,有些图的边带有权重,这时我们就需要新的方法来寻找最短路。
Dijkstra's
思考两个问题:
1. 图中从某一点到另一点的最短路径。
2. 图中从某一点开始遍历所有点的最短路径。
在无权重时,BFS能很好解决问题1.
问题2
从起点s开始的最短路径树可以以如下方式创建:
1. 对图中的每一个点v,找到从s到v的最短路。
2. 合并你上述找到的所有边。
注意到最短路径树永远是一棵树。有v个结点的树拥有v-1条边。除根节点外,对于每个节点,在edgeTo array上都有一个父节点。
Dijkstra概述:
它输入一个节点s,输出从s开始的最短路径树。
1. 创建一个优先队列priority queue
2. 将s以优先级0加入到优先队列中。将所有其他节点以优先级∞加入到优先队列中。
3. 当优先队列不是空的:将一个节点弹出优先队列,然后释放(relax)所有以该点为起点的有向边。
释放是什么意思?
假设我们从优先队列里去除的顶点是v,我们看v的所有边,比如说(v,w),我们试着释放这条边:
看当前从根节点source到w的最佳距离curBestDistToW 和 curBestDistToV + weight(v,w),称为potentialDistToWUsingV.
potentialDistToWUsingV是否更好,或者说是否小于curBestDistToW?如果是,将curBestDistToW设置成=potentialDistToWUsingV,并且将edgeTo[w]更新为v。(edgeTo应该指向遍历顺序中的上一个节点)
注意:从不释放已经访问过的节点的边。
伪代码:
def dijkstras(source):
PQ.add(source, 0)
For all other vertices, v, PQ.add(v, infinity)
while PQ is not empty:
p = PQ.removeSmallest()
relax(all edges from p)
def relax(edge p,q):
if q is visited (i.e., q is not in PQ):
return
if distTo[p] + weight(edge) < distTo[q]:
distTo[q] = distTo[p] + w
edgeTo[q] = p
PQ.changePriority(q, distTo[q])
只要边都是非负的,Dijkstra 的方法就能保证最优。
图解
例:
带权图。
为了防止循环,我们需要数组记录.
其中distTo为该点距开始节点的最短距离,edgeTo为在目前的最小生成树中指向该节点的上一个节点。
首先,我们把所有可供选择的节点加入Fringe队列(最小优先队列)中,括号里第一个元素是节点标号,第二个是距开始节点的最短距离(一开始都为无穷)。
对从0出发的两个边分别做relax。1、2两节点的edgeTo更新(为上一节点即0)。2<∞,1<∞,故将2、1两节点距离distTo更新,优先级fringe更新(数值与distTo相等)。
然后,2的优先级(为1)最低,将2弹出(pop)优先队列fringe
在从2继续,释放(relax)从2起始的边(2,5)
1的优先级最低,弹出(pop)节点1
relax三条边。最下面:5>1,不需更新。其他节点需要更新。
4的优先级最低,pop4。relax三条边,(4,2) 5+1>1,不需要替换。其他两个< 替换。
5的优先级最低,pop5
...
注意:
总是按照与源点的总距离顺序排列顶点。
在访问过(白色)顶点的边上,relaxation 总是失败。
负数边
如果边的权重出现负数,Dijkstra就不能总是找到最佳路径。这时可以试用belmanFord等方法。
A*
一个起点的前提下,如果我们只有一个目标,在起点和终点相距非常远的大图下Dijkstra's 会非常非常慢,因为Dijkstra's是以BFS为基础的,需要很长时间才能挖到足够深的地方。
在Dijkstra的基础上,我们在计算优先级的时候加一个数据,这个数据估算各个节点到终点的大致距离。
这组数据 h(v,goal) 称为启发式(Heuristics),它的生成可以使用各种方法(例如具体图片上两点之间直线距离,这里省略),但注意它只是估算,所以不必过于复杂精确。加上权重过后,A*树能很高效地找到两个确定点之间的最短路。
A*的特点:
1. A*并不访问所有节点。
2. 结果不再是有效的最短路径树。
启发式生成的好坏直接决定A*算法的效率。总而言之,好的启发式需要以下两点:
1. Admissibility(不知道咋翻译):h(v,target)要小于等于v到target的真实距离。
2. Consistency一致性:对于w的每个相邻节点v:
1. h(v, target)<=distance(v, w)+h(w, target);
2. 其中,dist(v, w) 是 v 到 w 的边的权重。