摘要:只好特地拎出來(lái)記錄證明一下算法步驟第一步在逆圖上運(yùn)行,將頂點(diǎn)按照逆后序方式壓入棧中顯然,這個(gè)過(guò)程作用在有向無(wú)環(huán)圖上得到的就是一個(gè)拓?fù)渑判蜃饔迷诜巧系玫降氖且粋€(gè)偽拓?fù)渑判虻诙皆谠瓐D上按第一步的編號(hào)順序進(jìn)行。等價(jià)于已知在逆圖中存在有向路徑。
Algorithms Fourth Edition
Written By Robert Sedgewick & Kevin Wayne
Translated By 謝路云
Chapter 4 Section 2 有向圖
修改了方法void addEdge(v, w) 添加的邊為單向的, 從v到w
修改了方法adj(v) 返回的是從v指出去的邊連接的頂點(diǎn)
增加了方法Digraph reverse() 創(chuàng)建了一個(gè)反向邊的圖副本,因?yàn)橛袝r(shí)候我們需要的是指向特定頂點(diǎn)的其他頂點(diǎn)
Digraph 代碼public class Digraph { private final int V; private int E; private Bag可達(dá)性(深度優(yōu)先搜索) 可達(dá)性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; } }
增加了構(gòu)造函數(shù)DirectedDFS(G,sources) 一個(gè)source生成一棵樹(shù),n個(gè)sources生成好n棵樹(shù);也有可能是一棵樹(shù),只是先找到了孫子,沒(méi)法通過(guò)孫子找爸爸和爺爺,后來(lái)輸入了爺爺,找到了爸爸,連到了孫子,形成了一棵大樹(shù)。
DirectedDFS 代碼基本和有向圖沒(méi)區(qū)別
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多點(diǎn)可達(dá)性應(yīng)用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]; } }
標(biāo)記-清除的垃圾箱
尋路
環(huán)和有向無(wú)環(huán)圖調(diào)度問(wèn)題
拓?fù)渑判?給定一副有向圖,將所有頂點(diǎn)排序,使得所有的有向邊從排在前面的元素指向后面的元素
有向無(wú)環(huán)圖 Directed Acyclic Graph (DAG)
有向環(huán)API方法boolean hasCycle() 有向環(huán)檢測(cè)
DirectedCycle 代碼public class DirectedCycle { private boolean[] marked; private int[] edgeTo; private Stack拓?fù)渑判駻PIcycle; // 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; //這個(gè)變量是神來(lái)之筆。因?yàn)橛泻脦卓脴?shù),但我只要查我所在的這棵樹(shù)。所以在遞歸的時(shí)候一路把這棵樹(shù)標(biāo)成true,在返回之前再標(biāo)回false。為下一棵樹(shù)可以循環(huán)再利用做準(zhǔn)備。 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]) { // 我遇到了組織,我們形成了一個(gè)環(huán)! cycle = new Stack (); for (int x = v; x != w; x = edgeTo[x]) cycle.push(x); cycle.push(w); cycle.push(v);//v壓了兩次,第一次作為箭頭終點(diǎn),第二次作為箭頭起點(diǎn) } onStack[v] = false; } public boolean hasCycle() { return cycle != null; } public Iterable cycle() { return cycle; } }
前提
當(dāng)且僅當(dāng)一幅圖是有向無(wú)環(huán)圖時(shí),才能進(jìn)行拓?fù)渑判?/strong>
一種拓?fù)渑判虻膶?shí)現(xiàn)方式
深度優(yōu)先搜索DFS
頂點(diǎn)排列順序
前序 遞歸調(diào)用之前將頂點(diǎn)加入隊(duì)列
后續(xù) 遞歸調(diào)用之后將頂點(diǎn)加入隊(duì)列
逆后續(xù) 遞歸調(diào)用之后將頂點(diǎn)壓入棧
DepthFirstOrder 代碼頂點(diǎn)排列順序
就是記錄頂點(diǎn)的位置和方式不一樣
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; } }
復(fù)雜度:
時(shí)間: V+E
(為什么我覺(jué)得Topological這個(gè)類(lèi)沒(méi)干什么實(shí)事,DirectedCycle檢測(cè)是否是有向無(wú)環(huán)圖,DepthFirstOrder進(jìn)行了排序。。。Topological感覺(jué)就封裝了一下)
public class Topological { private Iterable強(qiáng)聯(lián)通性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是相互可達(dá)的,則稱(chēng)它們?yōu)閺?qiáng)連通的(Strongly Connected)
(v到w有一條路徑,則w是從v可達(dá)的)
如果有向圖G的每?jī)蓚€(gè)頂點(diǎn)都強(qiáng)連通,稱(chēng)G是一個(gè)強(qiáng)連通圖。
有向圖的極大強(qiáng)連通子圖,稱(chēng)為強(qiáng)連通分量(Strongly Connected Components/Strong Components)
兩個(gè)頂點(diǎn)是強(qiáng)連通的,當(dāng)且僅當(dāng)它們?cè)谕粋€(gè)有向環(huán)中
性質(zhì)
自反性
對(duì)稱(chēng)性
傳遞性
強(qiáng)連通分量APIStrongly Connected Components
算法步驟很簡(jiǎn)單,但是原理很玄幻。。。只好特地拎出來(lái)記錄+證明一下
算法步驟第一步:在逆圖GR上運(yùn)行DFS,將頂點(diǎn)按照逆后序(reversePost)方式壓入棧Stack s中
(顯然,這個(gè)過(guò)程作用在有向無(wú)環(huán)圖DAG上得到的就是一個(gè)拓?fù)渑判?;作用在非DAG上得到的是一個(gè)偽拓?fù)渑判颍?/p>
第二步:在原圖G上按第一步s.pop()的編號(hào)順序進(jìn)行DFS。
(棧的特點(diǎn)是FILO,先進(jìn)后出)
算法基于CC(ConnectedComponents)
CC按順序0~G.V()
SCC按順序s.pop()
SCC順序怎么得到?
類(lèi)DepthFirstOrder
兩次DFS
圖示兩次DFS
復(fù)雜度:所需時(shí)間與空間與V+E成正比,連通性查詢(xún)?yōu)槌?shù)時(shí)間
時(shí)間: 處理有向圖的反向圖,+ 2次DFS 這三步所需的時(shí)間都與V+E成正比
空間: 反向復(fù)制一副有向圖的空間與V+E成正比
連通性查詢(xún): 數(shù)組id[]查詢(xún),常數(shù)時(shí)間
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() { //復(fù)制書(shū)上的,感覺(jué)返回的不是強(qiáng)連通的個(gè)數(shù)N,因?yàn)閺牧汩_(kāi)始計(jì)數(shù),所以返回的是N-1 return count; } }模糊猜想
原圖
pop順序 →→→→ →→→→→→→→→
7 → 8 6 → 9 → 11 10 → 12 0 → 5 → 4 → 3 → 2 1
← ←←←←←← ←←←←←←←
由于排版問(wèn)題只畫(huà)了部分的有向邊
可見(jiàn)是否是環(huán),應(yīng)該從1開(kāi)始排除,因?yàn)椋笔菆D中最深的結(jié)點(diǎn),最有可能只有指向1的,而沒(méi)有從1指出去的。通過(guò)開(kāi)始一個(gè)一個(gè)排查,應(yīng)該是一種比較好的想法。
算法正確性證明證明的目標(biāo),就是最后一步 --- 每一顆搜索樹(shù)代表的就是一個(gè)強(qiáng)連通分量
首先 最后一步是在原圖G中通過(guò)s找到其他頂點(diǎn)v的,即從s→v是可達(dá)的。
那么我們需要證明,原圖G中v→s也是可達(dá)的。
等價(jià)于已知:在逆圖GR中存在有向路徑v→s。
那么要證明:逆圖GR中從s→v是可達(dá)的。
而之所以DFS(s)能夠在DFS(v)之前被調(diào)用,是因?yàn)樵趯?duì)G獲取ReversePost-Order序列時(shí),s出現(xiàn)在v之前,這也就意味著,v是在s之前加入該序列的(因?yàn)樵撔蛄惺褂脳W鳛閿?shù)據(jù)結(jié)構(gòu),先加入的反而會(huì)在序列的后面)。
因此根據(jù)DFS調(diào)用的遞歸性質(zhì),DFS(v)應(yīng)該在DFS(s)之前返回,而有當(dāng)時(shí)在逆圖GR中兩種情形滿(mǎn)足該條件:
DFS(v) START -> DFS(v) END -> DFS(s) START -> DFS(s) END
DFS(s) START -> DFS(v) START -> DFS(v) END -> DFS(s) END
第一種情形下,調(diào)用DFS(v)卻沒(méi)能在它返回前遞歸調(diào)用DFS(s),與在逆圖GR中存在有向路徑v→s相矛盾的,因此不可取。
故情形二為唯一符合邏輯的調(diào)用過(guò)程。而根據(jù)DFS(s) START -> DFS(v) START可以推導(dǎo)出在逆圖GR中存在有向路徑s→v。
所以從s到v以及v到s都有路徑可達(dá),證明完畢。
頂點(diǎn)對(duì)的可達(dá)性頂點(diǎn)對(duì)的四種情況
w?v 強(qiáng)連通
w→v
w←v
w?v
問(wèn)題:如何寫(xiě)一個(gè)算法判斷從w到v是可達(dá)的嗎?
有向圖的可達(dá)性問(wèn)題 和 連通性 有很大區(qū)別。 因?yàn)檫B通性是雙向的,可達(dá)性是單向的。
目前的解決辦法:傳遞閉包(其實(shí)就是一個(gè)類(lèi)似于鄰接矩陣的矩陣,用來(lái)記錄是否連通)
給每個(gè)頂點(diǎn)創(chuàng)立了一棵樹(shù),在每棵樹(shù)里有數(shù)組marked[V],標(biāo)記是否連通。
復(fù)雜度
空間:V*V
時(shí)間: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); } }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/66497.html
摘要:在實(shí)際的測(cè)試中,算法的運(yùn)行效率也比算法高左右。此外,該算法與求無(wú)向圖的雙連通分量割點(diǎn)橋的算法也有著很深的聯(lián)系。學(xué)習(xí)該算法,也有助于深入理解求雙連通分量的算法,兩者可以類(lèi)比組合理解。固屬于同一連通分支。 參考資料http://blog.csdn.net/acmmmm/a...https://www.byvoid.com/blog/s...http://blog.csdn.net/noth...
摘要:相關(guān)操作就是判斷的不等號(hào)符號(hào)改反,初始值設(shè)為負(fù)無(wú)窮副本的最短路徑即為原圖的最長(zhǎng)路徑。方法是同上面一樣構(gòu)造圖,同時(shí)會(huì)添加負(fù)權(quán)重邊,再將所有邊取反,然后求最短路徑最短路徑存在則可行沒(méi)有負(fù)權(quán)重環(huán)就是可行的調(diào)度。 Algorithms Fourth EditionWritten By Robert Sedgewick & Kevin WayneTranslated By 謝路云Chapter ...
摘要:區(qū)別把數(shù)字對(duì)應(yīng)成字符。這個(gè)是字符串的第位。稍作修改可適應(yīng)不等長(zhǎng)的字符串。因此增加一個(gè)組別,記錄字符為空的頻次。 Algorithms Fourth EditionWritten By Robert Sedgewick & Kevin WayneTranslated By 謝路云Chapter 5 Section 1 字符串排序 參考資料http://blog.csdn.net/gua...
摘要:由的位置決定乘以幾,依次為,,,,,。遞歸算法遞歸算法就是本次結(jié)果用另外的參數(shù)調(diào)用自己。然而這個(gè)函數(shù)方法本身并沒(méi)有用,因?yàn)榉椒ㄖ腥魝鬟f參數(shù)為基本型如,在方法中對(duì)其值的改變并不會(huì)在主函數(shù)中產(chǎn)生影響。 Algorithms Fourth EditionWritten By Robert Sedgewick & Kevin WayneTranslated By 謝路云 筆記 二分查找 Bin...
摘要:堆中位置的結(jié)點(diǎn)的父節(jié)點(diǎn)的位置為,子節(jié)點(diǎn)的位置分別是和一個(gè)結(jié)論一棵大小為的完全二叉樹(shù)的高度為用數(shù)組堆實(shí)現(xiàn)的完全二叉樹(shù)是很?chē)?yán)格的,但它的靈活性足以使我們高效地實(shí)現(xiàn)優(yōu)先隊(duì)列。 Algorithms Fourth EditionWritten By Robert Sedgewick & Kevin WayneTranslated By 謝路云Chapter 2 Section 4 優(yōu)先隊(duì)列 ...
閱讀 3116·2021-10-12 10:20
閱讀 2835·2021-09-27 13:56
閱讀 806·2021-09-27 13:36
閱讀 1444·2021-09-26 09:46
閱讀 2433·2019-08-30 14:02
閱讀 2700·2019-08-28 18:14
閱讀 1280·2019-08-26 10:32
閱讀 1717·2019-08-23 18:25