背景介紹
由於Graph模塊直到最近幾個版本才加入到Guava中,網上對應的中文教程也幾乎是缺失的,因此想借機翻譯下它對應wiki的文檔,以此作爲入門示例。文章肯定有一些翻譯不恰當的地方,會在一步一步的瞭解其理念後,漸進的更新註釋。
對應的原文文檔爲:https://github.com/google/guava/wiki/GraphsExplained
爲了便於理解,先拋出它的架構類圖,在心目中有個大概的輪廓:
圖論的解釋
Guava
庫的目錄common.graph
包含的模塊是一個描述實體(entity
)以及實體之間的關係的圖數據結構模型庫。例如:網頁與超鏈接、科學家與他們寫的論文、機場與其航線、人與其家族等。Guava-Graph
模塊的目的是提供一種通用以及可擴展的語言來描述類似上述的舉例。
定義(Definitions)
圖中包含一組節點(node
)(也稱爲頂點)和一組連接節點的邊(edge
)(也稱爲鏈接或者弧);邊緣的節點我們稱爲端點(endpoint
)。(我們將在下文介紹一個叫Graph
的接口,我們將使用“圖”來作爲一般術語描述該數據結構,也可以使用它引用某個具體的圖類型,即接口Graph
有很多具體的實現類)。
如果一條邊定義了開始(source
)和結束(target
),這條邊被城爲有向邊(directed
),否則稱爲無向邊(undirected
)。有向邊適用於非對稱的關係模型(起源、指向、作者),而無向邊適用於對稱關係模型(摺疊、距離、同級關係)。
圖中每一條邊都是有向邊的,則被稱爲有向圖;每一條邊都是無向的,則被稱爲無向圖。(common.graph
模塊不支持圖中既有有向邊又有無向邊的情形。)
舉例:
graph.addEdge(nodeU, nodeV, edgeUV);
-
nodeU
和nodeV
是兩個鄰接點(adjacent
)。 -
edgeUV
是頂點nodeU
到頂點nodeV
的事件(incident
)(反之亦然)
在有向圖中,有如下定義:
-
nodeU
是nodeV
的一個前趨(predecessor
) -
nodeV
是nodeU
的一個後繼(successor
) -
edgeUV
是nodeU
的一條出度(outgoing
)邊 -
edgeUV
是nodeV
的一條入度(incoming
)邊 -
nodeU
是邊edgeUV
的起點(source
) -
nodeV
是邊edgeUV
的終點(target
)
在無向圖中,有如下定義:
-
nodeU
既是nodeV
的前趨也是nodeV
的後繼 -
nodeV
既是nodeU
的前趨也是nodeU
的後繼 -
edgeUV
既是nodeU
的入度也是nodeU
的出度 -
edgeUV
既是nodeV
的入度也是nodeV
的出度
一條連接節點本身的邊被稱爲自環(self-loop),也就是說,一條邊連接了兩個相同的節點。如果這個自環是有向的,那麼這條邊既是節點的入度邊也是節點的出度邊,這個節點既是邊的起點(source
)也是邊的終點(target
)。
如果兩條邊以相同的順序連接相同的節點,則稱這兩條邊爲平行邊(parallel
);如果以相反的順序連接相同的節點則稱這兩條邊爲逆平行邊(antiparallel
)。(無向邊不能被稱爲逆平行邊)
例如:
//有向圖
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
是兩兩相互逆平行邊。
功能(Capabilities)
common.graph
模塊的核心是提供圖相關操作的接口和類。另外,它沒有提供類似I/O
或者可視化的功能。如果選用這個模塊將會有非常多的限制,具體詳細信息可以查看下面FAQ
的相關主題。總體來講,它提供瞭如下幾種類型的圖:
- 有向圖
- 無向圖
- 節點和(或)邊帶權圖
- 允許(不允許)自環圖
- 允許(不允許)平行邊圖(允許平行邊圖有時也稱爲多重圖(
multigraphs
) - 節點或邊被有序插入、順序、無序圖(graphs whose nodes/edges are insertion-ordered, sorted, or unordered)
Javadoc
中有這樣的描述:common.graph
中的各種類型的圖都是通過與其相關的Builder
具體實現類型來構建的,不過這些Builder
實現類型不一定支持上面提到的所有圖類型,但也可能支持其他類型的圖。
庫中圖的數據結構是通過矩陣
、鄰接list
或鄰接map
等方式來存儲的,選擇何種存儲方式取決於適用的實現場景。
對於以下這些變形圖在common.graph
中沒有確切的支持,儘管它們可以通過已有的圖類型進行建模:
common.graph
不允許圖中同時存在有向邊和無向邊。Graphs
中提供了很多基本操作(如:圖的拷貝和比較操作)。
圖的類型
common.graph
模塊中有三種通過邊來作爲區分依據的"top-level
"接口(interface
):Graph
、 ValueGraph
、和Network
。另外還存在一些同級類型,不過這些都不是這三種類型的子類型。
上面三種 "top-level
" 接口都繼承自接口SuccessorsFunction
和PredecessorsFunction
。這樣做是爲了在僅需要訪問節點的後繼(successors
)或者前趨(predecessors
)的圖中,它可以直接被用來作爲圖算法(例如,BFS
廣度優遍歷)中參數的類型。 This is especially useful in cases where the owner of a graph already has a representation that works for them and doesn't particularly want to serialize their representation into a common.graph type just to run one graph algorithm.(不知道怎麼翻)
Graph
Graph
是最簡單也是最基本的圖類型。爲了處理節點與節點之間的關係它定義了一些基本的操作,例如:successors(node)
-->獲取node
的後繼、adjacentNodes(node)
-->獲取node
的鄰接點、inDegree(node)
-->獲取node
的入度等。這些節點在圖中都是唯一的對象,在其內部數據結構中,你可以認爲它們是Map
的鍵值(Key
)。
Graph
中的邊是完全匿名的,他們只能根據端點來定義。舉例:Graph<Airport>
中,其邊連接任意兩個可以直航的機場。
ValueGraph
接口ValueGraph
包含了Graph
中的所有與節點相關的方法,並增加了一些檢索指定邊權值的方法。
ValueGraph
中的每一條邊都有一個與之相關的特定權值,但是這些權值不能保證唯一性。ValueGraph
與Graph
的關係類似與Map
與Set
的關係(Graph
中的邊是以頂點對的形式保存在Set
中,而ValueGraph
的邊是以頂點對與其權值的映射關係保存在Map
中)。
ValueGraph
提供了一個asGraph()
的函數,它可以從ValueGraph
中返回一個Graph
視圖,這樣作用於Graph
實例上的方法也能作用於ValueGraph
的實例上。
舉例:ValueGraph<Airport, Integer>
,其邊表示在能直航的兩個機場之間航班必須花費的時間。
Network
Network
中包含了Graph
中的所有與節點相關的方法,還增加了操作邊以及操作頂點與邊的關係的方法,例如:outEdges(node)
--->獲取node
的出度邊, incidentNodes(edge)
-->獲取邊edge
的頂點對, 和 edgesConnecting(nodeU, nodeV)
-->獲取nodeU
和nodeV
的直連邊。
Network
中每一條邊都是唯一的,就像節點在所有的Graph
類型中是唯一的一樣。邊的唯一性限制使得Network
能夠天然的支持並行邊,以及與邊和節點與邊相關的方法。
Network
類提供了一個asGraph()
的方法,它可以從Network
中返回一個Graph
視圖,這樣作用於Graph
實例上的方法也能操作Network
的實例上。
舉例:Network<Airport, Flight>
,它的每一條邊代表了從一個機場到另一個機場可以乘坐的特定航班(兩個機場之間可以同時有多趟航班)。
如何選擇合適的圖類型
這三種圖類型之間本質的區別在於它們邊表示的不同:
-
Graph
中的邊是節點之間的匿名連接,沒有自己的標識或屬性。如果每一對節點之間都是通過最多一條邊連接的,而且這些邊沒有任何與之相關的信息時,則選用它。 -
ValueGraph
中的邊帶有一個值(例如權值或標籤),且邊在整個圖中不能保證其唯一性。如果每一對節點之間都是通過最多一條邊連接的,並且每一條邊都有與之相關的權值時,則選用它。 -
Network
中邊是全局唯一的,就像節點在圖中是唯一的一樣。如果邊對象需要唯一,並且希望能查詢它們的引用時,則選用它。(請注意,這種唯一性使得Network
支持平行邊。)
構建圖的實例
common.graph
中圖的具體實現類並沒有設計成public
的,這樣主要是爲了減少用戶需要了解的圖類型的數量,使得構建各種功能的圖變得更加容易。
要創建一個內置圖類型的實例,可以使用相應的Builder
類: GraphBuilder
, ValueGraphBuilder
和NetworkBuilder
。例如:
MutableGraph<Integer> graph = GraphBuilder.undirected().build();
MutableValueGraph<City, Distance> roads = ValueGraphBuilder.directed().build();
MutableNetwork<Webpage, Link> webSnapshot = NetworkBuilder.directed()
.allowsParallelEdges(true)
.nodeOrder(ElementOrder.natural())
.expectedNodeCount(100000)
.expectedEdgeCount(1000000)
.build();
- 可以使用下面兩種的任意一種方式通過
Builder
來構建圖實例:
1、調用靜態方法directed()
或者undirected()
來實例化一個有向圖或者無向圖。
2、調用靜態方法from()
基於一個已存在的圖實例構建圖。 - 在創建了
Builder
實例後,還可以選擇指定其他特性和功能。 - 同一個
Builder
實例可以多次調用build()
方法來創建多個圖的實例。 - 不需要在
Builder
上指定節點和邊的類型,只需要在圖類型本身上指定即可。 -
build()
方法返回一個Mutable
子類型的圖時,提供了變形的方法。下面將會介紹更多關於Mutable
和Immutable
的圖。
Builder constraints vs. optimization hints
The Builder types generally provide two types of options: constraints and optimization hints.
Constraints specify behaviors and properties that graphs created by a given Builder instance must satisfy, such as:
- whether the graph is directed
- whether this graph allows self-loops
- whether this graph's edges are sorted
and so forth.
Optimization hints may optionally be used by the implementation class to increase efficiency, for example, to determine the type or initial size of internal data structures. They are not guaranteed to have any effect.
Each graph type provides accessors corresponding to its Builder-specified constraints, but does not provide accessors for optimization hints.
可變(Mutable)和不可變(Immutable)圖
Mutablexx類型
每種圖類型都有與其對應的Mutablexx
子類型: MutableGraph
,MutableValueGraph
,以及 MutableNetwork
。這些子類型定義了下面這些變形方法:
- 增加和刪除節點的方法:
addNode(node)
、removeNode(node)
。 - 增加和刪除邊的方法:
MutableGraph
-->putEdge(nodeU, nodeV)
、removeEdge(nodeU, nodeV)
。
MutableValueGraph
-->putEdgeValue(nodeU, nodeV, value)
、removeEdge(nodeU, nodeV)
。
MutableNetwork
-->addEdge(nodeU, nodeV, edge)
、removeEdge(edge)
。
這些方法的定義與java的集合類型以及guava的新集合類型都有所不同——每種類型都包含變形方法的函數簽名。選擇將變形方法放在子類型中,一部分原因是爲了鼓勵防禦性編程:一般來說,如果你的代碼只是爲了檢查或者遍歷一個圖,而不改變它,那麼輸入應該就指定爲Graph
、ValueGraph
或者Network
類型,而不是它們的可變子類型。另一方面,如果你的代碼確實需要修改一個對象,那麼使用帶Mutable
修飾的子類對你會很有幫助。
由於Grpah
等都是接口,即使它們不包含這些變形方法,但這些接口的實例並不能保證這些方法不被調用(如果它們實際上是Mutablexx的子類型),因爲調用者可能會將其轉換成該子類型。如果有一種契約的保證,即一個方法的參數或返回值不能被修改的Grahp
類型,你可以使用下面介紹的這種不可變(Immutable
)實現。
Immutablexx的實現
每一種圖類型(Graph
、ValueGraph
、 Network
)都有相應的不可變實現類(ImmutableGraph
、ImmutableValueGraph
、ImmutableNetwork
),這些類類似於Guava的ImmutableSet
、ImmutableList
、ImmutableMap
等,一旦被創建出來,它們就不能被修改,且它們在內部使用了高效的不可變數據結構。
不同與Guava
的其他不可變類型,這些不可變實現類壓根沒有提供修改的方法,所以他們不需要拋出UnsupportedOperationException
異常來應對這些操作。
通過調用靜態方法copyOf()
來創建ImmutableGraph
等的實例。例如:
ImmutableGraph<Integer> immutableGraph = ImmutableGraph.copyOf(graph);
每一種Immutable*
類型提供了一下保證:
-
不變性:元素不能被添加、刪除、替換。(這些類沒有實現
Mutable*
的接口) - 迭代的確定性:迭代順序總是和輸入圖的順序相同
- 線程安全:多個線程同時訪問該圖是安全的操作
- 完整性:該類型不能在包之外定義子類型(允許違反這條)
將這些類看作成接口(interface
),而不是實現類:
每一個Immutable*
類都是提供了有意義行爲保證的類型,而不僅僅是特定的實現類。所以你應該把它們當作是有重要意義的接口來看待。
如果字段或返回值是Immutable*
的實例(如ImmutableGraph
),則應將其申明爲Immutable*
類型,而不是對應的接口類型(如Graph
)。This communicates to callers all of the semantic guarantees listed above, which is almost always very useful information.
另一方面,一個ImmutableGraph
類型的參數對調用者來說會覺得比較麻煩,而更傾向與Graph
類型。
警告:正如其他地方指出的,修改一個包含在集合中的元素幾乎總是一個壞注意,這樣會導致一些未定義的行爲和錯誤出現。因此,通常最好避免使用可變對象作爲Immutable*
類型的對象的元素,因爲大多數用戶是期望你的immutable
對象是真的不可修改。
圖的元素(節點和邊)
節點(Network
中的邊)必須可以用做Map
的鍵:
- 必須在圖中唯一:當且僅當
nodeA.equals(nodeB) == false
時,則認爲nodeA
和nodeB
是不相等的。 - 必須合適的實現函數
equals()
和hashCode()
。 - 如果元素是有序的(例如
GraphBuilder.orderNodes()
),則必須和equals()
保持一致(由Comparator
和Comparable
接口定義)
如果圖的元素有可變狀態:
- 不能在
equals()
/hashCode()
方法中反射獲取可變狀態(這些在Map
的相關文檔中有過詳細討論) - 不要創建多個相等的元素,並希望它們可以互換。特別是,如果你需要在創建期間多次引用這些元素,應該在向圖中添加這些元素時一次性的創建並保存其引用(而不是每次都通過
new MyMutableNode(id)
傳給add**()
的中)
如果需要保存可變元素的每一個可變狀態,可以選用不可變元素,並將可變狀態保存在單獨的數據結構中(如:一個元素狀態Map
)。
圖的元素必須不能爲null
!
common.graph的契約和行爲
本節討論常見內置圖的實現方式。
變形
可以往圖中添加一條邊,如果圖中還沒有出現對應的兩個端點時,兩個端點則會靜默添加到圖中:
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"
...
}
圖的equals()和等價
Guava
的22版本中每種圖都定義有特定意義的equals()
:
-
Graph.equals()
定義爲兩個相等的Graph
有相同的節點和邊集(也就是說,兩個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)) { ... }
訪問器方法
訪問器可以返回下面兩種集合:
- 返回圖的視圖。不支持對圖的修改將結果反映到到視圖上(例如,在通過
nodes()
遍歷圖時,又在調用addNode(n)
或者removeNode(n)
),而且可能會拋出ConcurrentModificationException
異常。 - 儘管輸入是有效的,但是當沒有元素滿足請求時,則會返回一個空集合。
如果傳遞的元素不在圖中,訪問器將會拋出IllegalArgumentException
異常。
一些JDK的集合框架中的方法(如contains()
)使用對象來作爲參數,而沒有使用合適的泛型參數;在Guava22中common.graph
模塊中方法一般採用泛型參數來改進類型的安全性。
同步
圖的同步策略取決於每個圖自己的實現。默認情況下,如果在另一個線程中修改了圖,則調用圖的任何方法都可能導致未定義的行爲。
一般來說,內置的實現的可變類型沒有提供同步的保證,但是Immutable*
類型(由於是不可修改的)是線程安全的。
元素對象
添加到圖中的節點、邊或值對象與內置實現無關,它們只是作爲內部數據結構的鍵。這意味這,節點/邊可以在圖實例之間共享。
默認情況下,節點和邊是按照順序來插入的(也就是說,nodes()
和edges()
的Iterator
是按照它們加入到圖中的順序的順序來訪問的,就像在LinkedHashSet
中一樣)。
實現者的筆記
存儲模型
common.graph
支持多種機制來存儲圖的拓撲結構,包括:
- 圖本身自己實現拓撲存儲(例如使用
Map<N, Set<N>>
存儲,將節點映射到鄰接節點上),這意味着節點只是鍵,可以在不同的圖之間共享。 - 節點自己存儲拓撲順序(例如
List<E>
存儲的鄰接節點),這意味着節點獨立與圖。 - 一個單獨的數據存儲庫(如數據庫)存儲拓撲順序。
注意:Multimap
不是一個支持孤立節點(沒有邊的節點)的內部數據結構,由於它們的限制,一個鍵要麼至少映射一個值,要麼不出現在Multimap
中。
訪問行爲
對於返回一個集合的訪問器函數,有下面幾個語義選項:
- 集合是一個不可變拷貝(例如:
ImmutableSet
):試圖以任何方式修改集合的嘗試都將拋出異常,且對圖的修改不會反映到集合中。 - 集合是一個不可修改的視圖(例如:
Collections.unmodifiableSet()
):視圖以任何方式修改集合的嘗試都將拋出異常,且對圖的修改將反映到集合中。 - 集合是一個可變拷貝:它可以被修改,但是對圖的修改不會反映到集合中,反之亦然。
- 集合是一個可變的視圖:它可以被修改,對集合的修改也將反映到圖中,反之亦然。
(理論上,你可以返回一個through writes in one direction but not the other (collection-to-graph or vice-versa)的集合,但是基本上不會有用或者邏輯清晰,所以請不要這樣做)
(1)和(2)一般是首選,在撰寫本文時,內置的實現通常使用(2);(3)是一個可行的選項,但是如果用戶期望修改集合影響圖,或者對圖的修改反映在集合上,可能會讓用戶感到困惑;(4)是一個非常危險的設計選擇,應該非常謹慎的使用,因爲這樣很難保證內部數據結構的一致性。
Abstract*類
每一種圖都有一個相應的Abstract
類:AbstractGraph
等。
如果可能的話,圖的實現應該繼承適當的Abstract
類,而不是直接實現接口。Abstract
類提供了幾個比較難正確執行或者有助於實現一致性實現的方法,例如:
*degree()
toString()
Graph.edges()
Network.asGraph()
代碼例子
判斷節點是否在圖中?
graph.nodes().contains(node);
節點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;
Graph例子
MutableGraph<Integer> graph = GraphBuilder.directed().build();
graph.addNode(1);
graph.putEdge(2, 3); // also adds nodes 2 and 3 if not already present
Set<Integer> successorsOfTwo = graph.successors(2); // returns {3}
graph.putEdge(2, 3); // no effect; Graph does not support parallel edges
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
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
遍歷無向圖
// 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;
}
遍歷有向圖
// 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);
}
}
}
FAQ
爲什麼Guava要介紹common.graph
?
因爲Guava
所做的其他事情,同樣的理論也適用於圖:
- 代碼重用/互操作性的統一範式:很多事情的處理都與圖相關
- 效率:有多少代碼使用了低效的、佔用更多空間的圖結構(例如矩陣表示)?
- 正確性:做圖分析的代碼有多少錯誤?
- 推廣圖的抽象數據類型:如果圖更容易使用,會有多少人會使用圖?
- 簡潔性:code which deals with graphs is easier to understand if it’s explicitly using that metaphor.
common.graph都支持哪些類型的圖?
上面的“功能”章節已經回答這個了。
common.graph沒有 feature/algorithm X,後面會添加嗎?
也許可能。
我們的理念是,如果(a)與Guava的核心使命相適應,那麼就應該有這些東西;(b)有充分的理由期待它會被合理地廣泛使用。
comon.graph
可能永遠都不會有像可視化或者I/O這樣的功能,因爲它們本身就可以成爲一個項目,與Guava的任務不太相符。
像遍歷(traversal)、過濾(filtering)或者轉換(transformation)這樣的功能更加合適,因此將更有可能被包含在庫中,儘管最終我們希望其他的圖庫能提供這些功能。
是否支持非常大的圖(例如MapReduce)?
這一次還不是。在少於百萬個節點的圖應該還是可行的,但你應該把這個庫看成類似Java集合框架類型(Map
、List
、Set
等等)。
爲什麼我要使用它而不是其他的庫?
你應該使用適合你的方法,但是如果這個庫不支持你,請告訴我們你需要什麼。
這個庫的主要競品有:JUNG和JGraphT。
-
JUNG
是Joshua O'Madadhain在2003年開源的(領先與common.graph),直到現在他還在維護它。JUNG
是相當成熟和功能齊全的圖庫,已經被廣泛使用,不過它仍有些晦澀而且低效。現在common.graph
已經發布了,它計劃創建一個新版本的JUNG
。 -
JGraphT
是另一個已經存在一段時間的第三方Java版的圖庫。我們不太熟悉它,所以我們不能詳細評價它,但它至少與JUNG
有一些共同點。
Rolling your own solution is sometimes the right answer if you have very specific requirements. But just as you wouldn’t normally implement your own hash table in Java (instead of using HashMap or ImmutableMap), you should consider using common.graph (or, if necessary, another existing graph library) for all the reasons listed above.
主要貢獻者
common.graph
是一個團隊的努力,我們獲得了來自谷歌內外許多人的幫助,只是下面這些人的影響最大:
- Omar Darwish做了很多早期的實現,併爲覆蓋測試設置了標準。
- James Sexton是該項目貢獻最多的人,他對項目的方向和設計產生了重大的影響;他負責一些關鍵性特性也負責我們所提供這些實現上的有效性。
-
Joshua O'Madadhain在反思
JUNG
的優點和缺點後,在他的幫助下我們啓動了common.graph
項目,他領導了這個項目的開展,並對設計和代碼的各個方面進行了review和編寫。