加權有向圖問題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; }
}
相關文章
相關標籤/搜索