摘要:只好特地拎出來記錄證明一下算法步驟第一步在逆圖上運行,將頂點按照逆后序方式壓入棧中顯然,這個過程作用在有向無環圖上得到的就是一個拓撲排序作用在非上得到的是一個偽拓撲排序第二步在原圖上按第一步的編號順序進行。等價于已知在逆圖中存在有向路徑。
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() 創建了一個反向邊的圖副本,因為有時候我們需要的是指向特定頂點的其他頂點
Digraph 代碼public class Digraph { private final int V; private int E; private Bag可達性(深度優先搜索) 可達性API[] adj; public Digraph(int V) { this.V = V; this.E = 0; adj = (Bag []) new Bag[V]; for (int v = 0; v < V; v++) adj[v] = new Bag (); } public int V() { return V; } public int E() { return E; } public void addEdge(int v, int w) { adj[v].add(w); E++; } public Iterable 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棵樹;也有可能是一棵樹,只是先找到了孫子,沒法通過孫子找爸爸和爺爺,后來輸入了爺爺,找到了爸爸,連到了孫子,形成了一棵大樹。
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多點可達性應用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]; } }
標記-清除的垃圾箱
尋路
環和有向無環圖調度問題
拓撲排序 給定一副有向圖,將所有頂點排序,使得所有的有向邊從排在前面的元素指向后面的元素
有向無環圖 Directed Acyclic Graph (DAG)
有向環API方法boolean hasCycle() 有向環檢測
DirectedCycle 代碼public class DirectedCycle { private boolean[] marked; private int[] edgeTo; private Stack拓撲排序APIcycle; // 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 (); 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 cycle() { return cycle; } }
前提
當且僅當一幅圖是有向無環圖時,才能進行拓撲排序
一種拓撲排序的實現方式
深度優先搜索DFS
頂點排列順序
前序 遞歸調用之前將頂點加入隊列
后續 遞歸調用之后將頂點加入隊列
逆后續 遞歸調用之后將頂點壓入棧
DepthFirstOrder 代碼頂點排列順序
就是記錄頂點的位置和方式不一樣
public class DepthFirstOrder { private boolean[] marked; private QueueTopological 代碼pre; // vertices in preorder private Queue post; // vertices in postorder private Stack reversePost; // vertices in reverse postorder public DepthFirstOrder(Digraph G) { pre = new Queue (); post = new Queue (); reversePost = new Stack (); 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 pre() { return pre; } public Iterable post() { return post; } public Iterable reversePost() { return reversePost; } }
復雜度:
時間: V+E
(為什么我覺得Topological這個類沒干什么實事,DirectedCycle檢測是否是有向無環圖,DepthFirstOrder進行了排序。。。Topological感覺就封裝了一下)
public class Topological { private Iterable強聯通性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 order() { return order; } public boolean isDAG() { return order == null; } }
定義
w和v是相互可達的,則稱它們為強連通的(Strongly Connected)
(v到w有一條路徑,則w是從v可達的)
如果有向圖G的每兩個頂點都強連通,稱G是一個強連通圖。
有向圖的極大強連通子圖,稱為強連通分量(Strongly Connected Components/Strong Components)
兩個頂點是強連通的,當且僅當它們在同一個有向環中
性質
自反性
對稱性
傳遞性
強連通分量APIStrongly 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); } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66497.html
摘要:在實際的測試中,算法的運行效率也比算法高左右。此外,該算法與求無向圖的雙連通分量割點橋的算法也有著很深的聯系。學習該算法,也有助于深入理解求雙連通分量的算法,兩者可以類比組合理解。固屬于同一連通分支。 參考資料http://blog.csdn.net/acmmmm/a...https://www.byvoid.com/blog/s...http://blog.csdn.net/noth...
摘要:相關操作就是判斷的不等號符號改反,初始值設為負無窮副本的最短路徑即為原圖的最長路徑。方法是同上面一樣構造圖,同時會添加負權重邊,再將所有邊取反,然后求最短路徑最短路徑存在則可行沒有負權重環就是可行的調度。 Algorithms Fourth EditionWritten By Robert Sedgewick & Kevin WayneTranslated By 謝路云Chapter ...
摘要:區別把數字對應成字符。這個是字符串的第位。稍作修改可適應不等長的字符串。因此增加一個組別,記錄字符為空的頻次。 Algorithms Fourth EditionWritten By Robert Sedgewick & Kevin WayneTranslated By 謝路云Chapter 5 Section 1 字符串排序 參考資料http://blog.csdn.net/gua...
摘要:由的位置決定乘以幾,依次為,,,,,。遞歸算法遞歸算法就是本次結果用另外的參數調用自己。然而這個函數方法本身并沒有用,因為方法中若傳遞參數為基本型如,在方法中對其值的改變并不會在主函數中產生影響。 Algorithms Fourth EditionWritten By Robert Sedgewick & Kevin WayneTranslated By 謝路云 筆記 二分查找 Bin...
摘要:堆中位置的結點的父節點的位置為,子節點的位置分別是和一個結論一棵大小為的完全二叉樹的高度為用數組堆實現的完全二叉樹是很嚴格的,但它的靈活性足以使我們高效地實現優先隊列。 Algorithms Fourth EditionWritten By Robert Sedgewick & Kevin WayneTranslated By 謝路云Chapter 2 Section 4 優先隊列 ...
閱讀 3063·2021-10-12 10:20
閱讀 2809·2021-09-27 13:56
閱讀 790·2021-09-27 13:36
閱讀 1424·2021-09-26 09:46
閱讀 2417·2019-08-30 14:02
閱讀 2685·2019-08-28 18:14
閱讀 1257·2019-08-26 10:32
閱讀 1700·2019-08-23 18:25