图表,说明
Guava的common.graph
是用于模型图表结构数据的类库,即,实体和他们之间的关系。比如网页和超链接;科学家和他们写的论文;机场和他们之间的航线;人和他们的家庭关系(家族树)。它的目的是提供一个处理这些数据的通用可扩展的语言。
定义
图表由一组节点[node](也可以称为顶点[vertices])和边界[edge](也可以称为链接[link]或者弧线[arcs])组成;每个边界将节点将彼此连接起来。附属于一边的节点称为他的端点[endpoint]。
(尽管下面我们介绍了一个称为Graph
的接口,我们将使用“graph”(小写g)作为这种类型的数据结构的通用术语。当我们想要引用这个类库的特定类型时,我们要大写它。)
如果它已经定义开始(它的源[source])和结尾(它的目标[target],也可以称为目的地[destination])一个边[edge]是有向的。除此之外,它是无向的[undirected]。有向的边适用于非对称关系模型(“从哪传下来”,“链接到哪里”,“由谁著作”),同时无向边适用于对称关系模型(“与谁合著一篇论文”,“之间的距离”,“兄妹关系”)。
如果图表的边是有向的,那么图表就是有向的,如果它的边是无向的那么它就是无向的。(common.graph
不支持同时有向和无向的边的图表。)
给出这样的示例:
graph.addEdge(nodeU, nodeV, edgeUV);
nodeU
和nodeV
是相临的。edgeUV
是附属于nodeU
和nodeV
(反之亦然)
如果graph
是有向的,那么:
nodeU
在nodeV
的前面nodeV
在nodeU
的后面edgeUV
是nodeU
的向外边(或者外边[out-edge])edgeUV
是nodeV
的向内边(或者内边[in-edge])nodeU
是edgeUV
的源nodeV
是edgeUV
的目标
如果graph
是无向的,那么:
nodeU
是nodeV
的前面和后面nodeV
是nodeU
的前面和后面edgeUV
同时是nodeU
的向内和向外边edgeUV
同时是nodeV
的向内和向外边
所有的这些关系都是关于图表的。
一个自循环的[self-loop]是连接节点本身的边;相当于它是一个端点相同节点。如果一个自循环是有方向的,它同时是事件节点的向外和向内边,它的事件节点既是源同时也是字循环边的目标。
如果两个边相同的顺序(如果有的话)连接相同的节点那么他们是串行的[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
每一个与其他两个都是相对平行的。
功能
common.graph
致力于提供接口和类来支持处理图表。它不提供例如I/O或者可视化支持功能,并且它的实用工具的选择非常有限。关于这个主题可以查看FAG。
作为一个整体,common.graph
支持以下各种各样的图表:
- 有向图
- 无向图
- 节点并且具有相关值(权重,标签等等)的边。
- 允许或者不允许自包含的图表
- 允许或者不允许平行的边的图(带有平行的边的图表有时称为multigraphs)
- 节点/边为插入顺序、排序和无序的图
通过特定的common.graph
类型的各种图在它的javadoc中指定。通过每一种图类型内置实现支持的各种图在javadoc中其相关联的Builder
类型指定。在这个类库中类型的指定实现(特别是第三方实现)不要求支持所有这些类型,并且可能还支持其他类型。
类库对于底层数据的选择是不可知的:关系可能存储为指标,邻接表,邻接图等等。取决于实现者想要想要优化的用例。
common.graph
不包含(就是现在)以下图表变体的显性支持。尽管他们可以实用现有类型建模:
- trees,forests
- 相同种类的元素(节点或者边)有不同的类型的图表(例如:二部图/k部图,多模态图[bipartite/k-partite graphs, multimodal graphs)])
- 超图[hypergraphs]
common.graph
不允许既有有向边又有无向边的图表。
Graphs
类提供一些基本的实用工具(例如,对图表进行复制和比较)。
图表类型
有三个顶级图表接口,他们是根据边的表示区分的:Graph
,ValueGraph
和Network
。他们是孪生类型(级别一样,平等的)即没有一个是任何其他类型的子类型。
每一个顶级接口继承SuccessorsFunction
和PredecessorsFunction
。这些接口打算用作图算法(例如广度优先遍历)的参数类型,这些算法只需要一种访问图中节点的前面和后置方法。在图表的拥有者已经有一个适用他们的表示的情况下特别有用,不会特定想将他们的表示序列化到common.graph
类型,只是运行一个图算法。
Graph
Graph
是最简单的最基本的图类型。它定义了处理节点到节点关系的最低等级的操作,例如successors(node)
,adjacentNodes(node)
和inDegree(node)
。它的节点是头等(first-class)唯一对象;你可以想象他们类似于Map
的key进入Graph
内部数据结构。
Graph
的 边是完全匿名的;他们依据他们的端点定义。
示例用例:Graph<Airport>
,它的边连接了可以直飞乘坐直飞航班的机场。
ValueGraph
ValueGraph
有Graph
所有的相关节点的方法,并且添加了一组方法,其可以检索指定表的值。
ValueGraph
的边每一个都有相关联的用户指定的值。这些值不需要唯一(但是节点唯一);ValueGraph
和Graph
之间的关系类似于Map
和Set
的关系;Graph's
的边是一对端点的集合,并且ValueGraph
的边从一对端点到值的映射。
ValueGraph
提供asGraph()
方法,其返回ValueGraph
的一个Graph
的视图。允许在Graph
实例上的方法也对ValueGraph
实例起作用。
示例用例:ValueGraph<Airport, Integer>
,它的边值表示在这条边连接的两个Airport
之间旅行所需的时间。
Network
Network
有Graph
所做的所有相关节点方法,但是添加了处理边和节点到边关系的方法,例如outEdges(node)
,incidentNodes(edge)
和edgesConnecting(nodeU, nodeV)
。
Network
的边是优先类(first-class)唯一的对象,就像做所有图类型的节点一样。对于边的唯一约束是允许Network
本地支持并行边,以及与边和节点到边关系相关的方法。
Network
提供一个asGraph()
方法,其返回一个Network
的Graph
视图。这允许操作在Graph
实例上的方法也可以用于Network
实例。
示例用例:Network<Airport, Flight>
,在内部的边表示指定的一个从一个机场到另外一个可以乘坐的航线。
选择正确的图类型
三个图之间最基本的差别是他们的边的表示。
Graph
有节点间匿名连接的边,他们自己没有身份和属性。如果每一个节点对通过一个边连接,而且你不需要使用边关联任何信息,你应该使用Graph
。
ValueGraph
是有值(即边权重或者标签)的边,可能不唯一。如果每一个节点对最多由一个边连接,并且你需要对不同的边可能是相同的[边的实例不同,但是值相同]那些边关联信息,你应该使用ValueGraph
。
Network
是优先类的唯一对象的边,就跟节点一样。如果你的边对象是唯一的,并且你希望能够发出引用他们的查询,你应该使用Network
。(注意,这个唯一性允许Network
支持并行边)
构建图实例
按照设计,common.graph
提供的实现类不是public的。这减少了用户需要了解public类型的数量,并且更容易导向内置实现提供的各种功能,而不会让只想创建图的用户不知所措。
要创建图类型的内建实现的其中一个的实例,使用相对应的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();
- 你可以通过两种方式的一种得到graph的
Builder
实例:- 调用静态方法
directed()
或者undirected()
。Builder
提供的每一个图实例将是有向或者无向的。 - 调用静态方法
from()
,其给一个基于以存在的图实例的Builder
。
- 调用静态方法
- 你创建
Builder
实例之后,你可以可选指定其他特性和功能。 - 构建可变图
- 你可以在相同的
Builder
实例调用build()
多次来构建多个具有相同配置的图实例。 - 你不需要在
Builder
指定元素类型(S);在图类型本身上指定他们已经足够。 build()
方法返回相关联的图类型的Mutable
子类型,其提供变体方法;更多信息在下方的"Mutable
andImmutable
graphs"章节。
- 你可以在相同的
- 构建不可变图
- 你可以在相同的
Builder
实例上调用immmutable()
多次来创建多个具有相同配置的ImmutableGraph.Builder
实例。 - 你需要在调用
immutable
时指定元素类型。
- 你可以在相同的
Builder约束和可优化点
Builder
类型通常提供两种方式的选择:约束和可优化点。
约束指定了通过给定的Builder
实例创建的图必须满足的行为和属性,例如:
- 图是否是有向的
- 图是否允许自包含
- 图的边是否是排序的
等等。
优化点可以通过实现类选择性使用来增加效率,例如,确定类型或者初始内部数据结构的大小。他们不能保障有任何效果。
每一个图类型提供相对于它的指定的Builder
约束的访问工具,但是不会提供优化点的访问工具。
Mutable
和Immutable
图
Mutable*
类型
每一个图类型有一个相对应的Mutable*
的子类型: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 Collections Framework的方式不同–而是Guava的新的集合类型–在历史上是有效的;每一个这些类型包括变种方法的签名。我们选择将可变方法分解为子类型,部分原因是鼓励防御性编程:通常来说,如果你的代码仅检查或者遍历图,而不是改变它,它的输入应该指定为Graph
,ValueGraph
或者Network
而不是他们的可变子类型。另一方面,如果你的代码确实需要改变一个对象,通过使用一个将自身标记为“Mutable”的类型来提醒你注意这个事实是很有帮助的。
因为Graph
等是接口,尽管他们不能包含变体方法,提供此接口的一个实例给调用者,并不能保证它不会被调用者进行改变,因为(实际上如果它是一个Mutable*
子类型)调用者可以将它转换为子类型。如果你想要提供一个合同担保一个方法的参数的图或者返回值不能被修改,你应该使用Immutable
实现;更多信息可以在以下内容了解。
Immutable*
实现
每一个图类型也有对应的Immutable
实现。这些类类似于Guava的ImmutableSet
,ImmutableList
,ImmutableMap
等等;一旦构建,他们不能被修改,并且他们在内部使用有效的不可修改数据结构。
不像其他GuavaImmutable
类型,但是这些实现没有任何变体方法的方法签名,所以他们不需要为尝试的突变抛出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
,然后关联边的顺序将是在复制过程中他们被访问的顺序。
保证
每一个Immutable*
类型有以下保障:
- 弱不变性:元素可以不被添加,移除或者替换(这些类没有实现
Mutable*
接口)。 - 确定的迭代:迭代的顺序一直与输入图的顺序一致。
- 线程安全:通过多个线程并行访问图是安全的。
- 完整性:此类型不能被此包外的类子类化(谁会允许这些保证被违反呢)。
将这些类视为“接口”,不是实现
每一个Immutable*
类是提供有意义的行为保证的类型–不仅仅是一个特殊的实现。你应该将他们视为所有重要意义上的接口。
存储Immutable*
实例(例如ImmutableGraph
)的字段和方法返回值应该被声明为Immutable*
类型而不是相对应的接口类型(例如Graph
).这将向调用者传递上面列出来的所有保证,其几乎总是非常有用的信息。
另一方面,一个为ImmutableGraph
的参数类型通常对于调用者是一个麻烦事。相反,接收Graph
。
注意:正如在其他地方提到的。当元素包含在集合中时,修改一个元素几乎总是一个坏注意(以影响其equals()
行为的方式)。将导致未定义的行为和bug。最好是防止使用可变对象作为Immutable*
实例的元素,因为用户可能期望你的"不可变"对象称为深度不可变的。
图元素(节点和边)
元素必须可用当作May
的key
用户提供的图元素应该被看作图实现维护的内部数据结构的key。因此,用于表示图元素的类必须有equels()
和hashCode()
实现,它们具有或引起下面列出的属性。
唯一性
如果A
和B
满足A.equals(B) == true
,则两个最多有一个是图的元素。
hashCode()
和equals()
之间一致性
hashCode()
必须与Object.hashCode()
定义的equels()
一致。
equals()
排序一致
如果节点已经排序(例如,通过GraphBuilder.orderNodes()
),这个顺序必须与equels()
一致,正如在Comparator
和Comparable
一样。
非递归
hashCode
和equels()
不能递归依赖其他元素,正如下方例子:
// 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
实现已经存储三个关系。 - 效率低:添加/访问这样的元素调用
equels()
(还有可能hashCode()
),需要O(n)时间。 - 不可行:如果在图中存在循环,
equels()
和hashCode()
可能无法终止。
相反,仅使用T
值本身作为节点类型(假设T
值是他们自己有效的Map
的key)。
元素和可变状态
如果图元素有可变状态:
- 可变状态不能反映在
equals()/hashCode()
方法中(已经在Map
文档细节中讨论过)。 - 不要构建相互相等的多个元素,并期望他们是可互换的。特别是,如果在创建期间,你需要引用这些元素超过一次,当添加这样的元素到图中时,你应该只创建他们一次并存储引用(而不是在每一次
add*()
调用传入new MyMutableNode(id)
)。
如果你需要存储可变的每一个元素状态,一个选择是使用不可变元素并存储可变状态到单独的数据结构中(即,element-to-state映射)。
元素必须不为空
添加元素到图的方法是要求拒绝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"
...
}
Graph equels()
和图等价
从Guava 22开始,common.graph
的图类型以一种对特定类型有意义的方式定义equals()
:
- 如果两个
Graph
有相同的节点和变集合(即,在两个图中每一个边有相同的端点和相同的方向),Graph.equals()
定义两个Graph
是相等的。 - 如果两个
ValueGraph
有相同的节点和边集合并且相等的边有相等的值,ValueGraph.equals()
定义了两个ValueGraph
相等的。 - 如果两个
Network
有相同的节点和边集合并且每一个边对象以相同的方向(如果有的话)连接相同的节点,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
。 - 如果他们的输入是有效的但是没有元素满足请求将返回空集合(例如:如果
node
没有相邻的节点,adjacentNodes(node)
将返回空集合)。
访问器将抛出IllegalArgumentException
,如果传入的一个元素不在图中。
而一些Java集合框架方法,例如contains()使用Object
参数,和不是合理的泛型说明符,从Guava 22开始,common.graph
方法使用泛型说明符来提升类型安全性。
同步
这取决于每一个图实现来确定他自己的同步策略。默认情况下,图被另外一个线程改变的任何方法的调用都可能导致未定义的行为。
通常来说,内建可变实现不提供同步保证,但是Immutable*
类(因为它是不可变的)是线程安全的。
元素对象
添加到图中的节点,边和值对象与我们的内部实现是不想关的;他们仅仅用做内部数据结构的key。这意味着节点/边可能在图实例之间共享。
默认情况下,节点和边对象是按照插入排序的(即,对于nodes()
和edges()
的Iterator
被访问的顺序就是他们被添加到图的顺序,正如LinkedHashSet
)。
实现的注意事项
存储模型
common.graph
支持多种机制存储一个图的拓扑,包含:
- 图实现存储拓扑(例如,通过存储一个
Map<N, Set<N>>
,映射map节点到他们的相邻节点);这意味着节点仅仅是key,可以在图之间共享。 - 节点存储拓扑(例如,通过存储相邻节点的
List<E>
);这意味着节点是指定的图。 - 单独的数据仓库存储拓扑(例如,数据库)。
注意:对于支持独立的节点(没有关联边的节点)的Graph实现,Multimap
并不足够满足内部数据结构,因为他们的限制是key要么映射至少一个值,要么不存在Multimap
中。
访问器行为
对于返回一个集合的访问器,有几个用于此语义得选项,包括:
- 集合是一个不可变得副本(即,
ImmutableSet
):以任何方式尝试修改集合将抛出异常,并且对图得修改将不会反应到集合中。 - 集合是一个不可修改的视图(即,
Collections.unmodifiableSet()
):以任何方式尝试修改集合将抛出异常,并且对图得修改将会反应到集合中。 - 集合是一个可变副本:它可以被修改,但是集合的修改将不会反应到图中,反之亦然。
- 集合是一个可修改视图:它可以被修改,并且对集合的修改将反应到图中,反之亦然。
(理论上,我们可以返回一个集合,其通过写入一个方向而不是另外一个方向(集合到图,或者相反),但是这基本上不会有用或者清楚,请不要这样做)
(1)和(2)通常优先选择;在撰写本文时,内建实现通常使用(2)。
(3)是一个可行的选项,但是对于用户可能是令人疑惑的,如果他们期望修改影响图,或者对图的修改反应在集合中。
(4)是一个危险的设计选择并且应该极其小心使用,因为保持内部数据结构一致性可能是棘手的。
Abstract*
类
每一个图类型有一个对应的Abstract
类:AbstractGraph
等等。
图接口的实现应该(尽可能)继承合理的抽象类而不是直接实现接口。抽象类提供几个关键方法的实现,要正确执行这些方法可能比较棘手,或者有统一的实现是有帮助的,例如:
*degree()
toString()
Graph.edges()
Network.asGraph()
代码示例
Node
是否在图中?
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
示例
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}
基本的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所作的许多其他事情:
- 模型的代码复用/互通性/一致:很多事情和图形处理有关
- 效率:有多少代码使用了低效的图形表示?空间太大(例如矩阵表示)?
- 正确性:图形分析有多少代码是错误的?
- 推广使用图形作为ADT:如果图形很简单,有多少人会使用它?
- 简易:如果显示地使用这个比喻,处理图形的代码更容易理解
common.graph
支持什么种类的图形?
这个答案在上面的"功能"章节。
common.graph
没有特性/算法X,你可以添加它吗?
大概吧。你可以发邮件guava-discuss@googlegroups.com
给我们,或者在Github上创建issue.
我们的想法是以下一些东西可以成为Guava的一部分,a)符合Guava的核心使命,b)我们有充分的理由相信它会得到相当广泛的应用。
common.graph
将有可能从不会有像可视化和I/O的功能;这些都是自己的项目,与Guava的使命不太匹配。
像遍历,过滤或者转换功能是比较适合,因此更有可能被包含在内,尽管最终我们系统其他图库将提供大多数功能。
支持非常大的图吗?(即MapReduce规模 )
现在还没有。抵数百万个节点的图应该是可行的,但是你应该把这个类库看作类似于Java集合框架类型(Map
,List
,Set
等等)。
我可以定义successors(node)
的顺序吗?
在图构建器中设置incidentEdgeOrder()
为ElementOrder.stable()
确保successors(node)
按照边插入的顺序返回node
的继任节点。对于关联的节点的附属边的大多数方法也是如此(例如,incidentEdges(node)
)。
我为什么应该使用它代替其他东西?
tl;dr: 你应该使用任何适合你的,但是如果这个库不支持它请让我们知道你需要什么。
JUNG
是Joshua O’Madadhain(common.graph
领导)在2003年共同创建的,并且他一直维护它。JUNG是相当成熟的和功能齐全的并且广泛使用,但是有很多麻烦和低效率。现在common.graph
已经对外发布了,他正在致力于JUNG的新版本,该版本使用common.graph
作为其数据模型。
JGraphT
是另外一个已经存在一段时间的第三方Java图类库。我们对它不是很熟悉,所以我们详细地评价它,但是它至少和JUNG
有共同之处。这个类库也包含多个适配类来适配common.graph
图到JGraphT
图。
如果你有非常具体的要求,有时自行制定解决方案是正确的答案。但是正如你通常不会使用Java实现自己的hash table一样(反而使用HashMap
或者ImmutableMap
),出于以上列出的所有原因,你应该考虑使用common.graph
(或者,如果有必要,另外一个现有的图类库)。