单点最短路径
给定一幅加权有向图和一个起点s,回答“从s到给定的目的顶点v是否存在一条有向路径?如果有,找出最短(总权重最小)的那条路径。”等类似问题。
最短路径树
给定一幅加权有向图和一个顶点s,以s为起点的一棵最短路径树是图的一幅子图,它包含s和从s可达的所有顶点。这颗有向树的根结点为s,树的每条路径都是有向图中的一条最短路径。
最短路径算法的理论基础
最优性条件
令G为一幅加权有向图,顶点s是G中的起点,distTo[]是一个由顶点索引的数组,保存的是G中路径的长度。对于从s可达的所有顶点v,distTo[v]的值是从s到v的某条路径的长度,对于从s不可达的所有顶点v,该值为无穷大。当且仅当对于从v到w的任意一条边e,这些值都满足distTo[w]<=distTo[v]+e.weight()时(换句话说,不存在有效边时),它们是最短路径的长度。
通用算法
将distTo[s]初始化为0,其他distTo[]元素初始化为无穷大,继续如下操作:放松G中的任意边,直到不存在有效边为止。
对于任意从s可达的顶点w,在进行这些操作之后,distTo[w]的值即为从s到w的最短路径的长度。
最短路径的API
API | 功能 |
---|---|
DijkstraSP(EdgeWeightedDigraph G, int s) | 构造函数 |
double distTo(int v) | 从顶点s到v的距离,如果不存在则路径为无穷大 |
boolean hasPathTo(int v) | 是否存在从顶点s到v的路径 |
Iterable<DirectedEdge> pathTo(int v) | 从顶点s到v的路径,如果不存在则为null |
Dijkstra算法
Dijkstra算法能够解决边权重非负的加权有向图的单起点最短路径问题。
Dijkstra算法首先将distTo[s]初始化为0,distTo[]中的其他元素初始化为正无穷。然后将distTo[]最小的非树顶点放松并加入树中,如此这般,直到所有的顶点都在树中或者所有的非树顶点的distTo[]值均为无穷大。
Prim算法每次添加的都是离树最近的非树顶点,Dijkstra算法每次添加的都是离起点最近的非树顶点。
代码
其中索引优先队列为IndexMinPQ
DijkstraSP.java
package section4_4;
import java.util.ArrayList;
import java.util.List;
public class DijkstraSP {
private DirectedEdge[] edgeTo;
private double[] distTo;
private IndexMinPQ<Double> pq;
public DijkstraSP(EdgeWeightedDigraph G, int s) {
edgeTo = new DirectedEdge[G.V()];
distTo = new double[G.V()];
pq = new IndexMinPQ<>(G.V());
for (int v = 0;v < G.V();v++) {
distTo[v] = Double.POSITIVE_INFINITY;
}
distTo[s] = 0.0;
pq.insert(s,0.0);
while (!pq.isEmpty()) {
relax(G,pq.delMin());
}
}
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;
if (pq.contains(w)) {
pq.change(w,distTo[w]);
} else {
pq.insert(w,distTo[w]);
}
}
}
}
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;
List<DirectedEdge> path = new ArrayList<>();
for (DirectedEdge e = edgeTo[v];e != null;e = edgeTo[e.from()]) {
path.add(0,e);
}
return path;
}
public static void main(String[] args) {
int[][] data = {
{4,5},
{5,4},
{4,7},
{5,7},
{7,5},
{5,1},
{0,4},
{0,2},
{7,3},
{1,3},
{2,7},
{6,2},
{3,6},
{6,0},
{6,4}
};
double[] weights = {
0.35,
0.35,
0.37,
0.28,
0.28,
0.32,
0.38,
0.26,
0.39,
0.29,
0.34,
0.40,
0.52,
0.58,
0.93
};
int vn = 8;
int en = 15;
EdgeWeightedDigraph G = new EdgeWeightedDigraph(vn,en,data,weights);
int s = 0;
DijkstraSP sp = new DijkstraSP(G,s);
for (int t = 0;t < G.V();t++) {
System.out.print(s + " to " + t);
System.out.printf(" (%4.2f): ",sp.distTo(t));
if (sp.hasPathTo(t)) {
for (DirectedEdge e : sp.pathTo(t)) {
System.out.print(e + " ");
}
System.out.println();
}
}
}
}
输出: