算法(第4版) Chapter 4.2 有向圖

Algorithms Fourth Edition
Written By Robert Sedgewick & Kevin Wayne
Translated By 謝路雲
Chapter 4 Section 2 有向圖算法


有向圖的創建

有向圖API

有向圖API

  • 修改了方法void addEdge(v, w) 添加的邊爲單向的, 從v到w數組

  • 修改了方法adj(v) 返回的是從v指出去的邊鏈接的頂點數據結構

  • 增長了方法Digraph reverse() 建立了一個反向邊的圖副本,由於有時候咱們須要的是指向特定頂點的其餘頂點閉包

Digraph 代碼

public class Digraph {
    private final int V;
    private int E;
    private Bag<Integer>[] adj;

    public Digraph(int V) {
        this.V = V;
        this.E = 0;
        adj = (Bag<Integer>[]) new Bag[V];
        for (int v = 0; v < V; v++)
            adj[v] = new Bag<Integer>();
    }

    public int V() {
        return V;
    }

    public int E() {
        return E;
    }

    public void addEdge(int v, int w) {
        adj[v].add(w);
        E++;
    }

    public Iterable<Integer> adj(int v) {
        return adj[v];
    }

    public Digraph reverse() {
        Digraph R = new Digraph(V);
        for (int v = 0; v < V; v++)
            for (int w : adj(v))
                R.addEdge(w, v);
        return R;
    }
}

可達性(深度優先搜索)

可達性API

可達性API

  • 增長了構造函數DirectedDFS(G,sources) 一個source生成一棵樹,n個sources生成好n棵樹;也有多是一棵樹,只是先找到了孫子,無法經過孫子找爸爸和爺爺,後來輸入了爺爺,找到了爸爸,連到了孫子,造成了一棵大樹。ide

DirectedDFS 代碼

基本和有向圖沒區別函數

public class DirectedDFS {
    private boolean[] marked;

    public DirectedDFS(Digraph G, int s) {
        marked = new boolean[G.V()];
        dfs(G, s);
    }

    public DirectedDFS(Digraph G, Iterable<Integer> sources) {
        marked = new boolean[G.V()];
        for (int s : sources)
            if (!marked[s]) dfs(G, s);
    }

    private void dfs(Digraph 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];
    }
}

多點可達性應用

  • 標記-清除的垃圾箱post

  • 尋路this

環和有向無環圖

  • 調度問題spa

  • 拓撲排序 給定一副有向圖,將全部頂點排序,使得全部的有向邊從排在前面的元素指向後面的元素code

有向無環圖 Directed Acyclic Graph (DAG)

有向環API

DirectedCycle

  • 方法boolean hasCycle() 有向環檢測

DirectedCycle 代碼

public class DirectedCycle {
    private boolean[] marked;
    private int[] edgeTo;
    private Stack<Integer> cycle; // vertices on a cycle (if one exists)
    private boolean[] onStack; // vertices on recursive call stack

    public DirectedCycle(Digraph G) {
        onStack = new boolean[G.V()];
        edgeTo = new int[G.V()];
        marked = new boolean[G.V()];
        for (int v = 0; v < G.V(); v++)
            if (!marked[v]) dfs(G, v);
    }

    private void dfs(Digraph G, int v) {
        onStack[v] = true; //這個變量是神來之筆。由於有好幾棵樹,但我只要查我所在的這棵樹。因此在遞歸的時候一路把這棵樹標成true,在返回以前再標回false。爲下一棵樹能夠循環再利用作準備。
        marked[v] = true;
        for (int w : G.adj(v))
            if (this.hasCycle()) return;
            else if (!marked[w]) {
                edgeTo[w] = v;
                dfs(G, w);
            } else if (onStack[w]) { // 我遇到了組織,咱們造成了一個環!
                cycle = new Stack<Integer>();
                for (int x = v; x != w; x = edgeTo[x])
                    cycle.push(x);
                cycle.push(w);
                cycle.push(v);//v壓了兩次,第一次做爲箭頭終點,第二次做爲箭頭起點
            }
        onStack[v] = false;
    }

    public boolean hasCycle() {
        return cycle != null;
    }

    public Iterable<Integer> cycle() {
        return cycle;
    }
}

拓撲排序API

拓撲排序API

  • 前提

    • 當且僅當一幅圖是有向無環圖時,才能進行拓撲排序

  • 一種拓撲排序的實現方式

    • 深度優先搜索DFS

  • 頂點排列順序

    • 前序 遞歸調用以前將頂點加入隊列

    • 後續 遞歸調用以後將頂點加入隊列

    • 逆後續 遞歸調用以後將頂點壓入棧

DepthFirstOrder 代碼

頂點排列順序

就是記錄頂點的位置和方式不同

public class DepthFirstOrder {
    private boolean[] marked;
    private Queue<Integer> pre; // vertices in preorder
    private Queue<Integer> post; // vertices in postorder
    private Stack<Integer> reversePost; // vertices in reverse postorder

    public DepthFirstOrder(Digraph G) {
        pre = new Queue<Integer>();
        post = new Queue<Integer>();
        reversePost = new Stack<Integer>();
        marked = new boolean[G.V()];
        for (int v = 0; v < G.V(); v++)
            if (!marked[v]) dfs(G, v);
    }

    private void dfs(Digraph G, int v) {
        pre.enqueue(v);
        marked[v] = true;
        for (int w : G.adj(v))
            if (!marked[w]) dfs(G, w);
        post.enqueue(v);
        reversePost.push(v);
    }

    public Iterable<Integer> pre() {
        return pre;
    }

    public Iterable<Integer> post() {
        return post;
    }

    public Iterable<Integer> reversePost() {
        return reversePost;
    }
}

Topological 代碼

  • 複雜度:

    • 時間: V+E

(爲何我以爲Topological這個類沒幹什麼實事,DirectedCycle檢測是不是有向無環圖,DepthFirstOrder進行了排序。。。Topological感受就封裝了一下)

public class Topological {
    private Iterable<Integer> order; // topological order

    public Topological(Digraph G) {
        DirectedCycle cyclefinder = new DirectedCycle(G);
        if (!cyclefinder.hasCycle()) {
            DepthFirstOrder dfs = new DepthFirstOrder(G);
            order = dfs.reversePost(); //排序方式
        }
    }

    public Iterable<Integer> order() {
        return order;
    }

    public boolean isDAG() {
        return order == null;
    }
}

強聯通性

  • 定義

    • w和v是相互可達的,則稱它們爲強連通的(Strongly Connected)
      (v到w有一條路徑,則w是從v可達的)

    • 若是有向圖G的每兩個頂點都強連通,稱G是一個強連通圖。

    • 有向圖的極大強連通子圖,稱爲強連通份量(Strongly Connected Components/Strong Components)

  • 兩個頂點是強連通的,當且僅當它們在同一個有向環中

  • 性質

    • 自反性

    • 對稱性

    • 傳遞性

強連通份量API

Strongly Connected Components
強連通份量API

Kosaraju算法

算法步驟很簡單,可是原理很玄幻。。。只好特意拎出來記錄+證實一下

算法步驟

第一步:在逆圖GR上運行DFS,將頂點按照逆後序(reversePost)方式壓入棧Stack s中
(顯然,這個過程做用在有向無環圖DAG上獲得的就是一個拓撲排序;做用在非DAG上獲得的是一個僞拓撲排序)

第二步:在原圖G上按第一步s.pop()的編號順序進行DFS。
(棧的特色是FILO,先進後出)

  • 算法基於CC(ConnectedComponents)

    • CC按順序0~G.V()

    • SCC按順序s.pop()

  • SCC順序怎麼獲得?

    • 類DepthFirstOrder

  • 兩次DFS

圖示兩次DFS
直觀理解2

KosarajuSCC 代碼

  • 複雜度:所需時間與空間與V+E成正比,連通性查詢爲常數時間

    • 時間: 處理有向圖的反向圖,+ 2次DFS 這三步所需的時間都與V+E成正比

    • 空間: 反向複製一副有向圖的空間與V+E成正比

    • 連通性查詢: 數組id[]查詢,常數時間

public class KosarajuSCC {
    private boolean[] marked; // reached vertices
    private int[] id; // component identifiers
    private int count; // number of strong components

    public KosarajuSCC(Digraph G) {
        marked = new boolean[G.V()];
        id = new int[G.V()];
        DepthFirstOrder order = new DepthFirstOrder(G.reverse());
        for (int s : order.reversePost())
            if (!marked[s]) {
                dfs(G, s);
                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 boolean stronglyConnected(int v, int w) {
        return id[v] == id[w];
    }

    public int id(int v) {
        return id[v];
    }

    public int count() { //複製書上的,感受返回的不是強連通的個數N,由於從零開始計數,因此返回的是N-1
        return count;
    }
}

模糊猜測

原圖
直觀理解

pop順序   →→→→     →→→→→→→→→
7 → 8 6 → 9 → 11 10 → 12 0 → 5 → 4 → 3 → 2 1
 ←     ←←←←←←   ←←←←←←←      

因爲排版問題只畫了部分的有向邊

可見是不是環,應該從1開始排除,由於1是圖中最深的結點,最有可能只有指向1的,而沒有從1指出去的。經過開始一個一個排查,應該是一種比較好的想法。

算法正確性證實

證實的目標,就是最後一步 --- 每一顆搜索樹表明的就是一個強連通份量

首先 最後一步是在原圖G中經過s找到其餘頂點v的,即從s→v是可達的。
那麼咱們須要證實,原圖G中v→s也是可達的。

等價於已知:在逆圖GR中存在有向路徑v→s
那麼要證實:逆圖GR中從s→v是可達的

而之因此DFS(s)可以在DFS(v)以前被調用,是由於在對G獲取ReversePost-Order序列時,s出如今v以前,這也就意味着,v是在s以前加入該序列的(由於該序列使用棧做爲數據結構,先加入的反而會在序列的後面)。

所以根據DFS調用的遞歸性質,DFS(v)應該在DFS(s)以前返回,而有當時在逆圖GR中兩種情形知足該條件:
DFS(v) START -> DFS(v) END -> DFS(s) START -> DFS(s) END
DFS(s) START -> DFS(v) START -> DFS(v) END -> DFS(s) END

第一種情形下,調用DFS(v)卻沒能在它返回前遞歸調用DFS(s),與在逆圖GR中存在有向路徑v→s相矛盾的,所以不可取。
故情形二爲惟一符合邏輯的調用過程。而根據DFS(s) START -> DFS(v) START能夠推導出在逆圖GR中存在有向路徑s→v。

因此從s到v以及v到s都有路徑可達,證實完畢。

頂點對的可達性

頂點對的四種狀況

w⇆v 強連通
w→v
w←v
w⇎v

問題:如何寫一個算法判斷從w到v是可達的嗎?
有向圖的可達性問題 和 連通性 有很大區別。 由於連通性是雙向的,可達性是單向的。

目前的解決辦法:傳遞閉包(其實就是一個相似於鄰接矩陣的矩陣,用來記錄是否連通)
傳遞閉包

傳遞閉包API

傳遞閉包2

TransitiveClosure 代碼

給每一個頂點創立了一棵樹,在每棵樹裏有數組marked[V],標記是否連通。

  • 複雜度

    • 空間:V*V

    • 時間:V*(V+E)

public class TransitiveClosure {
    private DirectedDFS[] all;

    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);
    }
}
相關文章
相關標籤/搜索