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,可以添加吗?
也许。你可以通过guava-discuss@googlegroups.com向我们发送电子邮件,或在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
和不可变的图生成器。他对项目的未来方向也有重大影响。