C#圖表算法之有向圖
在有向圖中,邊是單向的:每條邊連接的兩個(gè)頂點(diǎn)都是一個(gè)有序?qū)?,它們的鄰接性是單向的。許多應(yīng)用都是天然的有向圖,如下圖。為實(shí)現(xiàn)添加這種單向性的限制很容易也很自然,看起來(lái)沒(méi)什么壞處。但實(shí)際上這種組合性的結(jié)構(gòu)對(duì)算法有深刻的影響,使得有向圖和無(wú)向圖的處理大有不同。

1.術(shù)語(yǔ)
雖然我們?yōu)橛邢驁D的定義和無(wú)向圖幾乎相同(將使用的部分算法和代碼也是),但為了說(shuō)明邊的方向性而產(chǎn)生的細(xì)小文字差異所代表的結(jié)構(gòu)特性是重點(diǎn)。
定義:一幅有方向性的圖(或有向圖)是由一組頂點(diǎn)和一組有方向的邊組成的,每條有方向的邊都連著有序的一對(duì)頂點(diǎn)。
我們稱一條有向邊由第一個(gè)頂點(diǎn)指出并指向第二個(gè)頂點(diǎn)。在一幅有向圖中,一個(gè)頂點(diǎn)的出度為由該頂點(diǎn)指出的邊的總數(shù);一個(gè)頂點(diǎn)的入度為指向該頂點(diǎn)的邊的總數(shù)。一條有向邊的第一個(gè)頂點(diǎn)稱為它的頭,第二個(gè)頂點(diǎn)則稱為它的尾。用 v->w 表示有向圖中一條由v 指向 w 的邊。一幅有向圖的兩個(gè)頂點(diǎn)的關(guān)系可能有四種:沒(méi)有邊相連;v->w; w-> v;v->w 和 w->v。

在一幅有向圖中,有向路徑由一系列頂點(diǎn)組成,對(duì)于其中的每個(gè)頂點(diǎn)都存在一條有向邊從它指向序列中的下一個(gè)頂點(diǎn)。有向環(huán)為一條至少含有一條邊且起點(diǎn)和終點(diǎn)相同的有向路徑。路徑或環(huán)的長(zhǎng)度即為其中所包含的邊數(shù)。
當(dāng)存在從 v 到 w 的有向路徑時(shí),稱頂點(diǎn) w 能夠由頂點(diǎn) v 達(dá)到。我們需要理解有向圖中的可達(dá)性和無(wú)向圖中的連通性的區(qū)別。
2.有向圖的數(shù)據(jù)類型
有向圖API

有向圖表示
我們使用鄰接表來(lái)表示有向圖,其中邊 v -> w 表示頂點(diǎn) v 所對(duì)應(yīng)的鄰接鏈表中包含一個(gè) w 頂點(diǎn)。這種表示方法和無(wú)向圖幾乎相同而且更明晰,因?yàn)槊織l邊都只會(huì)出現(xiàn)一次。
有向圖取反
Digraph 的 API 中還添加了一個(gè) Reverse 方法。它返回該有向圖的一個(gè)副本,但將其中所有邊的方向反轉(zhuǎn)。在處理有向圖時(shí)這個(gè)方法有時(shí)很有用,因?yàn)檫@樣用例就可以找出“指向”每個(gè)頂點(diǎn)的所有邊,而 Adj 方法給出的是由每個(gè)頂點(diǎn)指出的邊所連接的所有頂點(diǎn)。
頂點(diǎn)的符號(hào)名
在有向圖中,使用符號(hào)名作為頂點(diǎn)也很簡(jiǎn)單,參考SymbolGraph。
namespace Digraphs
{
public class Digraph
{
private int v;
private int e;
private List<int>[] adj;
public Digraph(int V)
{
this.v = V;
this.e = 0;
adj = new List<int>[v];
for (var i = 0; i < v; i++)
{
adj[i] = new List<int>();
}
}
public int V()
{
return v;
}
public int E()
{
return e;
}
public List<int> Adj(int v)
{
return adj[v];
}
public void AddEdge(int v, int w)
{
adj[v].Add(w);
e++;
}
public Digraph Reverse()
{
Digraph R = new Digraph(v);
for (var i = 0; i < v; i++)
foreach (var w in Adj(i))
R.AddEdge(w,i);
return R;
}
}
}3.有向圖的可達(dá)性
在無(wú)向圖中介紹的深度優(yōu)先搜索DepthFirstSearch ,解決了單點(diǎn)連通性的問(wèn)題,使得用例可以判定其他頂點(diǎn)和給定的起點(diǎn)是否連通。使用完全相同的代碼,將其中的 Graph 替換成 Digraph 也可以解決有向圖中的單點(diǎn)可達(dá)性問(wèn)題(給定一幅有向圖和一個(gè)起點(diǎn) s ,是否存在一條從 s 到達(dá)給定頂點(diǎn) v 的有向路徑?)。

在添加了一個(gè)接受多個(gè)頂點(diǎn)的構(gòu)造函數(shù)之后,這份 API 使得用例能夠解決一個(gè)更加一般的問(wèn)題 -- 多點(diǎn)可達(dá)性 (給定一幅有向圖和頂點(diǎn)的集合,是否存在一條從集合中的任意頂點(diǎn)到達(dá)給定頂點(diǎn) v 的有向路徑?)
下面的DirectedDFS 算法使用了解決圖處理的標(biāo)準(zhǔn)范例和標(biāo)準(zhǔn)的深度優(yōu)先搜索來(lái)解決。對(duì)每個(gè)起點(diǎn)調(diào)用遞歸方法 Dfs ,以標(biāo)記遇到的任意頂點(diǎn)。
namespace Digraphs
{
public class DirectedDFS
{
private bool[] marked;
public DirectedDFS(Digraph G, int s)
{
marked = new bool[G.V()];
Dfs(G,s);
}
public DirectedDFS(Digraph G, IEnumerable<int> sources)
{
marked = new bool[G.V()];
foreach (var s in sources)
{
if (!marked[s])
Dfs(G,s);
}
}
private void Dfs(Digraph G, int V)
{
marked[V] = true;
foreach (var w in G.Adj(V))
{
if (!marked[w])
Dfs(G,w);
}
}
public bool Marked(int v)
{
return marked[v];
}
}
}在有向圖中,深度優(yōu)先搜索標(biāo)記由一個(gè)集合的頂點(diǎn)可達(dá)的所有頂點(diǎn)所需的時(shí)間與被標(biāo)記的所有頂點(diǎn)的出度之和成正比。

有向圖的尋路
在無(wú)向圖中的尋找路徑的算法,只需將 Graph 替換為 Digraph 就能夠解決下面問(wèn)題:
- 1.單點(diǎn)有向路徑:給定一幅有向圖和一個(gè)起點(diǎn) s ,從 s 到給定目的頂點(diǎn)是否存在一條有向路徑?如果有,找出這條路徑。
- 2.單點(diǎn)最短有向路徑:給定一幅有向圖和一個(gè)起點(diǎn) s ,從 s 到給定目的頂點(diǎn) v 是否存在一條有向路徑?如果有,找出其中最短的那條(所含邊數(shù)最少)。
4.環(huán)和有向無(wú)環(huán)圖
在和有向圖相關(guān)的實(shí)際應(yīng)用中,有向環(huán)特別的重要。沒(méi)有計(jì)算機(jī)的幫助,在一幅普通的有向圖中找出有向環(huán)可能會(huì)很困難。從原則上來(lái)說(shuō),一幅有向圖可能含有大量的環(huán);在實(shí)際應(yīng)用中,我們一般只重點(diǎn)關(guān)注其中一小部分,或者只想知道它們是否存在。

調(diào)度問(wèn)題
一種應(yīng)用廣泛的模型是給定一組任務(wù)并安排它們的執(zhí)行順序,限制條件是這些任務(wù)的執(zhí)行方法和開(kāi)始時(shí)間。限制條件還可能包括任務(wù)的耗時(shí)以及消耗的資源。最重要的一種限制條件叫做優(yōu)先級(jí)限制,它指明了哪些任務(wù)必須在哪些任務(wù)之前完成。不同類型的限制條件會(huì)產(chǎn)生不同類型不同難度的調(diào)度問(wèn)題。
下面以一個(gè)正在安排課程的大學(xué)生為例,有些課程是其他課程的先導(dǎo)課程:

如果假設(shè)該學(xué)生一次只能修一門(mén)課程,就會(huì)遇到優(yōu)先級(jí)下的調(diào)度問(wèn)題:給定一組需要完成的任務(wù),以及一組關(guān)于任務(wù)完成的先后次序的優(yōu)先級(jí)限制。在滿足限制條件的前提下應(yīng)該如何安排并完成所有任務(wù)?
對(duì)于任意一個(gè)這樣的問(wèn)題,我們先畫(huà)出一幅有向圖,其中頂點(diǎn)對(duì)應(yīng)任務(wù),有向邊對(duì)應(yīng)優(yōu)先級(jí)順序。為了簡(jiǎn)化問(wèn)題,我們以整數(shù)為頂點(diǎn):

在有向圖中,優(yōu)先級(jí)限制下的調(diào)度問(wèn)題等價(jià)于一個(gè)基本問(wèn)題--拓?fù)渑判?/strong>:給定一幅圖,將所有頂點(diǎn)排序,使得所有的有向邊均從排在前面的元素指向排在后面的元素(或者說(shuō)明無(wú)法做到這一點(diǎn))。
如圖,所有的邊都是向下的,所以清晰地表示了這幅有向圖模型所代表的有優(yōu)先級(jí)限制的調(diào)度問(wèn)題的一個(gè)解決方法:按照這個(gè)順序,該同學(xué)可以滿足先導(dǎo)課程限制的條件下修完所有課程。

有向圖中的環(huán)
如果任務(wù) x 必須在任務(wù) y 之前完成,而任務(wù) y 必須在任務(wù) z 之前完成,但任務(wù) z 又必須在任務(wù) x 之前完成,那肯定是有人搞錯(cuò)了,因?yàn)檫@三個(gè)限制條件是不可能被同時(shí)滿足的。一般來(lái)說(shuō),如果一個(gè)優(yōu)先級(jí)限制的問(wèn)題中存在有向環(huán),那么這個(gè)問(wèn)題肯定是無(wú)解的。要檢查這種錯(cuò)誤,需要解決 有向環(huán)檢測(cè):給定的有向圖中包含有向環(huán)嗎?如果有,按照路徑的方向從某個(gè)頂點(diǎn)并返回自己來(lái)找到環(huán)上的所有頂點(diǎn)。
一幅有向圖中含有環(huán)的數(shù)量可能是圖的大小的指數(shù)級(jí)別,因此我們只需找到一個(gè)環(huán)即可,而不是所有環(huán)。在任務(wù)調(diào)度和其他許多實(shí)際問(wèn)題中不允許出現(xiàn)有向環(huán),因此有向無(wú)環(huán)圖就變得很特殊。
基于深度優(yōu)先搜索可以解決有向環(huán)檢測(cè)的問(wèn)題,因?yàn)橛上到y(tǒng)維護(hù)的遞歸調(diào)用的棧表示的正是“當(dāng)前”正在遍歷的有向路徑。一旦我們找到了一條有向邊 v -> w 且 w 已經(jīng)存在于棧中,就找到了一個(gè)環(huán),因?yàn)闂1硎镜氖且粭l由 w 到 v 的有向路徑,而 v -> w 正好補(bǔ)全了這個(gè)環(huán)。如果沒(méi)有找到這樣的邊,就意味著這副有向圖是無(wú)環(huán)的。DirectedCycle 基于這個(gè)思想實(shí)現(xiàn)的:

namespace Digraphs
{
public class DirectedCycle
{
private bool[] marked;
private int[] edgeTo;
private Stack<int> cycle;//有向環(huán)中的所有頂點(diǎn)(如果存在)
private bool[] onStack;//遞歸調(diào)用的棧上的所有頂點(diǎn)
public DirectedCycle(Digraph G)
{
onStack = new bool[G.V()];
edgeTo = new int[G.V()];
marked = new bool[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;
marked[v] = true;
foreach (var w in G.Adj(v))
{
if (hasCycle())
return;
else if (!marked[w])
{
edgeTo[w] = v;
Dfs(G, w);
}
else if (onStack[w])
{
cycle = new Stack<int>();
for (int x = v; x != w; x = edgeTo[x])
cycle.Push(x);
cycle.Push(w);
cycle.Push(v);
}
}
onStack[v] = false;
}
private bool hasCycle()
{
return cycle != null;
}
public IEnumerable<int> Cycle()
{
return cycle;
}
}
}該類為標(biāo)準(zhǔn)的的遞歸 Dfs 方法添加了一個(gè)布爾類型的數(shù)組 onStack 來(lái)保存遞歸調(diào)用期間棧上的所有頂點(diǎn)。當(dāng)它找到一條邊 v -> w 且 w 在棧中時(shí),它就找到了一個(gè)有向環(huán)。環(huán)上的所有頂點(diǎn)可以通過(guò) edgeTo 中的鏈接得到。
在執(zhí)行 Dfs 時(shí),查找的是一條由起點(diǎn)到 v 的有向路徑。要保存這條路徑,DirectedCycle 維護(hù)了一個(gè)由頂點(diǎn)索引的數(shù)組onStack,以標(biāo)記遞歸調(diào)用的棧上的所有頂點(diǎn)(在調(diào)用 Dfs 時(shí)將 onStack[ v ] 設(shè)為 true,在調(diào)用結(jié)束時(shí)將其設(shè)為 false)。DirectedCycle 同時(shí)也使用了一個(gè)edgeTo 數(shù)組,在找到有向環(huán)時(shí)返回環(huán)中的所有頂點(diǎn)。
頂點(diǎn)的深度優(yōu)先次序與拓?fù)渑判?/h3>
優(yōu)先級(jí)限制下的調(diào)度問(wèn)題等價(jià)于計(jì)算有向無(wú)環(huán)圖中的所有頂點(diǎn)的拓?fù)渑判颍?/p>

下面算法的基本思想是深度優(yōu)先搜索正好只會(huì)訪問(wèn)每個(gè)頂點(diǎn)一次。如果將 Dfs 的參數(shù)頂點(diǎn)保存在一個(gè)數(shù)據(jù)結(jié)構(gòu)中,遍歷這個(gè)數(shù)據(jù)結(jié)構(gòu)實(shí)際上就能訪問(wèn)圖中的所有頂點(diǎn),遍歷的順序取決于這個(gè)數(shù)據(jù)結(jié)構(gòu)的性質(zhì)以及是在遞歸調(diào)用之前還是之后進(jìn)行保存。在典型的應(yīng)用中,頂點(diǎn)一下三種排列順序:
- 前序:在遞歸調(diào)用之前將頂點(diǎn)加入隊(duì)列;
- 后序:在遞歸調(diào)用之后將頂點(diǎn)加入隊(duì)列;
- 逆后序:在遞歸調(diào)用之后將頂點(diǎn)壓入棧。
該類允許用例用各種順序遍歷深度優(yōu)先搜索經(jīng)過(guò)得頂點(diǎn)。這在高級(jí)得有向圖處理算法非常有用,因?yàn)樗阉鞯眠f歸性使得我們能夠證明這段計(jì)算得許多性質(zhì)。
namespace Digraphs
{
public class DepthFirstOrder
{
private bool[] marked;
private Queue<int> pre;//所有頂點(diǎn)的前序排列
private Queue<int> post;//所有頂點(diǎn)的后序排列
private Stack<int> reversePost;//所有頂點(diǎn)的逆后序排列
public DepthFirstOrder(Digraph G)
{
marked = new bool[G.V()];
pre = new Queue<int>();
post = new Queue<int>();
reversePost = new Stack<int>();
for (var 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;
foreach (var w in G.Adj(v))
{
if (!marked[w])
Dfs(G,w);
}
post.Enqueue(v);
reversePost.Push(v);
}
public IEnumerable<int> Pre()
{
return pre;
}
public IEnumerable<int> Post()
{
return post;
}
public IEnumerable<int> ReversePost()
{
return reversePost;
}
}
}
一幅有向無(wú)環(huán)圖得拓?fù)渑判蚣礊樗许旤c(diǎn)的逆后序排列。
拓?fù)渑判?/h3>
namespace Digraphs
{
public class Topological
{
private IEnumerable<int> order;
public Topological(Digraph G)
{
DirectedCycle cycleFinder = new DirectedCycle(G);
if (cycleFinder.HasCycle())
{
DepthFirstOrder dfs = new DepthFirstOrder(G);
order = dfs.ReversePost();
}
}
public IEnumerable<int> Order()
{
return order;
}
public bool IsDAG()
{
return order != null;
}
}
}
namespace Digraphs
{
public class Topological
{
private IEnumerable<int> order;
public Topological(Digraph G)
{
DirectedCycle cycleFinder = new DirectedCycle(G);
if (cycleFinder.HasCycle())
{
DepthFirstOrder dfs = new DepthFirstOrder(G);
order = dfs.ReversePost();
}
}
public IEnumerable<int> Order()
{
return order;
}
public bool IsDAG()
{
return order != null;
}
}
}這段使用DirectedCycle 檢測(cè)是否有環(huán),使用DepthFirstOrder 返回有向圖的逆后序。

使用深度優(yōu)先搜索對(duì)有向無(wú)環(huán)圖進(jìn)行拓?fù)渑判蛩璧臅r(shí)間和 V+E 成正比。第一遍深度優(yōu)先搜索保證了不存在有向環(huán),第二遍深度優(yōu)先搜索產(chǎn)生了頂點(diǎn)的逆后序排列。
在實(shí)際應(yīng)用中,拓?fù)渑判蚝陀邢颦h(huán)的檢測(cè)總是一起出現(xiàn),因?yàn)橛邢颦h(huán)的檢測(cè)是排序的前提。例如,在一個(gè)任務(wù)調(diào)度應(yīng)用中,無(wú)論計(jì)劃如何安排,其背后的有向圖中包含的環(huán)意味著存在一個(gè)必須被糾正的嚴(yán)重錯(cuò)誤。因此,解決任務(wù)調(diào)度類應(yīng)用通常需要一下3步:
- 1.指明任務(wù)和優(yōu)先級(jí)條件;
- 2.不斷檢測(cè)并去除有向圖中的所有環(huán),以確保存在可行方案;
- 3.使用拓?fù)渑判蚪鉀Q調(diào)度問(wèn)題。
類似地,調(diào)度方案的任何變動(dòng)之后都需要再次檢查是否存在環(huán),然后再計(jì)算新的調(diào)度安排。
5.有向圖中的強(qiáng)連通性
如果兩個(gè)頂點(diǎn) v 和 w 是相互可達(dá)的,則稱它們?yōu)閺?qiáng)連通的。也就是說(shuō),即存在一條從 v 到 w 的有向路徑,也存在一條從 w 到 v 的有向路徑。如果一幅有向圖中的任意兩個(gè)頂點(diǎn)都是強(qiáng)連通的,則稱這副有向圖也是強(qiáng)連通的。
下面是強(qiáng)連通圖的例子,可以看到,環(huán)在強(qiáng)連通性的理解上起著重要的作用。

強(qiáng)連通分量
和無(wú)向圖中的連通性一樣,有向圖中的強(qiáng)連通性也是一種頂點(diǎn)之間的等價(jià)關(guān)系:
- 自反性:任意頂點(diǎn) v 和自己都是強(qiáng)連通的。
- 對(duì)稱性:如果 v 和 w 是強(qiáng)連通的,那么 w 和 v 也是。
- 傳遞性:如果 v 和 w 是強(qiáng)連通的且 w 和 x 也是強(qiáng)連通的,那么 v 和 x 也是強(qiáng)連通的。
作為一種等價(jià)關(guān)系,強(qiáng)連通性將所有頂點(diǎn)分為了一些等價(jià)類,每個(gè)等價(jià)類都是由相互均為強(qiáng)連通的頂點(diǎn)的最大子集組成。我們稱這些子集為強(qiáng)連通分量。如下圖,一個(gè)含有 V 個(gè)頂點(diǎn)的有向圖含有 1~ V個(gè)強(qiáng)連通分量——一個(gè)強(qiáng)連通圖只含有一個(gè)強(qiáng)連通分量,而一個(gè)有向無(wú)環(huán)圖則含有 V 個(gè)強(qiáng)連通分量。需要注意的是強(qiáng)連通分量的定義是基于頂點(diǎn)的,而不是邊。有些邊連接的兩個(gè)頂點(diǎn)都在同一個(gè)強(qiáng)連通分量中,而有些邊連接的兩個(gè)頂點(diǎn)則不在同一強(qiáng)連通分量中。

強(qiáng)連通分量API

設(shè)計(jì)一種平方級(jí)別的算法來(lái)計(jì)算強(qiáng)連通分量并不困難,單對(duì)于處理實(shí)際應(yīng)用中的大型圖來(lái)說(shuō),平方級(jí)別的時(shí)間和空間需求是不可接受的。
Kosaraju算法
在有向圖中如何高效地計(jì)算強(qiáng)連通分量?我們只需修改無(wú)向圖連通分量的算法 CC,KosarajuCC 算法如下,它將會(huì)完成一下任務(wù):
1.在給定的一幅有向圖 G 中,使用 DepthFirstOrder 來(lái)計(jì)算它的反向圖 GR 的逆后序排列;
2.在 G 中進(jìn)行標(biāo)準(zhǔn)的深度優(yōu)先搜索,但是要按照剛才計(jì)算得到的順序而非標(biāo)準(zhǔn)的順序來(lái)訪問(wèn)所有未被標(biāo)記的頂點(diǎn);
3.在構(gòu)造函數(shù)中,所有在同一個(gè)遞歸 Dfs() 調(diào)用中被訪問(wèn)到的頂點(diǎn)都在同一個(gè)強(qiáng)連通分量中,將它們按照和 CC 相同的方式識(shí)別出來(lái)。
namespace Digraphs
{
public class KosarajuCC
{
private bool[] marked;//已訪問(wèn)的頂點(diǎn)
private int[] id;//強(qiáng)連通分量的標(biāo)識(shí)符
private int count;//強(qiáng)連通分量的數(shù)量
public KosarajuCC(Digraph G)
{
marked = new bool[G.V()];
id = new int[G.V()];
DepthFirstOrder order = new DepthFirstOrder(G.Reverse());
foreach (var s in order.ReversePost())
{
if (!marked[s])
{
Dfs(G,s);
count++;
}
}
}
private void Dfs(Digraph G, int v)
{
marked[v] = true;
id[v] = count;
foreach (var w in G.Adj(v))
{
if (!marked[w])
Dfs(G,w);
}
}
public bool StronglyConnected(int v, int w)
{
return id[v] == id[w];
}
public int Id(int v)
{
return id[v];
}
public int Count()
{
return count;
}
}
}

Kosaraju 算法的預(yù)處理所需的時(shí)間和空間與 V+E 成正比且支持常數(shù)時(shí)間的有向圖強(qiáng)連通性的查詢。
再談可達(dá)性
在無(wú)向圖中如果兩個(gè)頂點(diǎn) V 和 W 是連通的,那么就既存在一條從 v 到 w 的路徑也存在一條從 w 到 v 的路徑。在有向圖中如果兩個(gè)頂點(diǎn) v 和 w 是強(qiáng)連通的,那么就既存在一條從 v 到 w 的路徑也存在另一條從 w 到 v 的路徑。但對(duì)于一對(duì)非強(qiáng)連通的頂點(diǎn),也許存在一條從 v 到 w 的路徑,也許存在一條從 w 到 v 的路徑,也許兩條都不存在,但不可能兩條都存在。
頂點(diǎn)對(duì)的可達(dá)性:對(duì)于無(wú)向圖,等價(jià)于連通性問(wèn)題;對(duì)于有向圖,它和強(qiáng)連通性有很大區(qū)別。 CC 實(shí)現(xiàn)需要線性級(jí)別的預(yù)處理時(shí)間才能支持常數(shù)時(shí)間的操作。在有向圖的相應(yīng)實(shí)現(xiàn)中能否達(dá)到這樣的性能?
有向圖 G 的傳遞閉包是由相同的一組頂點(diǎn)組成的另一幅有向圖,在傳遞閉包中存在一條從 v 指向 w 的邊當(dāng)且僅當(dāng)在 G 中 w 是從 v 可達(dá)的。

根據(jù)約定,每個(gè)頂點(diǎn)對(duì)于自己都是可達(dá)的,因此傳遞閉包會(huì)含有 V 個(gè)自環(huán)。上圖只有 22 條有向邊,但它的傳遞閉包含有可能的 169 條有向邊中的 102 條。一般來(lái)說(shuō),一幅有向圖的傳遞閉包中所含的邊都比原圖中多得多。例如,含有 V 個(gè)頂點(diǎn)和 V 條邊的有向環(huán)的傳遞閉包是一幅含有 V 的平方條邊的有向完全圖。因?yàn)閭鬟f閉包一般都是稠密的,我們通常都將它們表示為一個(gè)布爾值矩陣,其中 v 行 w 列的值為 true 當(dāng)且僅當(dāng) w 是從 v 可達(dá)的。與其計(jì)算一幅有向圖的傳遞閉包,不如使用深度優(yōu)先搜索來(lái)實(shí)現(xiàn)如下API:

下面的算法使用DirectedDFS 實(shí)現(xiàn):
namespace Digraphs
{
public class TransitiveClosure
{
private DirectedDFS[] all;
public TransitiveClosure(Digraph G)
{
all = new DirectedDFS[G.V()];
for (var v = 0; v < G.V(); v++)
all[v] = new DirectedDFS(G,v);
}
public bool Reachable(int v, int w)
{
return all[v].Marked(w);
}
}
}該算法無(wú)論對(duì)于稀疏圖還是稠密圖,都是理想解決方案,但對(duì)于大型有向圖不適用,因?yàn)闃?gòu)造函數(shù)所需的空間和 V 的平方成正比,所需的時(shí)間和 V(V+ E) 成正比。
總結(jié)

到此這篇關(guān)于C#圖表算法之有向圖的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C#實(shí)現(xiàn)Nginx平滑加權(quán)輪詢算法
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)Nginx平滑加權(quán)輪詢算法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07
C#計(jì)算兩個(gè)文件的相對(duì)目錄算法的實(shí)例代碼
現(xiàn)在已知兩個(gè)文件相對(duì)于網(wǎng)站根目錄的路徑,如何計(jì)算相對(duì)路徑呢,有需要的朋友可以參考一下2013-09-09
c#中的virtual方法及應(yīng)用場(chǎng)景分析
在 C# 中,virtual?關(guān)鍵字用于修飾方法、屬性、索引器或事件,這篇文章主要介紹了c#中的virtual方法及應(yīng)用場(chǎng)景分析,需要的朋友可以參考下2025-03-03

