本文作者为: SylvanasSun.转载请务必将下面这段话置于文章开头处(保留超链接).
本文转发自SylvanasSun Blog,原文链接: https://sylvanassun.github.io/2017/07/27/2017-07-27-Graph_WeightedDigraph
加权有向图
有向图
的实现比无向图
更加简单,要实现加权有向图
只需要在上一章讲到的加权无向图
的实现修改一下即可.
DirectedEdge
由于有向图
的边都是带有方向的,所以下面这个实现提供了from()
与to()
函数,用于获取代表v->w
的两个顶点
.
|
|
EdgeWeightedDigraph
|
|
加权有向图
的实现与加权无向图
区别不大,而且因为有向图
中的边只会出现一次,实现代码要比无向图
更简单.
最短路径
“找到一个顶点
到达另一个顶点
之间的最短路径
“是图论
研究中的经典算法问题.在加权有向图
中,每条有向路径
都有一个与之对应的路径权重
(路径中所有边的权重
之和),要找到一条最短路径
其实就是找到路径权重
最小的那条路径.
单点最短路径
“从s
到目的地v
是否存在一条有向路径
,如果有,找出最短的那条路径”.类似这样的问题就是单点最短路径
问题,它是我们主要研究的问题.
单点最短路径
的结果是一棵最短路径树
,它是图
的一幅子图
,包含了从起点到所有可达顶点的最短路径
.
从起点到一个顶点可能存在两条长度相等的路径,如果出现这种情况,可以删除其中一条路径的最后一条边,直到从起点到每个顶点都只有一条路径相连.
最短路径的数据结构
要实现最短路径
的算法还需要借助以下数据结构:
- edgeTo[]: 一个
由顶点索引
的DirectedEdge
对象的父链接数组,其中edgeTo[v]
的值为树中连接v
和它的父节点的边.
- distTo[]: 一个
由顶点索引
的double
数组,其中distTo[v]
代表从起点
到v
的已知最短路径的长度.
- 初始化时,
edgeTo[s]
的值为null
(s
为起点),distTo[s]
的值为0.0
,从s
到不可达的顶点距离为Double.POSITIVE_INFINITY
.
让边松弛
最短路径
算法都基于松弛(Relaxation)
操作,它在遇到新的边时,通过更新这些信息就可以得到新的最短路径.
假设对边v->w
进行松弛操作,意味着要先检查从s
到w
的最短路径
是否是先从s
到v
,然后再由v
到w
(也就是说v->w
是更短的一条路径),如果是,那么就进行更新.由v
到达w
的最短路径
是distTo[v]
与e.weight()
之和,如果这个值大于distTo[w]
,称这条边松弛失败,并将它忽略.
松弛操作就像用一根橡皮筋沿着连续两个顶点
的路径紧紧展开,放松一条边就像将这条橡皮筋转移到另一条更短的路径上,从而缓解橡皮筋的压力.
|
|
Dijkstra算法
Dijkstra算法
类似于Prim算法
,它将distTo[s]
初始化为0.0
,distTo[]
中的其他元素初始化为Double.POSITIVE_INFINITY
.然后将distTo[]
中最小的非树顶点
放松并加入树中,一直重复直到所有的顶点都在树中或者所有的非树顶点
的distTo[]
值均为Double.POSITIVE_INFINITY
.
Dijkstra算法
与Prim算法
都是用添加边的方式构造一棵树:
Prim算法
每次添加的是距离树
最近的非树顶点
.
Dijkstra算法
每次添加的都是离起点
最近的非树顶点
.
从上述的步骤我们就能看出,Dijkstra算法
需要一个优先队列(也可以用斐波那契堆
)来保存需要被放松的顶点
并确认下一个被放松的顶点
(也就是取出最小的).
如此简单的Dijkstra算法
也有其缺点,那就是它只适用于解决权重非负
的图
.
实现代码
|
|
上述的代码也可以用于处理加权无向图
,但需要修改传入的对象类型.不管是无向图
还是有向图
它们对于最短路径
问题是等价的.
无环加权有向图中的最短路径算法
如果是处理无环图
的情况下,还会有一种比Dijkstra算法
更快、更简单的算法.它的特点如下:
- 能够处理
负权重
的边.
- 能够在线性时间内解决单点最短路径问题.
在已知是一张
无环图
的情况下,它是找出最短路径
效率最高的方法.- 实现比
Dijkstra算法
更简单.
- 实现比
只需要将所有顶点
按照拓扑排序
的顺序来松弛边
,就可以得到这个简单高效的算法.
|
|
最长路径
要想找出一条最长路径
,只需要把distTo[]
的初始化变为Double.NEGATIVE_INFINITY
,并更改relax()
函数中的不等式的方向.
|
|
Bellman-Ford算法
我们已经知道了处理权重
非负图的Dijkstra算法
与处理无环图
的算法,但如果遇见既含有环,权重
也是负数的加权有向图
该怎么办?
Bellman-Ford算法
就是用于处理有环
且含有负权重
的加权有向图
的,它的原理是对图进行V-1
次松弛操作,得到所有可能的最短路径.
要实现Bellman-Ford算法
还需要以下数据结构:
- 队列: 用于保存即将被松弛的顶点.
- 布尔值数组: 用来标记该顶点是否已经存在于队列中,以防止重复插入.
我们将起点放入队列中,然后进入一个循环,每次循环都会从队列中取出一个顶点并对其进行松弛.为了保证算法在V
轮后能够终止,需要能够动态地检测是否存在负权重环
,如果找到了这个环则结束运行(也可以用一个变量动态记录轮数).
负权重环的检测
如果存在了一个从起点可达的负权重环
,那么队列就永远不可能为空,为了从这个无尽的循环中解脱出来,算法需要能够动态地检测负权重环
.
Bellman-Ford算法
也使用了edgeTo[]
来存放最短路径树
中的每一条边,我们根据edgeTo[]
来复制一幅图并在该图中检测环.
|
|
实现代码
|
|
总结
解决最短路径
问题一直都是图论
的经典问题,本文中介绍的算法适用于不同的环境,在应用中应该根据不同的环境选择不同的算法.
算法 | 局限性 | 路径长度的比较次数(增长的数量级) | 空间复杂度 | 优势 |
---|---|---|---|---|
Dijkstra | 只能处理正权重 | ElogV | V | 最坏情况下仍有较好的性能 |
拓扑排序 | 只适用于无环图 | E+V | V | 实现简单,是无环图情况下的最优算法 |
Bellman-Ford | 不能存在负权重环 | E+V,最坏情况为VE | V | 适用广泛 |