最短路径
定义:在一幅加权有向图中,从顶点 s 到顶点 t 的最短路径是所有从 s 到 t 的路径中的权重最小者。
单点最短路径: 给定一幅加权有向图和一个起点s,回答 “从s到给定的目标顶点v是否存在一条有向路径?如果有,找出最短(总权重最小)的那条路径。”
最短路径的性质
路径是有向的。
权重不一定等价于距离。
并不是所有顶点都是可达的。(为了简化问题,假定图是强连通的,即每个顶点从另外任意一个顶点都是可达的)
最短路径不一定是唯一的。
最短路径树: 给定一幅加权有向图和一个顶点 s,以 s 为起点的一棵最短路径树是图的一幅子图,它包含s和从s可达的所有顶点。这棵有向树的根节点为 s,树的每条路径都是有向图中的一条最短路径。
(即,包含顶点s到所有可达的顶点的最短路径)
加权有向图的数据结构
加权有向边的数据类型
加权有向图的数据结构比加权无向图的数据结构更加简单。与 Edge 类中的 either() 和 other() 方法不同,这里定义了 from() 和 to() 方法。
/*
* 加权有向边的数据类型
*/
public class DirectedEdge{
private final int v; //边的起点
private final int w; //边的终点
private final double weight; //边的权重
public DirectedEdge(int v, int w, double weight) {
this.v = v;
this.w = w;
this.weight = weight;
}
public double weight() { return weight; }
public int from() { return v; }
public int to() { return w; }
public String toString() { return String.format("%d%->d %.2f", v,w,weight); }
}
DirectedEdge类的实现比Edge类更加简单,因为边的两个端点是有区别的。
用例可以使用惯用代码 v = e.to(),w = e.from() 来访问一个DirectedEdge对象e的两个顶点。
加权有向图的数据类型
在之前的学习中,我们从 Graph 类过渡到了 EdgeWeightedGraph 类。现在考虑 EdgeWeightedDiGraph 类。
/*
* 加权有向图的数据类型
*/
public class EdgeWeightedDiGraph {
private final int V; //顶点总数
private int E; //边的总数
private Bag<DirectedEdge>[] adj; //邻接表
public EdgeWeightedDiGraph(int V) {
this.V = V;
this.E = 0;
adj = (Bag<DirectedEdge>[]) new Bag[V]; //和Queue不同,Bag保证无序
for(int v=0; v<V; v++) {
adj[v] = new Bag<DirectedEdge>();
}
}
public int V() { return V; }
public int E() { return E; }
public void addEdge(DirectedEdge e) {
adj[e.from()].add(e);
E++;
}
public Iterable<DirectedEdge> adj(int v) {
return adj[v];
}
//返回加权有向图中的所有边
public Iterable<DirectedEdge> edges() {
Bag<DirectedEdge> b = new Bag<DirectedEdge>();
for (int v = 0; v < V; v++)
for (DirectedEdge e : adj(v))
b.add(e);
return b;
}
}
EdgeWeightedDiGraph 类维护了一个由顶点索引的 Bag 对象的数组,Bag对象的内容为 DirectedEdge 对象。
最短路径的API
对于最短路径的API,设计思路与 DepthFirstPaths 和 BreadFirstPaths 类似。
构造函数会创建最短路径树并计算最短路径的长度,其他查询方法则会使用这些数据结构为用例提供路径的长度以及路径的 Iterable对象。最短路径的测试用例如下:
该测试用例接受一个输入流、一个起点作为命令行参数。从输入流中读取加权有向图,根据起点来计算有向图的最短路径树。
表示最短路径所需的数据结构很简单:
最短路径树中的边:父链接数组 edgeTo[v] 的值表示树中连接v和它的父节点的边。也就是从 s 到 v 的最短路径上的最后一条边。
到达起点的距离:distTo[v] 表示从 s 到 v 的已知最短路径的长度。
我们约定,edgeTo[s]的值为 null,distTo[s]的值为 0;从起点到不可达的顶点的距离为Double.POSITIVE_INFINITY。
最短路径中的查询方法
默认所有最短路径的实现都包含这段代码。
我们约定,对于从 s 不可达的顶点,distTo() 方法都应该返回无穷大。在实现这个约定时,将 distTo[] 中的所有元素都初始化为 Double.POSITIVE_INFINITY ,distTo[s] 则为 0。
最短路径算法会将从起点可达的顶点 v 的 distTo[v] 设为一个有限值,这样就不必再用 marked[] 数组在图搜索中标记可达的顶点,而是通过检测 distTo[v] 是否为 Double.POSITIVE_INFINITY 来实现 hasPathTo(v)。
/*
* 最短路径API中的查询方法
*/
public double distTo(int v) { return distTo[v]; }
public boolean hasPathTo(int v) { return distTo[v] < Double.POSITIVE_INFINITY; }
public Iterable<DirectedEdge> pathTo(int v){
if(!hasPathTo(v))
return null;
Stack<DirectedEdge> path = new Stack<DirectedEdge>();
for(DirectedEdge e = edge=edgeTo[v]; e!=null; e=edgeTo[e.from()])
path.push(e);
return path;
}
边、顶点的松弛
我们的最短路径API的实现都基于一个被称为松弛的操作。一开始,distTo[] 中只有起点所对应的元素的值为 0,其余元素的值均被初始化为 Double.POSITIVE_INFINITY。随着算法的执行,它将起点到其他顶点的最短路径信息存入edgeTo[] 和 distTo[] 数组中。在遇到新的边时,通过更新这些信息就可以得到新的最短路径,这其中就会用到边的松弛技术。
边的松弛
放松边 v->w 意味着检查从 s 到 w 的最短路径是否先从 s 到 v,然后再由 v 到 w。如果是,则根据情况更新数据结构的内容。
由 v 到 w 的最短路径是 distTo[v] 与 e.weight() 之和——如果这个值不小于 distTo[w] ,称这条边失效了并将它忽略了如果这个值更小,就更新数据。
//边的松弛
private void relax(DirectedEdge e){
int v = e.from(), w = e.to();
if (distTo[w] > distTo[v] + e.weight()){
distTo[w] = distTo[v] + e.weight();
edgeTo[w] = e;
}
}
顶点的松弛
放松从一个给定顶点指出的所有边。
某点从起点指出的边将会是第一条被加入edgeTo[]中的边。算法会谨慎的选择顶点,使得每次顶点的松弛操作都能得出到达某个顶点的更短的路径,最后逐渐找出到达每个顶点的最短路径。
//顶点的松弛
private void relax(EdgeWeightedDigraph G, int v){
for( DirectedEdge e : G.adj(v)){
int w = e.to();
if(distTo[w] > distTo[v] + e.weight()){
distTo[w] = distTo[v] + e.weight();
edgeTo[w] = e;
}
}
}