C#圖表算法之有向圖
在有向圖中,邊是單向的:每條邊連接的兩個(gè)頂點(diǎn)都是一個(gè)有序?qū)?,它們的鄰接性是單向的。許多應(yīng)用都是天然的有向圖,如下圖。為實(shí)現(xiàn)添加這種單向性的限制很容易也很自然,看起來沒什么壞處。但實(shí)際上這種組合性的結(jié)構(gòu)對(duì)算法有深刻的影響,使得有向圖和無向圖的處理大有不同。
1.術(shù)語
雖然我們?yōu)橛邢驁D的定義和無向圖幾乎相同(將使用的部分算法和代碼也是),但為了說明邊的方向性而產(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)系可能有四種:沒有邊相連;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)的長度即為其中所包含的邊數(shù)。
當(dāng)存在從 v 到 w 的有向路徑時(shí),稱頂點(diǎn) w 能夠由頂點(diǎn) v 達(dá)到。我們需要理解有向圖中的可達(dá)性和無向圖中的連通性的區(qū)別。
2.有向圖的數(shù)據(jù)類型
有向圖API
有向圖表示
我們使用鄰接表來表示有向圖,其中邊 v -> w 表示頂點(diǎn) v 所對(duì)應(yīng)的鄰接鏈表中包含一個(gè) w 頂點(diǎn)。這種表示方法和無向圖幾乎相同而且更明晰,因?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)也很簡單,參考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á)性
在無向圖中介紹的深度優(yōu)先搜索DepthFirstSearch ,解決了單點(diǎn)連通性的問題,使得用例可以判定其他頂點(diǎn)和給定的起點(diǎn)是否連通。使用完全相同的代碼,將其中的 Graph 替換成 Digraph 也可以解決有向圖中的單點(diǎn)可達(dá)性問題(給定一幅有向圖和一個(gè)起點(diǎn) s ,是否存在一條從 s 到達(dá)給定頂點(diǎn) v 的有向路徑?)。
在添加了一個(gè)接受多個(gè)頂點(diǎn)的構(gòu)造函數(shù)之后,這份 API 使得用例能夠解決一個(gè)更加一般的問題 -- 多點(diǎn)可達(dá)性 (給定一幅有向圖和頂點(diǎn)的集合,是否存在一條從集合中的任意頂點(diǎn)到達(dá)給定頂點(diǎn) v 的有向路徑?)
下面的DirectedDFS 算法使用了解決圖處理的標(biāo)準(zhǔn)范例和標(biāo)準(zhǔn)的深度優(yōu)先搜索來解決。對(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)的出度之和成正比。
有向圖的尋路
在無向圖中的尋找路徑的算法,只需將 Graph 替換為 Digraph 就能夠解決下面問題:
- 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)和有向無環(huán)圖
在和有向圖相關(guān)的實(shí)際應(yīng)用中,有向環(huán)特別的重要。沒有計(jì)算機(jī)的幫助,在一幅普通的有向圖中找出有向環(huán)可能會(huì)很困難。從原則上來說,一幅有向圖可能含有大量的環(huán);在實(shí)際應(yīng)用中,我們一般只重點(diǎn)關(guān)注其中一小部分,或者只想知道它們是否存在。
調(diào)度問題
一種應(yīng)用廣泛的模型是給定一組任務(wù)并安排它們的執(zhí)行順序,限制條件是這些任務(wù)的執(zhí)行方法和開始時(shí)間。限制條件還可能包括任務(wù)的耗時(shí)以及消耗的資源。最重要的一種限制條件叫做優(yōu)先級(jí)限制,它指明了哪些任務(wù)必須在哪些任務(wù)之前完成。不同類型的限制條件會(huì)產(chǎn)生不同類型不同難度的調(diào)度問題。
下面以一個(gè)正在安排課程的大學(xué)生為例,有些課程是其他課程的先導(dǎo)課程:
如果假設(shè)該學(xué)生一次只能修一門課程,就會(huì)遇到優(yōu)先級(jí)下的調(diào)度問題:給定一組需要完成的任務(wù),以及一組關(guān)于任務(wù)完成的先后次序的優(yōu)先級(jí)限制。在滿足限制條件的前提下應(yīng)該如何安排并完成所有任務(wù)?
對(duì)于任意一個(gè)這樣的問題,我們先畫出一幅有向圖,其中頂點(diǎn)對(duì)應(yīng)任務(wù),有向邊對(duì)應(yīng)優(yōu)先級(jí)順序。為了簡化問題,我們以整數(shù)為頂點(diǎn):
在有向圖中,優(yōu)先級(jí)限制下的調(diào)度問題等價(jià)于一個(gè)基本問題--拓?fù)渑判?/strong>:給定一幅圖,將所有頂點(diǎn)排序,使得所有的有向邊均從排在前面的元素指向排在后面的元素(或者說明無法做到這一點(diǎn))。
如圖,所有的邊都是向下的,所以清晰地表示了這幅有向圖模型所代表的有優(yōu)先級(jí)限制的調(diào)度問題的一個(gè)解決方法:按照這個(gè)順序,該同學(xué)可以滿足先導(dǎo)課程限制的條件下修完所有課程。
有向圖中的環(huán)
如果任務(wù) x 必須在任務(wù) y 之前完成,而任務(wù) y 必須在任務(wù) z 之前完成,但任務(wù) z 又必須在任務(wù) x 之前完成,那肯定是有人搞錯(cuò)了,因?yàn)檫@三個(gè)限制條件是不可能被同時(shí)滿足的。一般來說,如果一個(gè)優(yōu)先級(jí)限制的問題中存在有向環(huán),那么這個(gè)問題肯定是無解的。要檢查這種錯(cuò)誤,需要解決 有向環(huán)檢測:給定的有向圖中包含有向環(huán)嗎?如果有,按照路徑的方向從某個(gè)頂點(diǎn)并返回自己來找到環(huán)上的所有頂點(diǎn)。
一幅有向圖中含有環(huán)的數(shù)量可能是圖的大小的指數(shù)級(jí)別,因此我們只需找到一個(gè)環(huán)即可,而不是所有環(huán)。在任務(wù)調(diào)度和其他許多實(shí)際問題中不允許出現(xiàn)有向環(huán),因此有向無環(huán)圖就變得很特殊。
基于深度優(yōu)先搜索可以解決有向環(huá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)。如果沒有找到這樣的邊,就意味著這副有向圖是無環(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 來保存遞歸調(diào)用期間棧上的所有頂點(diǎn)。當(dāng)它找到一條邊 v -> w 且 w 在棧中時(shí),它就找到了一個(gè)有向環(huán)。環(huán)上的所有頂點(diǎn)可以通過 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)度問題等價(jià)于計(jì)算有向無環(huán)圖中的所有頂點(diǎn)的拓?fù)渑判颍?/p>
下面算法的基本思想是深度優(yōu)先搜索正好只會(huì)訪問每個(gè)頂點(diǎn)一次。如果將 Dfs 的參數(shù)頂點(diǎn)保存在一個(gè)數(shù)據(jù)結(jié)構(gòu)中,遍歷這個(gè)數(shù)據(jù)結(jié)構(gòu)實(shí)際上就能訪問圖中的所有頂點(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)過得頂點(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; } } }
一幅有向無環(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 檢測是否有環(huán),使用DepthFirstOrder 返回有向圖的逆后序。
使用深度優(yōu)先搜索對(duì)有向無環(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)的檢測總是一起出現(xiàn),因?yàn)橛邢颦h(huán)的檢測是排序的前提。例如,在一個(gè)任務(wù)調(diào)度應(yīng)用中,無論計(jì)劃如何安排,其背后的有向圖中包含的環(huán)意味著存在一個(gè)必須被糾正的嚴(yán)重錯(cuò)誤。因此,解決任務(wù)調(diào)度類應(yīng)用通常需要一下3步:
- 1.指明任務(wù)和優(yōu)先級(jí)條件;
- 2.不斷檢測并去除有向圖中的所有環(huán),以確保存在可行方案;
- 3.使用拓?fù)渑判蚪鉀Q調(diào)度問題。
類似地,調(diào)度方案的任何變動(dòng)之后都需要再次檢查是否存在環(huán),然后再計(jì)算新的調(diào)度安排。
5.有向圖中的強(qiáng)連通性
如果兩個(gè)頂點(diǎn) v 和 w 是相互可達(dá)的,則稱它們?yōu)閺?qiáng)連通的。也就是說,即存在一條從 v 到 w 的有向路徑,也存在一條從 w 到 v 的有向路徑。如果一幅有向圖中的任意兩個(gè)頂點(diǎn)都是強(qiáng)連通的,則稱這副有向圖也是強(qiáng)連通的。
下面是強(qiáng)連通圖的例子,可以看到,環(huán)在強(qiáng)連通性的理解上起著重要的作用。
強(qiáng)連通分量
和無向圖中的連通性一樣,有向圖中的強(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è)有向無環(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í)別的算法來計(jì)算強(qiáng)連通分量并不困難,單對(duì)于處理實(shí)際應(yīng)用中的大型圖來說,平方級(jí)別的時(shí)間和空間需求是不可接受的。
Kosaraju算法
在有向圖中如何高效地計(jì)算強(qiáng)連通分量?我們只需修改無向圖連通分量的算法 CC,KosarajuCC 算法如下,它將會(huì)完成一下任務(wù):
1.在給定的一幅有向圖 G 中,使用 DepthFirstOrder 來計(jì)算它的反向圖 GR 的逆后序排列;
2.在 G 中進(jìn)行標(biāo)準(zhǔn)的深度優(yōu)先搜索,但是要按照剛才計(jì)算得到的順序而非標(biāo)準(zhǔn)的順序來訪問所有未被標(biāo)記的頂點(diǎn);
3.在構(gòu)造函數(shù)中,所有在同一個(gè)遞歸 Dfs() 調(diào)用中被訪問到的頂點(diǎn)都在同一個(gè)強(qiáng)連通分量中,將它們按照和 CC 相同的方式識(shí)別出來。
namespace Digraphs { public class KosarajuCC { private bool[] marked;//已訪問的頂點(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á)性
在無向圖中如果兩個(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ì)于無向圖,等價(jià)于連通性問題;對(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 條。一般來說,一幅有向圖的傳遞閉包中所含的邊都比原圖中多得多。例如,含有 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)先搜索來實(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); } } }
該算法無論對(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)特殊字符快速轉(zhuǎn)碼
這篇文章主要介紹了C# 實(shí)現(xiàn)特殊字符快速轉(zhuǎn)碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-01-01C#獲取DataTable對(duì)象狀態(tài)DataRowState
這篇文章介紹了C#獲取DataTable對(duì)象狀態(tài)DataRowState的方法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-02-02