加权有向图问题1--单源最短路径问题(Dijkstra算法和Bellman - Ford算法)

加权有向图实现

咱们实现两个类,权重边类和有向图类。有向图类中组合权重边类来实现加权有向图。java

权重边类实现:

public class DirectedEdge {
	private int v;
	private int w;
	private 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 form(){  return v;  }
	public int to(){  return w;  }
    public String toString(){  return String.format("%d->%d %.2f", v,w,weight);  }
}

加权有向图实现:

public class EdgeWeightedDigraph {
	private 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];
		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.form()].add(e);
		E++;
	}
    //从v指出的边
	public Iterable<DirectedEdge> adj(int v){return adj[v];}
    //该有向图中全部的边
	public Iterable<DirectedEdge> edges(){
		Bag<DirectedEdge> bag = new Bag<DirectedEdge>();
		for(int v = 0; v<V; v++)
			for(DirectedEdge e : adj[v])
				bag.add(e);
		return bag;
	}
}

Dijkstra算法

Dijkstra算法能够解决边的权重非负的最短路径问题,没法判断含负权边的图的最短路径,但Bellman-Ford算法能够。算法

边的松弛:

在实现Dijkstra算法以前,必须先了解边的松弛:松弛边v->w意味着检查从s到w的最短路径是不是先从s到v,再从v到w。若是是,则根据这个状况更新数据。下面的代码实现了放松一个从给定顶点的指出的全部的边:数组

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;
        }
    }
}

其中,distTo[]是一个顶点索引数组,保存的是G中路径的长度。对于从s可达的全部顶点w,distTo[v]的值是从s到w的某条路径长度,对于s不可达的全部顶点w,该值为无穷大。数据结构

数据结构:

  • 最短路径树中的边:使用一个由顶点索引的父连接数组edgeTo[],其中edgeTo[v]的值为树中链接v和它父节点的边(也是从s到v的最短路径上的最后一条边),经过该数组能够逆推获得最短路径。
  • 到达起点的距离:用一个由顶点索引的数组distTo[],其中distTo[v]为从s到v的已知最短路径的长度。
  • 顶点优先权队列:保存须要被放松的顶点并确认下一个被放松的顶点。

算法实现:

public class DijkstraSP {
    private DirectedEdge[] edgeTo;  //edgeTo用来逆推最短路径
    private double[] distTo;    //distTo[]用来计算最短路径
    private IndexMinPQ<Double> pq;    //用来保存须要被放松的顶点并确认下一个被放松的顶点
	
    public DijkstraSP(EdgeWeightedDigraph G,int s) {
        //初始化
        edgeTo = new DirectedEdge[G.V()];
        distTo = new double[G.V()];
        pq = new IndexMinPQ<Double>(G.V());
        for(int v = 0;v<G.V();v++)
            distTo[v] = Double.POSITIVE_INFINITY;
        distTo[s] = 0.0;
        //从起始顶点s开始
        pq.insert(s,0.0);    //调用优先权队列的insert()方法
        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;
	    Stack<DirectedEdge>  path = new Stack<DirectedEdge>();
	    for(DirectedEdge e = edgeTo[v];e!=null;e = edgeTo[e.form()])
	    path.push(e);
	    return path;
    }
}

简单的在上述算法外添加一层循环便可实现任意顶点对之间的最短路径(权重非负)。this

无环状况下的最短路径算法

算法特色:

若是加权有向图不含有向环,则下面要实现的算法比Dijkstra算法更快更简单。它有如下特色:spa

  • 可以在线性时间内解决单点最短路径问题
  • 可以处理负权重的边
  • 可以解决相关的问题,例如找出最长的路径

算法思想:

该方法将顶点的放松与拓扑排序结合起来,首先将distTo[s]初始化为0,其余distTo[]初始化为无穷大,而后一个个地按照拓扑排序放松全部顶点。.net

按照拓扑排序放松顶点,就能在和V+E成正比的时间内解决无环加权有向图的单点最短路径问题。code

算法实现:

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;
		//拓扑排序对象
		Tolpological top = new Tolpological(G);
        //按照拓扑排序放松顶点
		for(int v: top.order())    
			relax(G,v);
	}
    //relax()、distTo()、hasPathTo()、pathTo()同Dijkstra算法
}

改实现中不须要marked[]数组,由于按照拓扑排序处理不可能再次遇到已经被放松过的顶点。orm

Bellman-Ford算法

Dijkstra算法没法判断含负权边的图的最短路径,若是遇到负权,在没有负权回路(回路的权值和为负)存在时,能够采用Bellman - Ford算法正确求出最短路径。对象

当且仅当加权有向图中至少存在一条从s到v的有向路径且全部从s到v的有向路径上的任意顶点都不存在与任何负权重环中,s到v的最短路径才是存在的。

算法思想:

在任意含有V个顶点的加权有向图中给定起点s,从s没法达到任何负权重环,一下算法可以解决其中的单源最短路径问题:将distTo[s]初始化为0,其余distTo[]初始化为无穷大。以任意顺序放松全部边,重复V轮。

算法实现:

这个算法很是简洁通用,在进行过负权重检测(见最后)以后,下面代码就能够实现Bellman-Ford算法:

for(int num = 0; num<G.V(); num++)
    for(v = 0;v<G.V();v++)
        for(DirectedEdge e: G.adj(v))
            relax(e);

但根据经验可知在任意一轮中许多边的放松是不会成功的:只有上一轮distTo[]值发生变化的顶点指出的边才可以改变其余distTo[]的值。为了记录这样的顶点,引入一条FIFO队列。

算法改进:

Bellman-Ford算法所需时间和EV成正比,空间和V成正比。

数据结构:

  • 一条用来保存即将被放松的顶点的队列
  • 一个由顶点索引的boolean[]数组,用来指示顶点是否已经在队列中

算法思想:

首先,将起始顶点s加入队列中,而后进入一个循环,其中每次都从队列中取出一个顶点将其放松。而后将被成功放松的边所指向的顶点加入队列中,这样能保证:

  • 队列中不会出现重复的顶点
  • 在某一轮中,改变了distTo[]和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;
			if(!onQ[w]) {
				queue.enqueue(w);
				onQ[w] = true;
			}
		}
		if(cost++%G.V()==0)
			findNegativeCycle();
	}
}

算法实现:

public class BellmanFordSP {
	private double [] distTo;	//从起点到某个顶点的路径长度
	private DirectedEdge[] edgeTo;		//从起点到某个顶点的最后一条边
	private boolean[] onQ;		//该顶点是否存在于队列中
	private Queue<Integer> queue;		//正在被放松的顶点
	private int cost;		//relax()的调用次数
	private Iterable<DirectedEdge> cycle;		//edgeTo[]中是否含有负权重环
	
	public BellmanFordSP(EdgeWeightedDigraph G,int s) {
		distTo = new double[G.V()];
		edgeTo = new DirectedEdge[G.V()];
		onQ = new boolean[G.V()];
		queue = new Queue<Integer>();
		for(int v=0;v<G.V();v++)
			distTo[v] = Double.POSITIVE_INFINITY;
		distTo[0] = 0.0;
		queue.enqueue(s);
		onQ[s] = true;
		while(!queue.isEmpty()&&!hasNegativeCycle()) {
			int v = queue.dequeue();
			onQ[v] = false;
			relax(G,v);
		}
	}
	
	private void relax(EdgeWeightedDigraph G,int v) {}//见上文算法
	public boolean hasPathTo(int v) {}//与Dijkstra算法中方法相同
	public Iterable<DirectedEdge> pathTo(int v){}//与Dijkstra算法中方法相同

/********************************************************************************************/
	
    //负权重检测
	private void findNegativeCycle() {
        int V = edgeTo.length;
        EdgeWeightedDigraph spt;
        spt = new EdgeWeightedDigraph(V);
        for(int v = 0;v<V; v++)
            if(edgeTo[v]!=null)
                spt.addEdge(edgeTo[v]);
        EdgeWeightedCycleFinder cf;
        cf = EdgeWeightedCycleFinder(spt);
        cycle = cf.cycle();
    }

    public boolean hasNegativeCycle() { return cycle != null; }
    public Iterable<DirectedEdge> negativeCycle(){ return cycle; }
}
相关文章
相关标签/搜索