日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

算法_有向图

發(fā)布時(shí)間:2025/7/25 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 算法_有向图 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一.定義以及和無向圖的區(qū)別

  一幅有向圖是由一組頂點(diǎn)和一組有方向的邊組成的,每條有方向的邊都連接著有序的一對(duì)頂點(diǎn).有向邊是由第一個(gè)頂點(diǎn)指出并指向第二個(gè)頂點(diǎn),用v->w來表示有向圖中一條由頂點(diǎn)v指向頂點(diǎn)w的一條邊.當(dāng)存在從v->w的有向路徑的時(shí)候,稱頂點(diǎn)w能夠由頂點(diǎn)v達(dá)到.和無向圖不同的是,在有向圖中由v能夠到達(dá)w,并不意味著由w也能到達(dá)v.下圖為一個(gè)有向圖舉例.

                      

?

二.有向圖的數(shù)據(jù)類型

  使用Bag表示有向圖,其中邊v->w表示為頂點(diǎn)v所對(duì)應(yīng)的鄰接鏈表中包含一個(gè)w頂點(diǎn),與無向圖不同的是,這里每條邊只會(huì)出現(xiàn)一次.有向圖的數(shù)據(jù)結(jié)構(gòu)類型如下:

/*** 有向圖的數(shù)據(jù)類型* @author Administrator**/ 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;}//添加一條邊v->w,由于是有向圖只要添加一條邊就可以了public void addEdge(int v,int w) {adj[v].add(w);E++;}public Iterable<Integer> adj(int v) {return adj[v];}//返回當(dāng)前圖的一個(gè)反向的圖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;} }

?三.有向圖中的可達(dá)性

  利用深度優(yōu)先搜索可以解決有向圖中的單點(diǎn)可達(dá)性問題:即:給定一幅有向圖和一個(gè)起點(diǎn)s,回答是否存在一條從s到達(dá)給定頂點(diǎn)v的有向路徑的問題.

/*** 從指定的圖中查找從s可達(dá)的所有頂點(diǎn).* 判斷一個(gè)點(diǎn)是否是從s可達(dá)的.* @author Administrator**/ public class DirectedDFS {private boolean[] marked;//從G中找出所有s可達(dá)的點(diǎn)public DirectedDFS(Digraph G,int s) {marked=new boolean[G.V()];dfs(G,s);}//G中找出一系列點(diǎn)可達(dá)的點(diǎn)public DirectedDFS(Digraph G,Iterable<Integer> sources) {marked=new boolean[G.V()];for(int s:sources) {if(!marked[s]) dfs(G,s);}}//深度優(yōu)先搜素判斷.private void dfs(Digraph G, int v) { marked[v]=true;for(int w:G.adj(v)) {if(!marked[w]) dfs(G,w);}}//v是可達(dá)的嗎public boolean marked(int v) {return marked[v];} }

四.尋找有向環(huán)

  下面的代碼可以用來檢測(cè)給定的有向圖中是否含有有向環(huán),如果有,則按照路徑的方向返回環(huán)上的所有頂點(diǎn).在執(zhí)行dfs的時(shí)候,查找的是從起點(diǎn)到v的有向路徑,onStack數(shù)組標(biāo)記了遞歸調(diào)用的棧上的所有頂點(diǎn),同時(shí)也加入了edgeTo數(shù)組,在找到有向環(huán)的時(shí)候返回環(huán)中的所有頂點(diǎn).

/*** 有向圖G是否含有有向環(huán)* 獲取有向環(huán)中的所有頂點(diǎn)* @author Administrator**/ public class DirectedCycle {private boolean[] marked; private int[] edgeTo;private Stack<Integer> cycle; //有向環(huán)中的所有頂點(diǎn)private boolean[] onStack; //遞歸調(diào)用的棧上的所有頂點(diǎn)public DirectedCycle(Digraph G) {edgeTo=new int[G.V()];onStack=new boolean[G.V()];marked=new boolean[G.V()];for(int v=0;v<G.V();v++) {if(!marked[v]) dfs(G,v);}} /*** 該算法的關(guān)鍵步驟在于onStack數(shù)組的運(yùn)用.* onStack數(shù)組標(biāo)記的是當(dāng)前遍歷的點(diǎn).如果對(duì)于一個(gè)點(diǎn)指向的所有點(diǎn)中的某個(gè)點(diǎn)* onstack[v]=true.代表該點(diǎn)正在被遍歷也就是說* 該點(diǎn)存在一條路徑,指向這個(gè)點(diǎn).而這個(gè)點(diǎn)現(xiàn)在又可以指向該點(diǎn),* 即存在環(huán)的結(jié)構(gòu)~* @param G* @param v*/ private void dfs(Digraph G, int v) {onStack[v]=true;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);}}//dfs方法結(jié)束,對(duì)于該點(diǎn)的遞歸調(diào)用結(jié)束.該點(diǎn)指向的所有點(diǎn)已經(jīng)遍歷完畢onStack[v]=false;}private boolean hasCycle() {return cycle!=null;}public Iterable<Integer> cycle() {return cycle;} }

五.頂點(diǎn)的深度優(yōu)先次序以及拓補(bǔ)排序

  拓補(bǔ)排序:給定一幅有向圖,將所有的頂點(diǎn)排序,使得所有的有向邊均從排在前面的元素指向排在后面的元素.如果存在有向環(huán)的話,那么拓補(bǔ)排序無法完成.

  要實(shí)現(xiàn)有向圖的拓補(bǔ)排序,利用標(biāo)準(zhǔn)深度優(yōu)先搜索順序即可完成任務(wù).這里頂點(diǎn)會(huì)有三種排列順序:

  1.前序:在遞歸調(diào)用前將頂點(diǎn)加入隊(duì)列

  2.后序:在遞歸調(diào)用之后將頂點(diǎn)加入隊(duì)列

  3.逆后序:在遞歸調(diào)用之后將頂點(diǎn)壓入棧.

  具體的操作見下面的代碼:

//有向圖中基于深度優(yōu)先搜索的拓補(bǔ)排序 public class DepthFirstOrder {private boolean[] marked;private Queue<Integer> pre; //所有頂點(diǎn)的前序排列private Queue<Integer> post; //所有頂點(diǎn)的后序排列private Stack<Integer> reversePost;//所有頂點(diǎn)的逆后序排列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<Integer> pre() {return pre;}public Iterable<Integer> post() {return post;}public Iterable<Integer> reversePost() {return reversePost;} }

  拓補(bǔ)排序的實(shí)現(xiàn)依賴于上面的API,實(shí)際上拓補(bǔ)排序即為所有頂點(diǎn)的逆后序排列,證明如下:

  對(duì)于任意邊v->w,在調(diào)用dfs(v)的時(shí)候,下面三種情況必然有一種成立:

  1.dfs(w)被調(diào)用且返回.(此時(shí)w被標(biāo)記)

  2.dfs(w)還沒有被調(diào)用,因此v->w會(huì)返回dfs(w),且dfs(w)在dfs(v)之前返回

  3.dfs(w)已經(jīng)被調(diào)用沒有返回,在有向無環(huán)圖中這種情況不可能!

  在1,2這兩種情況中dfs(w)都在dfs(v)之前返回,也就是說在逆后序排列中,順序?yàn)関,w.滿足拓補(bǔ)排序的要求!

  拓補(bǔ)排序的代碼如下:

public class Topological {private Iterable<Integer> order; //頂點(diǎn)的拓補(bǔ)排序public Topological(Digraph G) {DirectedCycle cyclefinder=new DirectedCycle(G);if(!cyclefinder.hasCycle()) {//只有無環(huán)才能進(jìn)行拓補(bǔ)排序DepthFirstOrder dfs=new DepthFirstOrder(G);order=dfs.reversePost();}}public Iterable<Integer> order() {return order;}public boolean isDAG() {return order!=null;} }

六.有向圖的強(qiáng)連通性

  定義:如果兩個(gè)頂點(diǎn)v和w是互相可達(dá)的,則稱它們?yōu)閺?qiáng)連通的.也就是說既存在一條從v到w的有向路徑也存在一條從w到v的有向路徑.如果一幅有向圖中的任意兩個(gè)頂點(diǎn)都是強(qiáng)連通的,則稱這副有向圖也是強(qiáng)連通的.任意頂點(diǎn)和自己都是強(qiáng)連通的.

  ? 強(qiáng)連通性將頂點(diǎn)分為了一些等價(jià)類,每個(gè)等價(jià)類都是由相互均為強(qiáng)連通的頂點(diǎn)的最大子集組成的.我們將這些子集稱為強(qiáng)連通分量.需要注意的是強(qiáng)連通分量是基于頂點(diǎn)的,而并非基于邊.

  下面的代碼采用如下步驟來計(jì)算強(qiáng)連通分量以及兩個(gè)點(diǎn)是否是強(qiáng)連通的:

  1.在給定的有向圖中,使用DepthFirsetOrder來計(jì)算它的反向圖GR的逆后序排列

  2.按照第一步計(jì)算得到的順序采用深度優(yōu)先搜索來訪問所有未被標(biāo)記的點(diǎn)

  3.在構(gòu)造函數(shù)中,所有在同一個(gè)遞歸dfs()調(diào)用中被訪問到的頂點(diǎn)都是在同一個(gè)強(qiáng)連通分量中.

  下面的代碼實(shí)現(xiàn)遵循了上面的思路:

/*** 該算法實(shí)現(xiàn)的關(guān)鍵:* 使用深度優(yōu)先搜索查找給定有向圖的反向圖GR.根據(jù)由此得到的所有頂點(diǎn)的逆后序* 再次用深度優(yōu)先搜索處理有向圖G.其構(gòu)造函數(shù)的每一次遞歸調(diào)用所標(biāo)記的頂點(diǎn)都在* 同一個(gè)強(qiáng)連通分量中.* 解決問題:* 判斷兩個(gè)點(diǎn)是否是強(qiáng)連通的* 判斷總共有多少個(gè)連通分量* @author Administrator**/ public class KosarajuSCC {private boolean[] marked;//已經(jīng)訪問過的頂點(diǎn)private int[] id; //強(qiáng)連通分量的標(biāo)識(shí)符private int count; //強(qiáng)聯(lián)通分量的數(shù)量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() {return count;} }

  為了驗(yàn)證這個(gè)代碼的正確性,需要證明這個(gè)命題,即:

  使用深度優(yōu)先搜索查找給定有向圖的GR,根據(jù)由此得到的所有頂點(diǎn)的逆后序再次用深度優(yōu)先搜索處理有向圖G,其構(gòu)造函數(shù)的每一次遞歸調(diào)用所標(biāo)記的頂點(diǎn)都在同一個(gè)強(qiáng)連通分量中.

  證明如下:

  首先用反證法證明每個(gè)和s強(qiáng)連通的頂點(diǎn)v都會(huì)在構(gòu)造函數(shù)中調(diào)用的dfs(G,s)中被訪問到.假設(shè)有一個(gè)頂點(diǎn)v沒有被訪問到.因?yàn)榇嬖趶膕到v的路徑,那么說明v肯定在之前被訪問過了.但是因?yàn)橐泊嬖趘到s的路徑,那么dfs(G,v)調(diào)用中,s肯定會(huì)被標(biāo)記,因此構(gòu)造函數(shù)肯定不會(huì)調(diào)用dfs(G,s)的,矛盾.

  其次要證明構(gòu)造函數(shù)調(diào)用的dfs(G,s)所到達(dá)的任意頂點(diǎn)v都必然是和s強(qiáng)連通的.設(shè)v為dfs(G,s)所到達(dá)的某個(gè)頂點(diǎn).那么G中必然存在從s到v的路徑.因此現(xiàn)在只需要證明在GR中存在一條從s到v的路徑即可.而由于按照逆后序進(jìn)行的深度優(yōu)先搜索,因此在GR中進(jìn)行的深度優(yōu)先搜索意味著,dfs(G,v)必然在dfs(G,s)之前就結(jié)束了.這樣dfs(G,v)的調(diào)用存在兩種情況:

  1.調(diào)用在dfs(G,s)的調(diào)用之前(并且也在dfs(G,s)的調(diào)用之前結(jié)束.這種情況不可能存在,因?yàn)樵贕R中存在一條從v到s的路徑.

  2.調(diào)用在dfs(G,s)的調(diào)用之后(并且也在dfs(G,s)的結(jié)束之前結(jié)束.這種情況說明GR中存在一條從s到v的路徑.證明完成.

 

轉(zhuǎn)載于:https://www.cnblogs.com/hlhdidi/p/5960018.html

總結(jié)

以上是生活随笔為你收集整理的算法_有向图的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。