Algorithms Fourth Edition
Written By Robert Sedgewick & Kevin Wayne
Translated By 謝路雲
Chapter 4 Section 2 有向圖算法
修改了方法void addEdge(v, w) 添加的邊爲單向的, 從v到w數組
修改了方法adj(v) 返回的是從v指出去的邊鏈接的頂點數據結構
增長了方法Digraph reverse() 建立了一個反向邊的圖副本,由於有時候咱們須要的是指向特定頂點的其餘頂點閉包
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; } }
增長了構造函數DirectedDFS(G,sources) 一個source生成一棵樹,n個sources生成好n棵樹;也有多是一棵樹,只是先找到了孫子,無法經過孫子找爸爸和爺爺,後來輸入了爺爺,找到了爸爸,連到了孫子,造成了一棵大樹。ide
基本和有向圖沒區別函數
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)
方法boolean hasCycle() 有向環檢測
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; } }
前提
當且僅當一幅圖是有向無環圖時,才能進行拓撲排序
一種拓撲排序的實現方式
深度優先搜索DFS
頂點排列順序
前序 遞歸調用以前將頂點加入隊列
後續 遞歸調用以後將頂點加入隊列
逆後續 遞歸調用以後將頂點壓入棧
頂點排列順序
就是記錄頂點的位置和方式不同
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; } }
複雜度:
時間: 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)
兩個頂點是強連通的,當且僅當它們在同一個有向環中
性質
自反性
對稱性
傳遞性
Strongly Connected Components
算法步驟很簡單,可是原理很玄幻。。。只好特意拎出來記錄+證實一下
第一步:在逆圖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
複雜度:所需時間與空間與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是可達的嗎?
有向圖的可達性問題 和 連通性 有很大區別。 由於連通性是雙向的,可達性是單向的。
目前的解決辦法:傳遞閉包(其實就是一個相似於鄰接矩陣的矩陣,用來記錄是否連通)
給每一個頂點創立了一棵樹,在每棵樹裏有數組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); } }