Graphs, Explained
Guava的 common.graph
是一个用于建模图形结构数据的库,即实体和它们之间的关系。例子包括网页和超链接;科学家和他们写的论文;机场和它们之间的路线;以及人们和他们的家庭关系(家谱)。它的目的是提供一种通用的和可扩展的语言来处理这些数据。
Definitions
图由一组节点(也称为顶点)和一组边(也称为链接或弧)组成;每个边将节点彼此连接。入射到边缘的节点称为其端点。(当我们在下面介绍一个名为Graph的接口时,我们将使用“graph”(小写“g”)作为对这种类型数据结构的通用术语。当我们想在这个库中引用一个特定的类型时,我们把它大写。)
如果有一个定义的开始(源)和结束(它的目标,也称为它的目的地),则指向一个边缘。否则,它是无向的。有向边适合于建模非对称关系(“从属”、“链接”、“授权”),而无向边则适合于对称关系的建模(“合著一篇论文”、“之间的距离”、“兄弟姐妹”)。
如果每个边都是有向的,或如果每个边都是无向的,则是一个图。(common.graph
不支持既有定向也有无向边的图)。
例子:
graph.addEdge(nodeU, nodeV, edgeUV);
nodeU
与nodeV
是相互邻接的edgeUV
是从nodeU
到nodeV
的边 (反之亦然)
如果 graph
是有向图:
nodeU
是nodeV 的前继节点
nodeV
是nodeU 的后继节点
edgeUV
是nodeU 的输出边(出边)
edgeUV
是nodeV 的输入边(入边)
nodeU
是edgeUV 的源节点
nodeV
是edgeUV 的目标节点
如果 graph
是无向图:
nodeU
既是nodeV 的前继节点也是后继节点
nodeV
既是nodeU 的前继节点也是后继节点
edgeUV
既是nodeU 的输入边也是输出边
edgeUV
既是nodeV 的输入边也是输出边
所有的这些关系都是关于 graph
的。
自循环是用边连接自身,等价地,它是端点是同一节点的边。如果自环是有向的,则它既是节点的出射边也是入射边,并且其既是源节点也是目标节点。
如果两个边以相同的顺序(如果有的话)连接相同的节点,则它们平行;如果它们以相反的顺序连接相同的节点,则反平行。(无向边不能反平行)
例如:
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。
总之, common.graph
支持下列类型的图表:
- 有向图
- 无向图
- 有权图
- 自环或无自环图
- 平行边或不平行边图 (具有平行边的图有时称为多重图)
-
节点/边是插入有序的或无序的图
common.graph
支持的特定图在Javadoc中被指定。每个图形类型的内置实现支持的图形类型在Javadoc中为其关联Builder类型指定。 这个库中的类型的特定实现(尤其是第三方实现)不需要支持所有这些变体,还可以支持其他变体。
该库对于底层数据结构的选择是不可知的:根据实现者想要优化的用例,关系可以存储为矩阵、邻接列表、邻接映射等。
common.graph
暂时不包含对以下变体图的显式支持,尽管可以使用现有类型对其进行建模:
- 树,森林
- 具有相同种类(节点或边)具有不同类型的元素的图(例如:二分图/k分图、多峰图)
- 超图
common.graph
不允许图中既有有向边也有无向边。
Graphs
类提供一些基础工具 (例如, 图的复制与比较).
Graph Types
有三个顶级图形接口,它们通过表示边缘来区分:Graph、ValueGraph和Network。这些是兄弟类型,也就是说,没有一个是任何其他类型的子类型。
这些顶级接口继承自 SuccessorsFunction
与 PredecessorsFunction
. 这些接口旨在用作图形算法(例如宽度优先遍历)的参数类型,这些算法只需要访问图中节点的后继/前继。这对于图的所有者已经具有适合他们的表示并且不想为了运行一个图算法而将它们的表示序列化为公共的图类型的情况特别有用。
Graph
Graph
是最简单也是最基础的图的类型. 它定义了用于处理节点到节点关系的低级操作符, 例如 successors(node)
, adjacentNodes(node)
, 与 inDegree(node)
. 它的节点最好是唯一对象; 你可以将它们视为类似于将键映射到图形内部数据结构中的方法。
Graph
的边是完全匿名的,它们仅根据端点来定义。
实例用例: Graph<Airport>
, 它的边连接机场,意味着可以直航。
ValueGraph
ValueGraph拥有Graph所做的所有与节点相关的方法,但是添加了两个方法,用于检索指定边缘的值。
每个ValueGraph
的边都有一个关联的用户指定值。这些值不需要是唯一的 (节点必须是); ValueGraph
与 Graph
之间的关系类似于 Map
与 Set
; 一个 Graph
的边是一组端点对, 而一个 ValueGraph
的边是从端点到值的映射。)
ValueGraph
提供 asGraph()
方法返回一个 Graph
的视图
. 这允许在Graph
实例上操作的方法也能对ValueGraph的实例起作用。
实例用例: ValueGraph<Airport, Integer>
, 其边值表示在两个机场之间的边连接所需的时间。
Network
Network
具有与 Graph
相关的所有与节点相关的方法,但添加了与边和节点到边关系一起工作的方法。例如, outEdges(node)
, incidentNodes(edge)
, 与 edgesConnecting(nodeU, nodeV)
.
Network
的边最好是唯一的对象,就像节点在所有的图形类型中一样。边的唯一性约束允许Network
固有地支持并行边缘,以及关于边和节点到边关系的方法。
Network
提供的 asGraph()
方法能够返回 Graph
类型的视图
. 这允许在 Graph
实例上操作的方法也适用于Network
实例。
实例用例: Network<Airport, Flight>
, 其中边代表可以从一个机场到达另一个机场的特定航班。
Choosing the right graph type
三种图形类型之间的本质区别在于它们的边表示。
Graph
具有节点之间的匿名连接的边,没有自身的身份或属性。如果每对节点最多连接一条边,并且不需要将任何信息与边相关联,则应该使用Graph。
ValueGraph
具有唯一或不唯一的值(例如,边缘权重或标签)的边。如果每对节点最多连接一条边,则应该使用ValueGraph,并且需要将信息与可能对于不同边(例如,边权重)相同的边相关联。
Network
的边最好的唯一对象,就像节点一样。如果边对象是唯一的,则应该使用网络,并且希望能够发布引用它们的查询。(注意,这种唯一性允许Network
支持并行边。)
Building graph instances
common.graph 提供的实现类不是公有的,而是通过设计来实现的。
This reduces the number of public types that users need to know about, and makes it easier to navigate the various capabilities that the built-implementations provide, without overwhelming users that just want to create a graph.这减少了用户需要了解的公共类型的数量,并更加容易的使用各种功能的实现,而不会难倒只想创建图形的用户。
若要创建图形类型的内置实现的一个实例,请使用相应的 Builder
类:
GraphBuilder
, ValueGraphBuilder
, or NetworkBuilder
. Examples:
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实例的方法有两种:
- 调用静态方法
directed()
或者undirected()
. Builder提供的图的实例可以使有向的或者无向的。 - 调用静态方法
from()
, 它提供一个基于已经存在的图的实例的Builder
.
- 调用静态方法
- 在创建好Builder实例之后,可以选择指定该实例其他的特性和能力。
- 可以多次调用
build()
方法在同一个Builder
实例中取创建复杂的图的实例。 - 在Builder上不需要指定节点与边的类型,在图形类型本身上指定它们就足够了。
build()
方法返回一个Mutable
的子类其关联着图的类型, 它提供了变异方法,更多使用在下面的 "Mutable
andImmutable
graphs" 中.
Builder constraints(约束条件) vs. optimization hints(优化提示)
Builder
类型通常提供两个选项: constraints and optimization hints.
在用Builder
创建实例时必须满足指定的约束行为与属性,例如:
- 图是否是有向的
- 图中是否允许自环
- 图的边值是否是有序的
诸如此类。
优化提示可以有选择性的由实现类用来提高效率,例如,用于确定内部数据结构的类型或初始大小。它们不能保证有任何效果。
每个图表类型提供相应的生成器中指定的限制访问,但不提供优化提示访问器。
Mutable
and Immutable
graphs
Mutable*
types
每种类型的图都有包含着复杂可转化的子类: MutableGraph
, MutableValueGraph
, 与
MutableNetwork
. 这些子类定义的转化方法有:
- 增加与删除顶点的方法:
addNode(node)
andremoveNode(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
而不是他们的子类。另一方面, 如果你得代码不需要转变成一个object,那么你得代码必须通过使用标记自己为"Mutable"来唤起人们对该事实的关注。
因此 Graph
, 等是接口, 他们不包含转变方法,但是向调用方提供该接口的实例不能保证调用方不会对其进行转变,它不会被调用方转变,因为(如果它实际上是一个Mutable*
子类型),调用者可以把它转换成那个子类型。 如果希望提供作为方法参数或返回值的图不能被修改的保证,那么应该使用Immutable实现;下面将对此进行详细介绍。
Immutable*
implementations
每一种类型的图也包含着复杂的 Immutable
实现. 这些类与 Guava的 ImmutableSet
, ImmutableList
, ImmutableMap
, 等相似.: 一旦被构造,则不会发生改变,并且在内部使用了高效的不可变数据结构。
不像Guava中其他的 Immutable
类型, 这些实现没有用于突变方法的任何方法签名,因此它们不需要为尝试的转变抛出UnsupportedOperationException。
可以通过调用静态的copyOf()
方法创建一个ImmutableGraph的实例:
ImmutableGraph<Integer> immutableGraph = ImmutableGraph.copyOf(graph);
Guarantees
这些Immutable*
类型做出以下保证:
- shallow immutability: 元素永远不能被添加,删除或者替换(这些类也没有实现
Mutable*
接口) - deterministic iteration: 迭代顺序总是与输入的图相同。
- thread safety: 多个线程同时访问此图是安全的。
- integrity: 此类型不能在该包外进行子类分类(这将允许这些保证被违反)
请将这些类作为接口对待,而不是实现方法
每个Immutable*
类是一个提供有意义的行为保证的类型,而不仅仅是一个特定的实现。你应该把它们当作世界上每个重要意义上的接口。
应该将存储Immutable*实例(比如ImmutableGraph)的字段和方法返回值声明为Immutable*类型,而不是相应的接口类型(比如Graph)。 这传达给调用方上面列出的所有语义保证,这些总是非常有用的信息。
另一方面, ImmutableGraph
的参数类型通常对调用方来说是讨厌的。取而代之的是接受图。
提醒: 正如在其他地方指出的,当元素包含在集合中时,修改元素(以影响其equals()行为的方式)几乎总是个坏主意。将导致未定义的行为和bug。最好完全避免使用可变对象作为Immutable*实例的元素,因为用户可能会期望您的“不可变”对象是深不可变的。
Graph elements (nodes and edges)
Elements must be useable as Map
keys
由用户提供的图形元素应该被认为是由图形实现所维护的内部数据结构的键。因此,用于表示图形元素的类必须具有equals()
和hashCode()
实现,这些实现具有或引出下面列出的属性。
唯一性
如果 A
与 B
满足 A.equals(B) == true
,那么A与B最多两个之一是图的元素。
hashCode()
与 equals()之间的一致性
hashCode()
必须与 equals()
所定义的 Object.hashCode()
一致。
equals()与排序的一致性
如果顶点是有序的 (例如, 通过raphBuilder.orderNodes()
), 那么这个序列必须与Comparator
或
Comparable定义的
equals()一致。
非递归性
hashCode
与quals()
不能递归的引用其他元素, 如在本例中:
// 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
元素类型 (e.g., Graph<Node<T>>
) 有以下问题:
- 冗余: the implementations of
Graph
的实现依赖common.graph
提供的方法已经存储了这些关系。 - 效率低: 增加或存取元素需要调用
equals()
(可能也会是hashCode()
), 时间复杂度为 O(n) - 不可行: 如果图中有环,
equals()
与hashCode()
可能不会停止。
相反,只使用T值本身作为节点类型(假设T值本身就是有效的MAP键)。
Elements and mutable state
如果图元素具有可变状态:
- 不能在
equals()/hashCode()
方法中反射可变状态(这在地图文档中详细讨论) - 不要构造多个彼此相等的元素,并期望它们是可互换的。don't construct multiple elements that are equal to each other and expect them to be interchangeable. 特别地,当向图中添加这些元素时,如果需要在创建期间多次引用这些元素(而不是向每个add*()调用传递新的MyMutableNode(id)),则应该创建它们一次,并存储引用。
如果需要存储可变的每个元素状态,一个选项是使用不可变元素,并将可变状态存储在单独的数据结构(例如, an element-to-state map).
Elements must be non-null
将元素添加到图中的方法实际上是拒绝空元素的方法。
Library contracts and behaviors
本节讨论 common.graph
类型的内置实现的行为。
Mutation
你可以添加一个边,其事件节点以前没有被添加到图中。如果他们还没有出现,他们会默默地添加到图表中:
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"
...
}
Graph equals()
and graph equivalence
对于 Guava 22, common.graph
的图类型都定义了 equals()
,这对特定类型来说是又意义的:
Graph.equals()
定义:两个图的顶点与边的集合相同那么这两个如相同 (其中,两个图的边具有相同的入射点与相同的方向 ).ValueGraph.equals()
定义两个ValueGraph
如果他们具有相同的顶点与边的集合,并且相同的边具有相同的权值那么这两个图相同。Network.equals()
定义两个Network
如果他们具有相同的顶点与边的集合,并且每个边都按照相同的指向连接着相同的顶点那么他们相同。
此外,对于每个图类型,只有当两个图的边具有相同的有向性(两个图都是有向的或者都是无向的)时,两个图才能相等。
当然,对于每个图形类型,hashCode()
总是与equals()
一致。
如果仅基于连接性比较两个网络或两个ValueGraph或将网络或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)) { ... }
Accessor methods
Accessors which return collections:
- 可以返回图的视图; 不支持对影响视图的图的修改(例如,在遍历node()时调用addNode(n)或removeNode(n)),并且可能导致抛出ConcurrentModificationException。
- 如果输入有效,但没有元素满足请求则返回空集合(例如:如果节点没有相邻节点,则相邻节点(节点)将返回空集合)。
Accessors 将跑出 IllegalArgumentException
如果元素不在图中.
虽然一些Java集合框架方法,如contains()
,使用对象参数而不是适当的泛型类型说明符,但是Guava22,普通图方法采用泛型类型说明符来提高类型安全性。
Synchronization
这取决于每个图形实现来确定自己的同步策略。默认情况下,未定义的行为可能是由于在另一个线程正在被更改的图上调用任何方法而导致的。
通常情况下,内置的可变实现不提供同步保证,但是Immutable*类(由于不可变)是线程安全的。
Element objects
添加到图中的节点、边和值对象与内置实现无关;它们只是用作内部数据结构的键。这意味着可以在图实例之间共享节点/边。
缺省情况下,节点和边缘对象是插入顺序的(即,节点和边缘对象的迭代器按照它们被添加到图的顺序访问node()和edges(),就像LinkedHashSet一样。
Notes for implementors
Storage models
common.graph
支持多种机制来存储图的拓扑结构,包括:
- 图形实现存储拓扑(例如,通过存储映射节点到相邻节点的Map<N,Set<N>),这意味着节点只是键,并且可以在图形之间共享
- 节点存储拓扑(例如,通过存储相邻节点的List<E>)
- 单独的数据存储库(例如,数据库)存储拓扑结构。
注意:对于支持孤立节点(没有入射边的节点)的图形实现,mutiMap不是足够的内部数据结构,因为它们限制键映射到至少一个值,或者在多映像中不存在。
Accessor behavior
对于返回集合的访问器,语义有几种选项,包括
- 该集合是不可变对象的拷贝 (例如
ImmutableSet
): 任何尝试改变集合的方法都将跑出异常,以及对图的改变不会映射带集合上来。 - 集合是一个不可变的视图 (e.g.
Collections.unmodifiableSet()
): 任何尝试改变集合的方法都将跑出异常,以及对图的改变不会映射带集合上来。 - 集合是一个可变的拷贝: 它可以被修改,但是对集合的修改不会反映在图中,反之亦然。
- 集合是一个可变的视图: 它可以被修改,但是对集合的修改不会反映在图中,反之亦然。
(理论上,人们可以返回一个集合,该集合沿着一个方向而不是另一个方向进行写操作(集合到图或者反之亦然),但是这基本上永远不会有用或清晰,所以请不要这样:)
(1)和(2)通常是优选的;在本文中,内置的实现通常使用(2)。
(3)是可行的选项,但是如果用户期望修改将影响图表,或者对图表的修改将反映在集合中,则可能会使用户感到困惑。
(4)是一个危险的设计选择,并且应该只在极端谨慎的情况下使用,因为保持内部数据结构的一致性可能很棘手。
Abstract*
classes
每个图形类型都有一个相应的 Abstract
class: AbstractGraph
, etc.
如果可能,图形接口的实现者应该扩展适当的抽象类,而不是直接实现接口。抽象类提供了几个关键方法的实现,这些方法很难正确地执行,或者具有一致的实现对其有帮助,例如:
*degree()
toString()
Graph.edges()
Network.asGraph()
Code examples
Is node
in the graph?
graph.nodes().contains(node);
Is there an edge between nodes u
and v
(that are known to be in the graph)?
在图是无向的情况下,下面例子中的参数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;
Basic Graph
example
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
Basic ValueGraph
example
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
Basic Network
example
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
Traversing an undirected graph node-wise
// 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;
}
Traversing a directed graph edge-wise
// 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
Why did Guava introduce common.graph
?
和Guava所做的其他事情一样同样的论点也适用于图:
- 代码重用/互操作/范式统一:与图形处理相关的许多事情
- efficiency: 使用低效率图形表示的代码有多少?太多的空间(例如矩阵表示)?
- correctness: 有多少代码对图的分析是错误的?
- promotion of use of graph as ADT: 如果容易的话,有多少人会使用图表?
- simplicity: 处理图形的代码更容易理解,如果它明确地使用了那个隐喻。
What kinds of graphs does common.graph
support?
This is answered in the "Capabilities" section, above.
common.graph
doesn’t have feature/algorithm X, can you add it?
Maybe. You can email us at guava-discuss@googlegroups.com
or open an issue on GitHub.
Our philosophy is that something should only be part of Guava if (a) it fits in with Guava’s core mission and (b) there is good reason to expect that it will be reasonably widely used.
common.graph
will probably never have capabilities like visualization and I/O; those would be projects unto themselves and don’t fit well with Guava’s mission.
Capabilities like traversal, filtering, or transformation are better fits, and thus more likely to be included, although ultimately we expect that other graph libraries will provide most capabilities.
Does it support very large graphs (i.e., MapReduce scale)?
Not at this time. Graphs in the low millions of nodes should be workable, but you should think of this library as analogous to the Java Collections Framework types (Map
, List
, Set
, and so on).
Why aren't successors/predecessors/in-edges/out-edges sorted/ordered?
tl;dr: Returning sorted/ordered sets of incident edges or adjacent nodes would be quite expensive or infeasible in our implementations, since those sets are live views. That said, you could create a custom implementation of the common.graph
interfaces that would have this behavior.
For more discussion, see this issue.
Why should I use it instead of something else?
tl;dr: you should use whatever works for you, but please let us know what you need if this library doesn't support it!
The main competitors to this library (for Java) are: JUNG and JGraphT.
JUNG
was co-created by Joshua O'Madadhain (the common.graph
lead) in 2003, and he still maintains it. JUNG is fairly mature and full-featured and is widely used, but has a lot of cruft and inefficiencies. Now that common.graph
has been released externally, he is working on a new version of JUNG
which uses common.graph
for its data model.
JGraphT
is another third-party Java graph library that’s been around for a while. We're not as familiar with it, so we can’t comment on it in detail, but it has at least some things in common with 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.
Major Contributors
common.graph
has been a team effort, and we've had help from a number of people both inside and outside Google, but these are the people that have had the greatest impact.
- Omar Darwish did a lot of the early implementations, and set the standard for the test coverage.
- James Sexton has been the single most prolific contributor to the project and has had a significant influence on its direction and its designs. He's responsible for some of the key features, and for the efficiency of the implementations that we provide.
- Joshua O'Madadhain started the
common.graph
project after reflecting on the strengths and weaknesses of JUNG, which he also helped to create. He leads the project, and has reviewed or written virtually every aspect of the design and the code.