加权有向图:有向路径,边的权重
单点最短路径:从s到t路径中权重最小者
最短路径树SPT:起始点s到所有可达顶点的最短路径
加权有向图数据结构
加权有向边
//加权有向图边数据结构
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);
}
}
对于边s-->t from返回s to返回t,分别获取某一条边的起始点和终点。
加权有向图数据结构
//加权有向图
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 = new Bag[V];
for(int v=0; v<V; v++){
adj[v] = new Bag<DirectedEdge>();
}
}
public EdgeWeightedDigraph(int V, int E, int[] vr, int[] wr, double[] ww){
this(V);
for(int i=0; i<E; i++){
DirectedEdge e = new DirectedEdge(vr[i], wr[i], ww[i]);
addEdge(e);
}
}
public int V(){
return this.V;
}
public int E(){
return this.E;
}
public void addEdge(DirectedEdge e){
int f = e.from();
adj[f].add(e);
E++;
}
public Iterable<DirectedEdge> adj(int v){
return adj[v];
}
public Iterable<DirectedEdge> edges(){
Bag<DirectedEdge> bag = new Bag<>();
for(int v=0; v<this.V; v++){
for(DirectedEdge e : adj[v]){
bag.add(e);
}
}
return bag;
}
public String toString(){
String s = V + " vertices, " + E + " edges\n";
for(int v = 0; v < V; v++){
s += v + ": ";
Iterator<DirectedEdge> it = this.adj(v).iterator();
while(it.hasNext()){
DirectedEdge e = it.next();
if(it.hasNext())
s += e.toString() + " ---> ";
else
s += e.toString();
}
s += "\n";
}
return s;
}
}
对于邻接表数组来说,每个数组元素是一个有向边结构的背包链表,由于边是有向的,因此只需要在from初始顶点背包中添加一条边即可;edges方法返回有向加权的所有有向边。
示例如下所示:
松弛操作
给定起始点,求出起始点s到所有可达顶点的最短路径,即求出最短路径树SPT。
利用edgeTo数组来保存最短路径树的边,edgeTo[v]=e表示的含义是从起始点s到达v的最短路径上最后一条边为e。
利用distTo数组来保存到达起始点的距离,例如distTo[v]=x,表示从s到v已知最短路径长度为x。
规定edgeTo[s]=null distTo[s]=0 distTo[不可达顶点]=无穷大。
边的松弛
假定在某一个时刻起始点s到顶点v的最短距离为distTo[v],从起始点s到顶点w的已知最短路径为distTo[w],而s-->w的最短路径上最后一条边为edgeTo[w];
顶点w为顶点v的相邻顶点,边e:v-->w连接v与w。由于s可以到达v,通过v可以访问到w,因此从s到达w的可能路径增加一条s---v---w,如果这条路径权重比已知的路径要小,则从s到达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;
}
}
顶点松弛
将顶点v所有可达顶点全部进行松弛操作,则完成顶点v的松弛
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;
}
}
}
最短路径的最优性条件
给定加权有向图G,起始点s,distTo数组。对于从s可达的所有顶点v,distTo[v]表示从s到v的某条路径,对于不可达顶点,该值为无穷大;当且仅当对于从v到w的任意边e,满足distTo[w]<=distTo[v]+e.weight,它们是最短路径的长度。
通用最短路径算法
distTo[s]初始化0,其余值初始化无穷大,放松G中的任意边,直到不存在有效边为止。此时distTo[w]即为从s到w的最短路径长度,且edgeTo[w]=e表示该最短路径的最后一条边为e。
Dijkstra算法----权重非负的加权有向图最短路径
从通用最短路径算法出发,distTo[s]初始化0,其余值初始化无穷大。不断将distTo[]中最小的非树顶点放松并加入树中,直到所有顶点都在树中,或者所有非树顶点值都为无穷大。
利用edgeTo来保存最短路径的最后一条边,distTo表示最短路径长度,优先级队列来排序distTo数组来不断取出最小值。
代码实现如下:
//权重非负的加权有向图单起点最短路径问题
public class DijkstraSP {
//最短路径路中连接v和其父节点的边 edgeTo[v]=e表示 s---->v最短路径上最后一条边为e
private DirectedEdge[] edgeTo;
//起始点到某个顶点已知最短路径的长度
private double[] distTo;
//顶点序号,距离起始点距离 二元组的优先级队列,每次都处理距离起始点最近的顶点
private PriorityQueue<VertexDist> pq;
public DijkstraSP(EdgeWeightedDigraph g, int s){
edgeTo = new DirectedEdge[g.V()];
distTo = new double[g.V()];
pq = new PriorityQueue<>();
for(int v=0; v<g.V(); v++){
distTo[v] = Double.POSITIVE_INFINITY;
}
distTo[s] = 0.0;
pq.add(new VertexDist(s, 0.0));
//每次都处理距离起始点最近的顶点
//由于新顶点v加入,假定起始点s到新顶点v的某一个相邻顶点w存在某一条路径
//由于v的加入此时出现一条新路径s--v---w,该路径可能比之前某条s--w路径短,如果短则更新distTo[w]以及edgeTo[w]
//判断条件是distTo[w] > distTo[v]+e.weight()
while(!pq.isEmpty()){
relax(g, pq.poll());
}
}
private void relax(EdgeWeightedDigraph g, VertexDist vd) {
int v = vd.getV();
//顶点v的松弛
for(DirectedEdge e : g.adj(v)){
int w = e.to();
//边e的松弛
if(distTo[w] > distTo[v]+e.weight()){
VertexDist t = new VertexDist(w, distTo[w]);
edgeTo[w] = e;
distTo[w] = distTo[v]+e.weight();
if(pq.contains(t)){
pq.remove(t);
t.setW(distTo[w]);
pq.add(t);
}else{
pq.add(new VertexDist(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;
Stack<DirectedEdge> s = new Stack<>();
for(DirectedEdge e=edgeTo[v]; e!=null; e=edgeTo[e.from()]){
s.push(e);
}
List<DirectedEdge> ll = new ArrayList<>();
while(!s.isEmpty()){
ll.add(s.pop());
}
return ll;
}
}
空间与V正比,时间与ElogV正比。
通用最短路径算法并没有指明放松顶点的顺序,Dijkstra算法每次添加的都是离起点最近的非树顶点。其局限是加权有向图的边权重非负。