文章目錄
- 1.說明
- 2.定義
- 3.能力
- 4.圖的類型
- 5.構建圖實例
- 6.`可變`和`不可變`圖
- 7.圖元素(節點和邊)
- 8.庫的契約與行爲
- 9.實現注意事項
- 10.代碼示例
- 10.1`node`是否在圖中?
- 10.2節點`u`和`v`之間是否存在邊(已知存在於圖中)?
- 10.3基本`Graph`示例
- 10.4基本[`ValueGraph`](http://google.github.io/guava/releases/snapshot/api/docs/com/google/common/graph/ValueGraph.html)示例
- 10.5基本[`Network`](http://google.github.io/guava/releases/snapshot/api/docs/com/google/common/graph/Network.html)示例
- 10.6節點遍歷無向圖
- 10.7邊遍歷有向圖
- 11.常見問題
- 11.1Guava爲什麼引入`common.graph`?
- 11.2`common.graph`支持哪些圖形?
- 11.3`common.graph`沒有功能/算法X,可以添加嗎?
- 11.4它是否支持非常大的圖形(即MapReduce規模)?
- 11.5如何定義`successors(node)`的順序?
- 11.6爲什麼要用它而不用別的東西?
- 12.主要貢獻者
Graphs:用於對圖結構數據(即實體及其之間的關係)進行建模的庫。主要功能包括:
Graph:圖的邊是匿名實體,沒有身份或信息。
ValueGraph:其邊具有關聯的非唯一值的圖。
Network:邊是唯一對象的圖形。
支持可變和不可變,有向和無向的圖,以及其它一些屬性。
1.說明
Guava的common.graph
是一個用於對圖結構數據(即實體及其之間的關係)進行建模的庫。示例包括網頁和超鏈接;科學家及其撰寫的論文;機場及其之間的路線;以及人們及其家庭紐帶(家譜)。其目的是提供一種通用且可擴展的語言來處理此類數據。
2.定義
圖由一組節點(也稱爲頂點)和一組邊(也稱爲連接或弧)組成;每個邊將節點彼此連接。與邊關聯的節點稱爲其端點。
(當我們在下面介紹一個稱爲Graph
的接口時,我們將使用"graph"(小寫字母"g")作爲通用術語來指代這種類型的數據結構。當我們要在該庫中引用特定類型時,我們會大寫它。)
如果一條邊具有已定義的起點(其源)和終點(其目標,也稱爲目的地),那麼它是有向邊。否則,它是無向邊。有向邊適用於非對稱關係的建模(“派生自”,“鏈接至”,“由…撰寫”),而無向邊適用於對稱關係的建模(“與…共同撰寫論文”,“之間的距離”,“同級”)。
如果圖的每個邊都是有向的,那麼它是有向圖;如果圖的每個邊都是無向的,那麼它是無向圖。(common.graph
不支持同時具有有向邊和無向邊的圖。)
給出以下示例:
graph.addEdge(nodeU, nodeV, edgeUV);
nodeU
和nodeV
彼此相鄰edgeUV
與nodeU
和nodeV
是關聯的(反之亦然)
如果是有向圖,則:
nodeU
是nodeV
的前驅nodeV
是nodeU
的後繼edgeUV
是nodeU
的輸出邊(或外邊)edgeUV
是nodeV
的輸入邊(或內邊)nodeU
是edgeUV
的來源nodeV
是edgeUV
的目標
如果是無向圖,則:
nodeU
是nodeV
的前驅和後繼nodeV
是nodeU
的前驅和後繼edgeUV
既是nodeU
的輸入邊,也是輸出邊edgeUV
既是nodeV
的輸入邊,也是輸出邊
所有這些關係都是關於圖的。
自環是將一個節點連接到自身的一條邊;等效地,它是一條端點爲相同節點的邊。如果自環是有向的,則它既是其關聯節點的輸出邊,又是其關聯節點的輸入邊,並且其關聯節點既是自環邊的來源又是目標。
如果兩條邊以相同順序(如果有)連接相同的節點,則它們是平行的;如果它們以相反的順序連接相同的節點,則它們是反平行的。 (無向邊不能是反平行的。)
給出以下示例:
directedGraph.addEdge(nodeU, nodeV, edgeUV_a);
directedGraph.addEdge(nodeU, nodeV, edgeUV_b);
directedGraph.addEdge(nodeV, nodeU, edgeVU);
undirectedGraph.addEdge(nodeU, nodeV, edgeUV_a);
undirectedGraph.addEdge(nodeU, nodeV, edgeUV_b);
undirectedGraph.addEdge(nodeV, nodeU, edgeVU);
在directedGraph
中,edgeUV_a
和edgeUV_b
相互平行,並且每個都與edgeVU
反平行。
在undirectedGraph
中,edgeUV_a
,edgeUV_b
和edgeVU
中的每一個與其它兩個都相互平行。
3.能力
common.graph
專注於提供接口和類來支持使用圖。它不提供諸如I/O或可視化支持等功能,並且工具的選擇非常有限。有關此主題的更多信息,請參見FAQ 。
總體而言,common.graph
支持以下種類的圖:
- 有向圖
- 無向圖
- 帶有相關值(權重、標籤等)的節點
和/或
邊 允許/不允許
自環的圖允許/不允許
平行邊的圖(具有平行邊的圖有時稱爲多圖)節點/邊
按插入順序、排序或無序的圖
特定common.graph
類型支持的圖的類型在其Javadoc中指定。Javadoc在其關聯的Builder
類型中指定了每種圖類型的內置實現所支持的圖的類型。該庫中類型的特定實現(尤其是第三方實現)不需要支持所有這些變體,並且還可以支持其它變體。
該庫與底層數據結構的選擇無關:可以將關係存儲爲矩陣、鄰接表、鄰接圖等,具體取決於實現者要針對哪些用例進行優化。
common.graph
(目前)不包括對以下圖變體的顯式支持,儘管可以使用現有類型對其進行建模:
- 樹木,森林
- 具有相同類型(節點或邊)的元素但具有不同類型的圖(例如:二部圖/k部圖,多模態圖)
- 超圖
common.graph
不允許同時具有有向邊和無向邊的圖。
Graphs
類提供了一些基本工具(例如,複製和比較圖)。
4.圖的類型
有三個頂級的圖接口,它們以邊的表示形式來區分:Graph
,ValueGraph
和Network
。這些是同級類型,即沒有一個是其它任何類型的子類型。
這些"頂級"接口均擴展了SuccessorsFunction
和PredecessorsFunction
。這些接口被用作圖形算法的參數類型(例如廣度優先遍歷),該算法僅需要訪問圖中節點的後繼/前驅的一種方式。圖所有者已經有了一個對其有效的表示,並且不特別希望將其表示序列化爲common.graph
類型以僅運行一種圖形算法,在這樣的情況下特別有用。
4.1Graph
Graph
是最簡單,最基礎的圖類型。它定義了用於處理節點到節點關係的低級運算符,例如successors(node)
、adjacentNodes(node)
和inDegree(node)
(後繼節點、鄰接節點和入度)。它的節點是最優的唯一對象。你可以認爲它們類似於將Map
鍵映射到Graph
內部數據結構中。
Graph
圖的邊是完全匿名的;它們只根據其端點進行定義。
用例示例:Graph<Airport>
,其邊連接着可以乘坐直達航班的機場。
4.2ValueGraph
ValueGraph
具有Graph
所具有的所有與節點有關的方法,但是添加了兩個方法來檢索指定邊的值。
ValueGraph
的每個邊都有一個用戶指定的關聯值。這些值不必是唯一的(就像節點一樣)。ValueGraph
和Graph
之間的關係類似於Map
和Set
之間的關係;Graph
的邊是一組端點對,而ValueGraph
的邊是從端點對到值的映射。
ValueGraph
提供了一個asGraph()
方法,該方法返回ValueGraph
的Graph
視圖。這允許在Graph
實例上操作的方法也可用於ValueGraph
實例。
用例示例:ValueGraph<Airport, Integer>
,其邊值表示該邊連接的兩個機場之間旅行所需的時間。
4.3Network
Network
具有Graph
所具有的所有與節點有關的方法,但是添加了處理邊和節點到邊關係的方法,例如outEdges(node)
、incidentNodes(edge)
和edgesConnecting(nodeU, nodeV)
。
Network
的邊是最優的(唯一)對象,就像所有圖類型中的節點一樣。邊的唯一性約束使Network
可以原生支持平行邊,以及與邊和節點到邊關係相關的方法。
Network
提供了一個asGraph()
方法,該方法返回Network
的Graph
視圖。這允許在Graph
實例上操作的方法也可用於Network
實例。
用例示例:Network<Airport, Flight>
,其中的邊表示從一個機場到另一個機場可以乘坐的特定航班。
4.4選擇正確的圖類型
這三種圖形類型之間的本質區別在於它們對邊的表示形式。
Graph
的邊是節點之間的匿名連接,沒有自己的標識或屬性。如果每對節點最多由一條邊連接,並且不需要將任何信息與邊相關聯,則應使用Graph
。
ValueGraph
的邊具有的值(例如邊的權重或標籤)可能是唯一的,也可能不是唯一的。如果每對節點最多由一條邊連接,並且需要將信息與對於不同邊可能相同的邊相關聯(例如,邊的權重),則應使用ValueGraph
。
Network
的邊就像節點一樣,是最優的唯一對象。如果邊對象是唯一的,並且你希望能夠發出引用它們的查詢,則應使用Network
。(請注意,這種唯一性允許Network
支持平行邊。)
5.構建圖實例
根據設計,common.graph
提供的實現類不是公共的。這減少了用戶需要了解的公共類型的數量,並使得導航內置實現所提供的各種功能更加容易,而不會使只想創建圖的用戶不知所措。
要創建圖類型的內置實現之一的實例,請使用相應的Builder類
:GraphBuilder
、ValueGraphBuilder
或NetworkBuilder
。例子:
// Creating mutable graphs
MutableGraph<Integer> graph = GraphBuilder.undirected().build();
MutableValueGraph<City, Distance> roads = ValueGraphBuilder.directed()
.incidentEdgeOrder(ElementOrder.stable())
.build();
MutableNetwork<Webpage, Link> webSnapshot = NetworkBuilder.directed()
.allowsParallelEdges(true)
.nodeOrder(ElementOrder.natural())
.expectedNodeCount(100000)
.expectedEdgeCount(1000000)
.build();
// Creating an immutable graph
ImmutableGraph<Country> countryAdjacencyGraph =
GraphBuilder.undirected()
.<Country>immutable()
.putEdge(FRANCE, GERMANY)
.putEdge(FRANCE, BELGIUM)
.putEdge(GERMANY, BELGIUM)
.addNode(ICELAND)
.build();
-
你可以通過以下兩種方式之一獲取圖生成器的實例:
- 調用靜態方法
directed()
或undirected()
。Builder
提供的每個Graph
實例將是有向的或無向的。 - 調用靜態方法
from()
,該方法將爲你提供基於現有圖實例的Builder
。
- 調用靜態方法
-
創建
Builder
實例後,可以選擇指定其它特徵和功能。 -
構建可變圖
- 你可以在同一個
Builder
實例上多次調用build()
,以構建具有相同的配置多個圖實例。 - 你無需在
Builder
上指定元素類型;在圖類型本身上指定它們就足夠了。 build()
方法返回關聯圖類型的一個可變
子類型,該子類型提供了變體方法;在下面的"可變
和不可變
圖"中對此有更多的內容。
- 你可以在同一個
-
構建不可變圖
- 你可以在同一個
Builder
實例上多次調用immmutable()
來創建具有相同配置的多個ImmutableGraph.Builder
實例。 - 你需要在
不可變
的調用中指定元素類型。
- 你可以在同一個
5.1構建器約束與優化提示
Builder
類型通常提供兩種類型的選項:約束和優化提示。
約束指定由給定的Builder
實例創建的圖必須滿足的行爲和屬性,例如:
- 此圖是否有向
- 此圖是否允許自環
- 此圖的邊是否已排序
等等。
實現類可以選擇使用優化提示來提高效率,例如,確定內部數據結構的類型或初始大小。不保證它們會產生任何效果。
每種圖類型都提供與其構建器指定的約束相對應的訪問器,但不提供優化提示的訪問器。
6.可變
和不可變
圖
6.1Mutable*
類型
每種圖類型都有一個對應的Mutable*
子類型:MutableGraph
、MutableValueGraph
和MutableNetwork
。這些子類型定義了變體方法:
-
添加和刪除節點的方法:
addNode(node)
和removeNode(node)
-
添加和刪除邊的方法:
-
putEdge(nodeU, nodeV)
removeEdge(nodeU, nodeV)
-
putEdgeValue(nodeU, nodeV, value)
removeEdge(nodeU, nodeV)
-
addEdge(nodeU, nodeV, edge)
removeEdge(edge)
-
這與Java集合框架以及Guava的新集合類型的工作方式不同;這些類型的每一個都包含(可選)變體方法的簽名。我們選擇將可變方法分爲子類型,部分原因是鼓勵防禦性編程:通常來說,如果你的代碼僅檢查或遍歷圖而不對其進行修改,則應將其輸入指定爲Graph
、ValueGraph
或Network
,而不是其可變子類型。另一方面,如果你的代碼確實需要修改對象,則代碼必須通過使用將自身標記爲“可變”的類型來引起對這一事實的注意,這對你很有幫助。
由於Graph
等是接口,即使它們不包含變體方法,向調用者提供此接口的實例也不能保證調用者不會對其進行改變,例如(如果它實際上是Mutable*
子類型),調用者可以將其轉換爲該子類型。如果要提供契約保證,作爲方法參數或返回值的圖不能被修改,則應使用Immutable
實現;下面將對此進行詳細介紹。
6.2Immutable*
實現
每個圖類型還具有相應的不可變實現。這些類類似於Guava的ImmutableSet
、ImmutableList
、ImmutableMap
等:一旦構建,便無法對其進行修改,並且它們在內部使用有效的不可變數據結構。
但是,與其它Guava不可變類型不同,這些實現沒有任何用於變體方法的方法簽名,因此它們無需爲嘗試的變體拋出UnsupportedOperationException
。
你可以通過以下兩種方式之一創建ImmutableGraph
等的實例:
使用GraphBuilder
:
ImmutableGraph<Country> immutableGraph1 =
GraphBuilder.undirected()
.<Country>immutable()
.putEdge(FRANCE, GERMANY)
.putEdge(FRANCE, BELGIUM)
.putEdge(GERMANY, BELGIUM)
.addNode(ICELAND)
.build();
使用ImmutableGraph.copyOf()
:
ImmutableGraph<Integer> immutableGraph2 = ImmutableGraph.copyOf(otherGraph);
不可變的圖總是保證提供穩定的關聯邊順序。如果使用GraphBuilder
填充圖,則關聯邊順序將盡可能是插入順序(有關更多信息,請參見ElementOrder.stable()
)。使用copyOf
時,關聯邊順序將是在複製過程中訪問邊的順序。
6.2.1保證
每種Immutable*
類型均提供以下保證:
- 淺層不變性:永遠不能添加,刪除或替換元素(這些類未實現
Mutable*
接口) - 確定性迭代:迭代順序總是與輸入圖的順序相同
- 線程安全:從多個線程併發訪問此圖是安全的
- 完整性:此類型不能在此包之外進行子類化(這會違反這些保證)
6.2.2將這些類視爲"接口",而不是實現
每個Immutable*
類都是提供有意義的行爲保證的類型——而不僅僅是特定的實現。你應該在每個重要的意義上將它們視爲接口。
存儲Immutable*
實例(例如ImmutableGraph
)的字段和方法返回值應聲明爲Immutable*
類型,而不是相應的接口類型(例如Graph
)。這會向調用者傳達上面列出的所有語義保證,這幾乎總是非常有用的信息。
另一方面,ImmutableGraph
的參數類型通常對調用者不利。而是接受Graph
。
警告:如其它地方所述,當元素包含在集合中的時候修改它(以影響其equals()
行爲的方式)幾乎總是一個壞主意。將導致未定義的行爲和錯誤。最好完全避免將可變對象用作Immutable*
實例的元素,因爲用戶可能希望你的"不可變"對象是高度不可變的。
7.圖元素(節點和邊)
7.1元素必須可作爲Map
鍵使用
用戶提供的圖元素應被視爲由圖實現維護的內部數據結構的鍵。因此,用於表示圖元素的類必須具有equals()
和hashCode()
實現,這些實現具有或歸納以下所列的屬性。
7.1.1Uniqueness
如果A
和B
滿足A.equals(B) == true
,則兩者中的最多一個可能是圖的元素。
7.1.2hashCode()
和equals()
之間的一致性
hashCode()
必須與Object.hashCode()
定義的equals()
一致。
7.1.3equals()
的排序一致性
如果節點已排序(例如,通過GraphBuilder.orderNodes()
),則排序必須與Comparator
和Comparable
定義的equals()
一致。
7.1.4非遞歸性
hashCode
和equals()
不能遞歸引用其它元素,如本例所示:
// DON'T use a class like this as a graph element (or Map key/Set element)
public final class Node<T> {
T value;
Set<Node<T>> successors;
public boolean equals(Object o) {
Node<T> other = (Node<T>) o;
return Objects.equals(value, other.value)
&& Objects.equals(successors, other.successors);
}
public int hashCode() {
return Objects.hash(value, successors);
}
}
使用此類作爲common.graph
元素類型(例如Graph<Node<T>>
)有以下問題:
- 冗餘:
common.graph
庫提供的Graph
實現已經存儲了這些關係 - 低效率:添加/訪問此類元素會調用
equals()
(可能還會調用hashCode()
),這需要O(n)
時間 - 不可行性:如果圖中存在循環,
equals()
和hashCode()
可能永遠不會終止
相反,只需將T
值本身用作節點類型(假設T
值本身是有效的Map
鍵)。
7.2元素和可變狀態
如果圖元素有可變狀態:
- 一定不能在
equals()/hashCode()
方法中反映可變狀態(有關詳細信息,請參見Map
文檔) - 不要構造彼此相等的多個元素,並期望它們可以互換。特別是,在將此類元素添加到圖中時,如果在創建過程中需要多次引用這些元素(而不是將
new MyMutableNode(id)
傳遞給每個add*()
調用),則應創建一次並存儲引用。
如果你需要存儲每個元素的可變狀態,則一種選擇是使用不可變元素並將可變狀態存儲在單獨的數據結構中(例如元素到狀態的映射)。
7.3元素必須爲非空
按照要求將元素添加到圖的方法必須拒絕空(null)元素。
8.庫的契約與行爲
本節討論common.graph
類型的內置實現的行爲。
8.1變體
你可以添加之前未將關聯節點添加到圖中的邊。如果尚不存在,則將它們靜默添加到圖中:
Graph<Integer> graph = GraphBuilder.directed().build(); // graph is empty
graph.putEdge(1, 2); // this adds 1 and 2 as nodes of this graph, and puts
// an edge between them
if (graph.nodes().contains(1)) { // evaluates to "true"
...
}
8.2圖equals()
和圖等價
從Guava 22開始,common.graph
的圖類型每個都以對特定類型有意義的方式定義equals()
:
Graph.equals()
將兩個Graph
定義爲相等,如果它們具有相同的節點和邊集(即,兩個圖中的每個邊都具有相同的端點和相同的方向)。ValueGraph.equals()
將兩個ValueGraph
定義爲相等,如果它們具有相同的節點和邊集,並且相等的邊具有相等的值。Network.equals()
將兩個Network
定義爲相等,如果它們具有相同節點和邊集,並且每個邊對象都沿相同方向(如果有)連接相同節點。
另外,對於每種圖類型,只有兩個圖的邊具有相同的有向性(兩個圖都是有向的,或者兩個圖都是無向的)時,兩個圖才能相等。
當然,hashCode()
的定義與每個圖類型的equals()
一致。
如果僅基於連接性比較兩個Network
或兩個ValueGraph
,或者要將Network
或ValueGraph
與Graph
進行比較,則可以使用Network
和ValueGraph
提供的Graph
視圖:
Graph<Integer> graph1, graph2;
ValueGraph<Integer, Double> valueGraph1, valueGraph2;
Network<Integer, MyEdge> network1, network2;
// compare based on nodes and node relationships only
if (graph1.equals(graph2)) { ... }
if (valueGraph1.asGraph().equals(valueGraph2.asGraph())) { ... }
if (network1.asGraph().equals(graph1.asGraph())) { ... }
// compare based on nodes, node relationships, and edge values
if (valueGraph1.equals(valueGraph2)) { ... }
// compare based on nodes, node relationships, and edge identities
if (network1.equals(network2)) { ... }
8.3訪問器方法
返回集合的訪問器:
- 可能返回圖的視圖;不支持對影響視圖的圖修改(例如,在遍歷
nodes()
時調用addNode(n)
或removeNode(n)
),並且可能會導致拋出ConcurrentModificationException
。 - 如果輸入有效但沒有元素滿足請求,則將返回空集合(例如:如果
node
沒有相鄰節點,則adjacentNodes(node)
將返回空集合)。
如果傳遞的元素不在圖中,則訪問器將拋出IllegalArgumentException
。
從Guava 22開始,雖然一些Java集合框架方法(例如contains()
)使用Object
對象參數而不是適當的泛型類型說明符,但common.graph
方法採用了泛型類型說明符以提高類型安全性。
8.4同步
由每個圖的實現來決定自己的同步策略。默認情況下,未定義的行爲可能是由於調用另一個線程正在修改的圖上的任何方法而導致的。
一般來說,內置的可變實現不提供同步保證,但是Immutable*
類(由於具有不可變性)是線程安全的。
8.5元素對象
你添加到圖中的節點、邊和值對象與內置實現無關;它們只是用作內部數據結構的鍵。這意味着節點/邊可以在圖實例之間共享。
默認情況下,節點和邊對象是按插入順序排列的(即,與LinkedHashSet
一樣,node()
和edge()
的Iterator
迭代器會按將它們添加到圖中的順序訪問)。
9.實現注意事項
9.1存儲模式
common.graph
支持多種存儲拓撲圖的機制,包括:
- 圖實現存儲拓撲(例如,通過存儲將節點映射到其相鄰節點的
Map<N, Set<N>>
);這意味着節點只是鍵,並且可以在圖之間共享 - 節點存儲拓撲(例如,通過存儲相鄰節點的
List<E>
);這(通常)意味着節點是特定於圖的 - 單獨的數據存儲庫(例如數據庫)存儲拓撲
注意:Multimap
對於支持隔離節點(無關聯邊的節點)的Graph
實現,不足以作爲內部數據結構,這是因爲它們限制了鍵至少映射到一個值或不存在於Multimap
中。
9.2訪問行爲
對於返回集合的訪問器,有幾種語義選項,包括:
1.集合是不可變的副本(例如ImmutableSet
):嘗試以任何方式修改集合都會拋出異常,並且對圖的修改不會反映在集合中。
2.集合是不可修改的視圖(例如Collections.unmodifiableSet()
):嘗試以任何方式修改集合都會拋出異常,並且對圖的修改會反映在集合中。
3.集合是可變的副本:可以對其進行修改,但是對集合的修改不會反映在圖中,反之亦然。
4.集合是可修改的視圖:可以對其進行修改,對集合的修改會反映在圖中,反之亦然。
(從理論上講,可以返回通過一個方向寫入但不通過另一個方向寫入的集合(集合到圖,反之亦然),但這基本上永遠不會有用或不明確,所以請不要這樣做。😃)
(1)和(2)通常是首選的;在撰寫本文時,內置實現通常使用(2)。
(3)是一個可行的選項,但如果用戶期望修改會影響圖,或者對圖的修改會反映在集合中,則可能會使他們感到困惑。
(4)是一種危險的設計選擇,應非常謹慎地使用,因爲保持內部數據結構的一致性可能很棘手。
9.3Abstract*
類
每種圖類型都有一個對應的Abstract
類:AbstractGraph
等。
圖接口的實現者應儘可能擴展適當的抽象類,而不是直接實現該接口。抽象類提供了幾種關鍵方法的實現,這些方法很難正確執行,或者有助於實現一致性,例如:
*degree()
toString()
Graph.edges()
Network.asGraph()
10.代碼示例
10.1node
是否在圖中?
graph.nodes().contains(node);
10.2節點u
和v
之間是否存在邊(已知存在於圖中)?
在圖是無向的情況下,以下示例中的參數u
和v
的順序無關緊要。
// This is the preferred syntax since 23.0 for all graph types.
graphs.hasEdgeConnecting(u, v);
// These are equivalent (to each other and to the above expression).
graph.successors(u).contains(v);
graph.predecessors(v).contains(u);
// This is equivalent to the expressions above if the graph is undirected.
graph.adjacentNodes(u).contains(v);
// This works only for Networks.
!network.edgesConnecting(u, v).isEmpty();
// This works only if "network" has at most a single edge connecting u to v.
network.edgeConnecting(u, v).isPresent(); // Java 8 only
network.edgeConnectingOrNull(u, v) != null;
// These work only for ValueGraphs.
valueGraph.edgeValue(u, v).isPresent(); // Java 8 only
valueGraph.edgeValueOrDefault(u, v, null) != null;
10.3基本Graph
示例
ImmutableGraph<Integer> graph =
GraphBuilder.directed()
.<Integer>immutable()
.addNode(1)
.putEdge(2, 3) // also adds nodes 2 and 3 if not already present
.putEdge(2, 3) // no effect; Graph does not support parallel edges
.build();
Set<Integer> successorsOfTwo = graph.successors(2); // returns {3}
10.4基本ValueGraph
示例
MutableValueGraph<Integer, Double> weightedGraph = ValueGraphBuilder.directed().build();
weightedGraph.addNode(1);
weightedGraph.putEdgeValue(2, 3, 1.5); // also adds nodes 2 and 3 if not already present
weightedGraph.putEdgeValue(3, 5, 1.5); // edge values (like Map values) need not be unique
...
weightedGraph.putEdgeValue(2, 3, 2.0); // updates the value for (2,3) to 2.0
10.5基本Network
示例
MutableNetwork<Integer, String> network = NetworkBuilder.directed().build();
network.addNode(1);
network.addEdge("2->3", 2, 3); // also adds nodes 2 and 3 if not already present
Set<Integer> successorsOfTwo = network.successors(2); // returns {3}
Set<String> outEdgesOfTwo = network.outEdges(2); // returns {"2->3"}
network.addEdge("2->3 too", 2, 3); // throws; Network disallows parallel edges
// by default
network.addEdge("2->3", 2, 3); // no effect; this edge is already present
// and connecting these nodes in this order
Set<String> inEdgesOfFour = network.inEdges(4); // throws; node not in graph
10.6節點遍歷無向圖
// Return all nodes reachable by traversing 2 edges starting from "node"
// (ignoring edge direction and edge weights, if any, and not including "node").
Set<N> getTwoHopNeighbors(Graph<N> graph, N node) {
Set<N> twoHopNeighbors = new HashSet<>();
for (N neighbor : graph.adjacentNodes(node)) {
twoHopNeighbors.addAll(graph.adjacentNodes(neighbor));
}
twoHopNeighbors.remove(node);
return twoHopNeighbors;
}
10.7邊遍歷有向圖
// Update the shortest-path weighted distances of the successors to "node"
// in a directed Network (inner loop of Dijkstra's algorithm)
// given a known distance for {@code node} stored in a {@code Map<N, Double>},
// and a {@code Function<E, Double>} for retrieving a weight for an edge.
void updateDistancesFrom(Network<N, E> network, N node) {
double nodeDistance = distances.get(node);
for (E outEdge : network.outEdges(node)) {
N target = network.target(outEdge);
double targetDistance = nodeDistance + edgeWeights.apply(outEdge);
if (targetDistance < distances.getOrDefault(target, Double.MAX_VALUE)) {
distances.put(target, targetDistance);
}
}
}
11.常見問題
11.1Guava爲什麼引入common.graph
?
Guava所做的許多其它事情的同樣的論證也適用於圖:
- 代碼重用/互通性/範例統一:很多事情與圖處理有關
- 效率:有多少代碼正在使用低效的圖表示形式?太多的空間(例如矩陣表示形式)?
- 正確性:圖分析中有多少代碼是錯誤的?
- 提倡使用圖作爲ADT的使用:如果簡單的話,有多少人會使用圖?
- 簡單:如果明確使用該隱喻,處理圖的代碼將更易於理解。
11.2common.graph
支持哪些圖形?
在上面的"能力"部分對此進行了回答。
11.3common.graph
沒有功能/算法X,可以添加嗎?
也許。你可以通過[email protected]向我們發送電子郵件,或在GitHub上發佈問題。
我們的理念是,只有在(a)符合Guava的核心使命並且(b)有充分理由期望它會被合理地廣泛使用的情況下,某些東西才應成爲Guava的一部分。
common.graph
可能永遠不會具有可視化和I/O之類的功能;這些都是屬於他們自己的項目,不符合Guava的使命。
遍歷、過濾或轉換等功能更適合,因此更可能包含這些功能,儘管最終我們希望其它圖形庫將提供大多數功能。
11.4它是否支持非常大的圖形(即MapReduce規模)?
目前不行。數百萬個節點中的圖應該是可行的,但是你應該將該庫看作類似於Java集合框架類型(Map
、List
、Set
等)。
11.5如何定義successors(node)
的順序?
在圖生成器中將incidentEdgeOrder()
設置爲ElementOrder.stable()
,可以確保successors(node)
按照插入邊的順序返回node
節點的後繼節點。對於與節點的關聯邊有關的大多數其它方法,也是如此,例如(諸如incidentEdges(node)
)。
11.6爲什麼要用它而不用別的東西?
tl;dr:你應該使用所有適合你的方法,但是如果該庫不支持你需要的內容,請告訴我們!
該庫(針對Java)的主要競爭對手是:JUNG和JGraphT。
JUNG
由Joshua O’Madadhain(common.graph
主管)於2003年共同創建,他仍然擁有它。JUNG
相當成熟,功能齊全,被廣泛使用,但是卻有很多不足和低效之處。現在,common.graph
已經對外發布,他正在研究使用common.graph
作爲其數據模型的JUNG
的新版本。
JGraphT
是另一個第三方Java圖庫,已經存在了一段時間。我們不太熟悉它,因此我們無法對其進行詳細評論,但它至少與JUNG
有一些共同之處。該庫還包括一些適配器類,以將common.graph
圖適配爲JGraphT
圖。
如果你有非常具體的要求,則推出自己的解決方案有時是正確的答案。但是,就像你通常不會在Java中實現自己的哈希表(而是使用HashMap
或ImmutableMap
)一樣,出於上述所有原因,你也應該考慮使用common.graph
(或在必要時使用另一個現有的圖庫)。
12.主要貢獻者
common.graph
是團隊合作的成果,我們得到了Google內部和外部許多人的幫助,但這些人的影響最大。
- Omar Darwish做了很多早期的實現,併爲測試覆蓋率設定了標準。
- James Sexton是該項目唯一最多產的貢獻者,並且對項目的方向和設計產生了重大影響。他負責一些關鍵功能以及我們提供的實現的效率。
- Joshua O’Madadhain在反思了
JUNG
的長處和短處之後,開始了common.graph
項目,他也幫助創建了這個項目。他領導該項目,並且幾乎審查或編寫了設計和代碼的各個方面。 - Jens Nyman貢獻了許多最新的功能,例如
Traverser
和不可變的圖生成器。他對項目的未來方向也有重大影響。