通用最短路径算法
distTo[s]初始化0,其余值初始化无穷大,放松G中的任意边,直到不存在有效边为止。此时distTo[w]即为从s到w的最短路径长度,且edgeTo[w]=e表示该最短路径的最后一条边为e。
Dijkstra算法----权重非负的加权有向图最短路径
从通用最短路径算法出发,distTo[s]初始化0,其余值初始化无穷大。不断将distTo[]中最小的非树顶点放松并加入树中,直到所有顶点都在树中,或者所有非树顶点值都为无穷大。
Dijkstra算法每次处理距离起始点距离最近的非树顶点,并且要求加权有向图的边权重非负。下面给出无环加权有向图最短路径一般解法。
Acyclic算法----无环加权有向图最短路径算法
distTo[s]初始化0,其余值初始化无穷大。按照拓扑排序顺序来放松所有顶点,可以在E+V正比时间内解决无环加权有向图最短路径。
下面给出一般加权有向图的最短路径算法
负权重环:环上所有边权重和为负的有向环
从s到v的有向路径上的任意顶点都不存在于负权重环上时,s到v的最短路径才存在。
负权重环的检测问题
Bellman-Ford算法----一般加权有向图最短路径算法
给定G以及s,从s无法到达任何负权重环。distTo[s]初始化0,其余值初始化无穷大,以任意顺序放松图中所有边,重复V轮,可以解决最短路径问题。
代码如下,时间复杂度O(EV)
for (int pass = 0; pass < G.V(); pass++)
for (int v = 0; v < G.V(); v++)
for (DirectedEdge e : G.adj(v))
relax(e);
基于队列的Bellman-Ford算法
只有上一轮中distTo[]值发生变化的顶点指出的边才能改变其他的distTo[]元素的值。
利用queue来保存正在被放松的顶点,利用onQ来标识某个顶点是否在队列中。将起始点s加入到队列中,并标记s。不断遍历队列取出的顶点元素v,进行顶点v放松,如果v的某个相邻顶点w发生松弛,则将w加入到队列中。利用cost来记录relax执行的次数,每当完成一轮处理时,此时edgeTo中保存最短路径树SPT,构建SPT,判断是否含有负权重环。
bellman-ford算法实现:
//一般加权有向图的最短路径
public class BellmanFordSP {
//起点到某个顶点的最短路径长度
private double[] distTo;
//从起点到某个顶点最短路径最后一条边
private DirectedEdge[] edgeTo;
//是否在队列中
private boolean[] onQ;
//正在处理的顶点队列
private Queue<Integer> queue;
//调用relax此时
private int cost;
//负权重环
private Iterable<DirectedEdge> cycle;
public BellmanFordSP(EdgeWeightedDigraph g, int s){
distTo = new double[g.V()];
edgeTo = new DirectedEdge[g.V()];
onQ = new boolean[g.V()];
queue = new LinkedList<>();
for(int v=0; v<g.V(); v++){
distTo[v] = Double.POSITIVE_INFINITY;
}
distTo[s] = 0.0;
queue.add(s);
onQ[s] = true;
while(!queue.isEmpty() && !hasNegativeCycle()){
int v = queue.poll();
onQ[v] = false;
relax(g, v);
//System.out.println(queue);
}
}
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(!onQ[w]){
queue.add(w);
onQ[w] = true;
}
}
if(cost++%g.V()==0){
findNegativeCycle();
}
}
}
//利用edgeTo构建最短路径树SPT,寻找负权重环
private void findNegativeCycle() {
int V = edgeTo.length;
EdgeWeightedDigraph spt = new EdgeWeightedDigraph(V);
for(int v=0; v<V; v++){
if(edgeTo[v]!=null){
spt.addEdge(edgeTo[v]);
}
}
//System.out.println(cost + " " + spt.toString());
DirectedCycle dc = new DirectedCycle(spt);
cycle = dc.cycle();
}
public Iterable<DirectedEdge> negativeCycle(){
return cycle;
}
public boolean hasNegativeCycle() {
return cycle != null;
}
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;
}
}
负权重环的检测实现DFS
//加权有向图 负权重环检测 dfs
public class DirectedCycle {
//记录是否访问过
private boolean[] marked;
//有向路径保存
private DirectedEdge[] edgeTo;
//记录是否在当前栈中
private boolean[] onStack;
//记录最终的环节点
private Stack<DirectedEdge> cycle;
public DirectedCycle(EdgeWeightedDigraph g){
marked = new boolean[g.V()];
onStack = new boolean[g.V()];
edgeTo = new DirectedEdge[g.V()];
for(int s=0; s<g.V(); s++){
if(!marked[s])
dfs(g, s);
}
}
private void dfs(EdgeWeightedDigraph g, int v) {
//标记当前节点入栈
onStack[v] = true;
//标记已经访问
marked[v] = true;
for(DirectedEdge e : g.adj(v)){
if(hasCycle()) return;
//如果w没有被访问过 记录v-->w路径 对于w继续进行dfs w入栈
int w = e.to();
if(!marked[w]){
edgeTo[w] = e;
dfs(g, w);
}
//如果w已经出现在栈中,说明出现了环
//出现有向环 记录该有向环路径
else if(onStack[w]){
cycle = new Stack<DirectedEdge>();
DirectedEdge f = e;
while (f.from() != w) {
cycle.push(f);
f = edgeTo[f.from()];
}
cycle.push(f);
return;
}
}
//w出栈
onStack[v] = false;
}
public boolean hasCycle(){
return cycle != null;
}
public Iterable<DirectedEdge> cycle(){
return this.cycle;
}
}