有向圖問題2----可達性問題和強連通份量問題(Kosaraju算法)

可達性問題

  • 單點可達性:回答「是否存在一條從起點s到給定節點v的有向路徑?」等相似問題。
  • 多點可達性:回答「是否存在一條從集合中任意頂點到給定節點v的有向路徑?」等相似問題。
  • 頂點對的可達性:回答「是否存在一條從一個給定節點v到給定節點w的有向路徑?」等相似問題。

單點可達性和多點可達性:

使用深度優先遍歷很容易實現。java

算法實現:

public class DirectedDFS{
    private boolean[] marked;
    //單點可達性
    public DirectedDFS(Digraph G,int s){
        mark = new boolean[G.V()];
        dfs(G,s);
    }
    //多點可達性
    public DirectedDFS(Digraph G, Iterable<Integer> sources){
        mark = new boolean[G.V()];
        for(int s : sources)
            if(!marked[s]) dfs(G,s); 
    }
    //深度優先遍歷算法
    private void dfs(Graph G,int v) {
        marked[v] = true;
        for(int w: G.adj(v))
            if(!marked[w])    dfs(G,w);
    }

    public boolean marked(int v){ return marked[v]; }
}

頂點對之間的可達性:

在無向圖中,這個問題很好解決,等價於連通性問題。無向圖中須要線性時間的預處理就能達到常數時間的查詢操做。但在有向圖中,該問題目前還達不到這樣的效率。算法

有向圖G的傳遞閉包是由相同的一組頂點組成的另外一幅有向圖,在傳遞閉包中存在一條從v指向w的邊當且僅當G中w是從v可達的。咱們很容易想到經過計算有向圖的傳遞閉包來解決頂點對的可達性問題,但通常來講,一幅有向圖的傳遞閉包中所含的邊比原圖中多得多,與其明確計算一幅有向圖的傳遞閉包,不如使用深度優先搜索來實現。數組

算法實現:

public class TransitiveClosure{
	private DirectedDFS[] all;  //all[]數組中每一個元素都是一個如下標爲起始頂點的深度優先遍歷逆後序排列
	public TransitiveClosure(Digraph G) {
		all = new DirectedDFS[G.V()];
		for(int v = 0; v<G.V();v++)
			all[v] = new DirectedDFS(G,v);
	}

	boolean reachable(int v,int w) {
		return all[v].marked(w);
	}
}

此方法不適用於實際問題中大型有向圖,由於構造函數所須要的空間和V^2成正比,所須要的時間和V(V+E)成正比:共有V個DirectedDFS對象,每一個所需的空間都與V成正比(他們都含有大小爲V的marked[]數組並會檢查E條邊來計算標記)。閉包

本質上,該方法是經過計算G的傳遞閉包來支持常數時間查詢----傳遞閉包的第v行就是TransitiveClosure類中    DirectedDFS[]數組中第v個元素的marked[]數組。函數

用遠小於平方級別的空間支持常數級別的查詢的通常解決方案還是一個有待解決的研究問題。spa

強連通份量問題

有向圖強連通份量:在有向圖G中,若是兩個頂點vi,vj間有一條從vi到vj的有向路徑,同時還有一條從vj到vi的有向路徑,則稱兩個頂點強連通。若是有向圖G的每兩個頂點都強連通,稱G是一個強連通圖。有向圖的極大強連通子圖,稱爲強連通份量。.net

Kosaraju算法:

Kosaraju算法能夠用來計算有向圖的強連通份量。code

算法思想:

  1. 在給定的一幅有向圖G中,使用DepthFirstOrder來計算它的反向圖G(R)的逆後序排列。
  2. 在G中進行標準的深度優先遍歷,但要按照剛纔獲得的逆後序排列而非標準的順序來訪問全部未被標記的頂點。
  3. 在構造函數中,全部在同一個遞歸dfs()調用中被訪問到的頂點都在同一個強連通份量中。

除了下面代碼中標出的兩行區別,Kosaraju算法的實現和求無向圖的連通性問題的實現幾乎徹底相同。Kosaraju算法實現簡單但難以理解。在知乎上看到一個對Kosaraju算法的淺顯易懂的解釋,能夠用來幫助理解該算法的原理:https://www.zhihu.com/question/58926821/answer/163724688對象

算法實現:

public class KosarajuSharirSCC {
    private boolean[] marked;     // 已訪問過的頂點
    private int[] id;             // 強連通份量的標識符
    private int count;            //強連通份量的數量

    public KosarajuSharirSCC(Digraph G) {
        marked = new boolean[G.V()];
        id = new int[G.V()];
        DepthFirstOrder dfs = new DepthFirstOrder(G.reverse());//區別
        for (int v : dfs.reversePost()) {//區別
            if (!marked[v]) {
                dfs(G, v);
                count++;
            }
        }
    }
    private void dfs(Digraph G, int v) { 
        marked[v] = true;
        id[v] = count;
        for (int w : G.adj(v)) {
            if (!marked[w]) dfs(G, w);
        }
    }
    public int count() { return count; }
    public boolean stronglyConnected(int v, int w) { return id[v] == id[w]; }
}
相關文章
相關標籤/搜索