2023-08-17
原文作者:LifeIsForSharing 原文地址:https://solang.blog.csdn.net/article/details/105101316

Graphs:用于对结构数据(即实体及其之间的关系)进行建模的库。主要功能包括:

Graph:图的边是匿名实体,没有身份或信息。
ValueGraph:其边具有关联的非唯一值的图。
Network:边是唯一对象的图形。

支持可变和不可变,有向和无向的图,以及其它一些属性。

1.说明

Guava的common.graph是一个用于对结构数据(即实体及其之间的关系)进行建模的库。示例包括网页和超链接;科学家及其撰写的论文;机场及其之间的路线;以及人们及其家庭纽带(家谱)。其目的是提供一种通用且可扩展的语言来处理此类数据。

2.定义

图由一组 节点 (也称为顶点)和一组 (也称为连接或弧)组成;每个边将节点彼此连接。与边关联的节点称为其 端点

(当我们在下面介绍一个称为Graph的接口时,我们将使用"graph"(小写字母"g")作为通用术语来指代这种类型的数据结构。当我们要在该库中引用特定类型时,我们会大写它。)

如果一条边具有已定义的起点(其源)和终点(其目标,也称为目的地),那么它是 有向边 。否则,它是 无向边 。有向边适用于非对称关系的建模(“派生自”,“链接至”,“由…撰写”),而无向边适用于对称关系的建模(“与…共同撰写论文”,“之间的距离”,“同级”)。

如果图的每个边都是有向的,那么它是有向图;如果图的每个边都是无向的,那么它是无向图。(common.graph不支持同时具有有向边和无向边的图。)

给出以下示例:

    graph.addEdge(nodeU, nodeV, edgeUV);
  • nodeUnodeV彼此 相邻
  • edgeUVnodeUnodeV关联 的(反之亦然)

如果是有向图,则:

  • nodeUnodeV前驱
  • nodeVnodeU后继
  • edgeUVnodeU输出边 (或外边)
  • edgeUVnodeV输入边 (或内边)
  • nodeUedgeUV来源
  • nodeVedgeUV目标

如果是无向图,则:

  • nodeUnodeV的前驱和后继
  • nodeVnodeU的前驱和后继
  • 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_aedgeUV_b相互平行,并且每个都与edgeVU反平行。
undirectedGraph中,edgeUV_aedgeUV_bedgeVU中的每一个与其它两个都相互平行。

3.能力

common.graph专注于提供接口和类来支持使用图。它不提供诸如I/O或可视化支持等功能,并且工具的选择非常有限。有关此主题的更多信息,请参见FAQ

总体而言,common.graph支持以下种类的图:

  • 有向图
  • 无向图
  • 带有相关值(权重、标签等)的节点和/或
  • 允许/不允许自环的图
  • 允许/不允许平行边的图(具有平行边的图有时称为多图)
  • 节点/边按插入顺序、排序或无序的图

特定common.graph类型支持的图的类型在其Javadoc中指定。Javadoc在其关联的Builder类型中指定了每种图类型的内置实现所支持的图的类型。该库中类型的特定实现(尤其是第三方实现)不需要支持所有这些变体,并且还可以支持其它变体。

该库与底层数据结构的选择无关:可以将关系存储为矩阵、邻接表、邻接图等,具体取决于实现者要针对哪些用例进行优化。

common.graph(目前)不包括对以下图变体的显式支持,尽管可以使用现有类型对其进行建模:

  • 树木,森林
  • 具有相同类型(节点或边)的元素但具有不同类型的图(例如:二部图/k部图,多模态图)
  • 超图

common.graph不允许同时具有有向边和无向边的图。

Graphs类提供了一些基本工具(例如,复制和比较图)。

4.图的类型

有三个顶级的图接口,它们以边的表示形式来区分:GraphValueGraphNetwork。这些是同级类型,即没有一个是其它任何类型的子类型。

这些"顶级"接口均扩展了SuccessorsFunctionPredecessorsFunction。这些接口被用作图形算法的参数类型(例如广度优先遍历),该算法仅需要访问图中节点的后继/前驱的一种方式。图所有者已经有了一个对其有效的表示,并且不特别希望将其表示序列化为common.graph类型以仅运行一种图形算法,在这样的情况下特别有用。

4.1Graph

Graph是最简单,最基础的图类型。它定义了用于处理节点到节点关系的低级运算符,例如successors(node)adjacentNodes(node)inDegree(node)(后继节点、邻接节点和入度)。它的节点是最优的唯一对象。你可以认为它们类似于将Map键映射到Graph内部数据结构中。

Graph图的边是完全匿名的;它们只根据其端点进行定义。

用例示例:Graph<Airport>,其边连接着可以乘坐直达航班的机场。

4.2ValueGraph

ValueGraph具有Graph所具有的所有与节点有关的方法,但是添加了两个方法来检索指定边的值。

ValueGraph的每个边都有一个用户指定的关联值。这些值不必是唯一的(就像节点一样)。ValueGraphGraph之间的关系类似于MapSet之间的关系;Graph的边是一组端点对,而ValueGraph的边是从端点对到值的映射。

ValueGraph提供了一个asGraph()方法,该方法返回ValueGraphGraph视图。这允许在Graph实例上操作的方法也可用于ValueGraph实例。

用例示例:ValueGraph<Airport, Integer>,其边值表示该边连接的两个机场之间旅行所需的时间。

4.3Network

Network具有Graph所具有的所有与节点有关的方法,但是添加了处理边和节点到边关系的方法,例如outEdges(node)incidentNodes(edge)edgesConnecting(nodeU, nodeV)

Network的边是最优的(唯一)对象,就像所有图类型中的节点一样。边的唯一性约束使Network可以原生支持平行边,以及与边和节点到边关系相关的方法。

Network提供了一个asGraph()方法,该方法返回NetworkGraph视图。这允许在Graph实例上操作的方法也可用于Network实例。

用例示例:Network<Airport, Flight>,其中的边表示从一个机场到另一个机场可以乘坐的特定航班。

4.4选择正确的图类型

这三种图形类型之间的本质区别在于它们对边的表示形式。

Graph的边是节点之间的匿名连接,没有自己的标识或属性。如果每对节点最多由一条边连接,并且不需要将任何信息与边相关联,则应使用Graph

ValueGraph的边具有的值(例如边的权重或标签)可能是唯一的,也可能不是唯一的。如果每对节点最多由一条边连接,并且需要将信息与对于不同边可能相同的边相关联(例如,边的权重),则应使用ValueGraph

Network的边就像节点一样,是最优的唯一对象。如果边对象是唯一的,并且你希望能够发出引用它们的查询,则应使用Network。(请注意,这种唯一性允许Network支持平行边。)

5.构建图实例

根据设计,common.graph提供的实现类不是公共的。这减少了用户需要了解的公共类型的数量,并使得导航内置实现所提供的各种功能更加容易,而不会使只想创建图的用户不知所措。

要创建图类型的内置实现之一的实例,请使用相应的Builder类GraphBuilderValueGraphBuilderNetworkBuilder。例子:

    // 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*子类型:MutableGraphMutableValueGraphMutableNetwork。这些子类型定义了变体方法:

  • 添加和删除节点的方法:

    • addNode(node)removeNode(node)
  • 添加和删除边的方法:

这与Java集合框架以及Guava的新集合类型的工作方式不同;这些类型的每一个都包含(可选)变体方法的签名。我们选择将可变方法分为子类型,部分原因是鼓励防御性编程:通常来说,如果你的代码仅检查或遍历图而不对其进行修改,则应将其输入指定为GraphValueGraphNetwork,而不是其可变子类型。另一方面,如果你的代码确实需要修改对象,则代码必须通过使用将自身标记为“可变”的类型来引起对这一事实的注意,这对你很有帮助。

由于Graph等是接口,即使它们不包含变体方法,向调用者提供此接口的实例也不能保证调用者不会对其进行改变,例如(如果它实际上是Mutable*子类型),调用者可以将其转换为该子类型。如果要提供契约保证,作为方法参数或返回值的图不能被修改,则应使用Immutable实现;下面将对此进行详细介绍。

6.2Immutable*实现

每个图类型还具有相应的不可变实现。这些类类似于Guava的ImmutableSetImmutableListImmutableMap等:一旦构建,便无法对其进行修改,并且它们在内部使用有效的不可变数据结构。

但是,与其它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

如果AB满足A.equals(B) == true,则两者中的最多一个可能是图的元素。

7.1.2hashCode()equals()之间的一致性

hashCode()必须与Object.hashCode()定义的equals()一致。

7.1.3equals()的排序一致性

如果节点已排序(例如,通过GraphBuilder.orderNodes()),则排序必须与ComparatorComparable定义的equals()一致。

7.1.4非递归性

hashCodeequals()不能递归引用其它元素,如本例所示:

    // 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,或者要将NetworkValueGraphGraph进行比较,则可以使用NetworkValueGraph提供的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节点uv之间是否存在边(已知存在于图中)?

在图是无向的情况下,以下示例中的参数uv的顺序无关紧要。

    // 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集合框架类型(MapListSet等)。

11.5如何定义successors(node)的顺序?

在图生成器中将incidentEdgeOrder()设置为ElementOrder.stable(),可以确保successors(node)按照插入边的顺序返回node节点的后继节点。对于与节点的关联边有关的大多数其它方法,也是如此,例如(诸如incidentEdges(node))。

11.6为什么要用它而不用别的东西?

tl;dr :你应该使用所有适合你的方法,但是如果该库不支持你需要的内容,请告诉我们!

该库(针对Java)的主要竞争对手是:JUNGJGraphT

JUNG由Joshua O’Madadhain(common.graph主管)于2003年共同创建,他仍然拥有它。JUNG相当成熟,功能齐全,被广泛使用,但是却有很多不足和低效之处。现在,common.graph已经对外发布,他正在研究使用common.graph作为其数据模型的JUNG的新版本。

JGraphT是另一个第三方Java图库,已经存在了一段时间。我们不太熟悉它,因此我们无法对其进行详细评论,但它至少与JUNG有一些共同之处。该库还包括一些适配器类,以将common.graph图适配为JGraphT图。

如果你有非常具体的要求,则推出自己的解决方案有时是正确的答案。但是,就像你通常不会在Java中实现自己的哈希表(而是使用HashMapImmutableMap)一样,出于上述所有原因,你也应该考虑使用common.graph(或在必要时使用另一个现有的图库)。

12.主要贡献者

common.graph是团队合作的成果,我们得到了Google内部和外部许多人的帮助,但这些人的影响最大。

  • Omar Darwish 做了很多早期的实现,并为测试覆盖率设定了标准。
  • James Sexton 是该项目唯一最多产的贡献者,并且对项目的方向和设计产生了重大影响。他负责一些关键功能以及我们提供的实现的效率。
  • Joshua O’Madadhain 在反思了JUNG的长处和短处之后,开始了common.graph项目,他也帮助创建了这个项目。他领导该项目,并且几乎审查或编写了设计和代码的各个方面。
  • Jens Nyman 贡献了许多最新的功能,例如Traverser和不可变的图生成器。他对项目的未来方向也有重大影响。

本文参考:
GraphsExplained
guava-tests-graph

阅读全文