算法_最短路径
一.概述
定义:在一幅加权有向图中,从顶点s到顶点t的最短路径是所有从s到t的路径中的权重的最小者.从定义可以看出单点最短路径的实现是建立在加权有向图的基础上的.
最短路径树:给定一幅加权有向图和一个顶点s,以s为起点的一颗最短路径树是图的一幅子图,它包含s和从s可达的所有顶点.这颗有向树的根节点是s,树的每条路径都是有向图中的一条最短路径.它包含了顶点s到所有可达的顶点的最短路径.
二.加权有向图和加权有向边的数据结构
加权有向图和加权有向边的数据结构和加权无向图无向边的数据结构类型基本相同.代码如下:
//加权有向边的数据类型 public class DirectedEdge { private final int v; //边的起点 private final int w; //边的终点 private final double weight;//边的权重 public DirectedEdge(int v, int w, double weight) { super(); 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 class EdgeWeightedDigraph { private final int V; //顶点总数 private int E; //边的总数 private Bag<DirectedEdge> [] adj;//邻接表 public EdgeWeightedDigraph(int V) { super(); this.V = V; this.E=0; adj=(Bag<DirectedEdge> [])new Bag[V]; 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++; } //由v指出的边 public Iterable<DirectedEdge> adj(int v) { return adj[v]; } public Iterable<DirectedEdge> edges() { Bag<DirectedEdge> bag=new Bag<>(); for(int v=0;v<V;v++) { for(DirectedEdge e:adj[v]) { bag.add(e); } } return bag; } }
三.松弛(relax方法)
边的松弛定义如下:放松边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(); int w=e.to(); if(distTo[w]>distTo[v]+e.weight()) { distTo[w]=distTo[v]+e.weight(); edgeTo[w]=e; } }
顶点的松弛:对于一个顶点,对它进行松弛,即意味着对它所指出的边都进行松弛操作.顶点的松弛的代码如下:
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是G中的起点,distTo[]是一个由顶点索引的数组.保存的是G中路径的长度(到起点s的路径).对于从s可达的所有顶点v,distTo[v]的值是从s到v的某条路径的长度,对于s不可到达的所有顶点v,该值为无穷大.只有当对于从v到w的任意一条边e,这些值都满足distTo[w]<=distTo[e]+e.weight()时,换句话说,不存在有效边的时候,它们是最短路径的长度.
五.Dijkstra算法
该算法解决最短路径的思路如下:
假设v是从起点可达的,当v被放松时,必有distTo[w]<=distTo[v]+e.weight()成立.因为该不等式在算法结束前都会一直成立,因此distTo[w]只会变小.而distTo[v]则不会改变.(因为在每一步都会选择distTo[]最小的顶点,之后的放松操作不可能使任意distTo[]的值小于distTo[v].因此在所有从s可达的顶点添加到树中的时候,最短路径的最优性条件成立,即命题p成立.
代码如下:
/** * 计算最短路径 * 从顶点s到v的最短路径,判断有没有,如果有,给出最短路径. * 基本的思路: * 将s加入到IndexMinPQ中,然后对于s的相邻的点进行一次放松操作, * 获得s的相邻的点距离起点最近的距离,并且从中抽出最小的距离,将对应的点 * 添加到最短路径树中.依次类推,直到所有的点均被添加到树中 * @author Administrator * */ 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.changeKey(w, distTo[w]); else pq.insert(w, distTo[w]); } } } public boolean hasPathTo(int v) { return distTo[v]<Double.POSITIVE_INFINITY; } public double distTo(int v) { return distTo[v]; } public Iterable<DirectedEdge> pathTo(int v) { if(!hasPathTo(v)) return null; Stack<DirectedEdge> path=new Stack<>(); for(DirectedEdge e=edgeTo[v];e!=null;e=edgeTo[e.from()]) { path.push(e); } return path; } }
六.无环加权有向图中的最短路径算法
它的算法的步骤是:首先将distTo[s]初始化为0,其他distTo[]元素初始化为无穷大,然后一个个按照拓补排序放松所有的顶点.即能解决无环加权有向图的单点最短路径问题.
证明的步骤如下:对于任意一条边v->w,当v被放松时,得到distTo[w]<=distTo[v]+e.weight().在算法结束前这个不等式都成立.因为distTo[v]是不会变化的(因为按照拓补排序的顺序放松所有的顶点,在v被放松后算法不会再处理任何指向v的边),而distTo[w]只会变小,因此最优性条件成立,因此这个步骤可以获得最短路径的树.
/** 采用拓补排序的最短路径算法. 前提是无环且权值为正 */ public class AcyclicSP { private DirectedEdge[] edgeTo; private double[] distTo; public AcyclicSP(EdgeWeightedDigraph G,int s) { edgeTo=new DirectedEdge[G.V()]; distTo=new double[G.V()]; for(int v=0;v<G.V();v++) { distTo[v]=Double.POSITIVE_INFINITY; } distTo[s]=0.0; Topological top=new Topological(G); for(int v:top.order()) { relax(G,v); } } private void relax(EdgeWeightedDigraph G, int v) { for(edu.princeton.cs.algs4.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; } } } public boolean hasPathTo(int v) { return distTo[v]<Double.POSITIVE_INFINITY; } public double distTo(int v) { return distTo[v]; } public Iterable<DirectedEdge> pathTo(int v) { if(!hasPathTo(v)) return null; Stack<DirectedEdge> path=new Stack<>(); for(DirectedEdge e=edgeTo[v];e!=null;e=edgeTo[e.from()]) { path.push(e); } return path; } }