/** * Returns true if {@code graph} has at least one cycle. A cycle is defined as a non-empty subset * of edges in a graph arranged to form a path (a sequence of adjacent outgoing edges) starting * and ending with the same node. * * <p>This method will detect any non-empty cycle, including self-loops (a cycle of length 1). */ public static <N> boolean hasCycle(Graph<N> graph) { int numEdges = graph.edges().size(); if (numEdges == 0) { return false; // An edge-free graph is acyclic by definition. } if (!graph.isDirected() && numEdges >= graph.nodes().size()) { return true; // Optimization for the undirected case: at least one cycle must exist. } Map<Object, NodeVisitState> visitedNodes = Maps.newHashMapWithExpectedSize(graph.nodes().size()); for (N node : graph.nodes()) { if (subgraphHasCycle(graph, visitedNodes, node, null)) { return true; } } return false; }
private static <N> GraphConnections<N, Presence> connectionsOf(Graph<N> graph, N node) { Function<Object, Presence> edgeValueFn = Functions.constant(Presence.EDGE_EXISTS); return graph.isDirected() ? DirectedGraphConnections.ofImmutable( graph.predecessors(node), Maps.asMap(graph.successors(node), edgeValueFn)) : UndirectedGraphConnections.ofImmutable( Maps.asMap(graph.adjacentNodes(node), edgeValueFn)); }
/** * Returns a {@link GraphBuilder} initialized with all properties queryable from {@code graph}. * * <p>The "queryable" properties are those that are exposed through the {@link Graph} interface, * such as {@link Graph#isDirected()}. Other properties, such as {@link #expectedNodeCount(int)}, * are not set in the new builder. */ public static <N> GraphBuilder<N> from(Graph<N> graph) { return new GraphBuilder<N>(graph.isDirected()) .allowsSelfLoops(graph.allowsSelfLoops()) .nodeOrder(graph.nodeOrder()); }
assertThat(graphString).contains("isDirected: " + graph.isDirected()); assertThat(graphString).contains("allowsSelfLoops: " + graph.allowsSelfLoops()); for (N node : sanityCheckSet(graph.nodes())) { assertThat(nodeString).contains(node.toString()); if (graph.isDirected()) { assertThat(graph.degree(node)).isEqualTo(graph.inDegree(node) + graph.outDegree(node)); assertThat(graph.predecessors(node)).hasSize(graph.inDegree(node)); assertThat(graph.successors(node)).hasSize(graph.outDegree(node)); } else { int selfLoopCount = graph.adjacentNodes(node).contains(node) ? 1 : 0; assertThat(graph.degree(node)).isEqualTo(graph.adjacentNodes(node).size() + selfLoopCount); assertThat(graph.predecessors(node)).isEqualTo(graph.adjacentNodes(node)); assertThat(graph.successors(node)).isEqualTo(graph.adjacentNodes(node)); assertThat(graph.inDegree(node)).isEqualTo(graph.degree(node)); assertThat(graph.outDegree(node)).isEqualTo(graph.degree(node)); for (N adjacentNode : sanityCheckSet(graph.adjacentNodes(node))) { if (!graph.allowsSelfLoops()) { assertThat(node).isNotEqualTo(adjacentNode); graph.predecessors(node).contains(adjacentNode) || graph.successors(node).contains(adjacentNode)) .isTrue(); for (N predecessor : sanityCheckSet(graph.predecessors(node))) { assertThat(graph.successors(predecessor)).contains(node); assertThat(graph.hasEdgeConnecting(predecessor, node)).isTrue();
assertThat(network.nodes()).isEqualTo(asGraph.nodes()); assertThat(network.edges().size()).isAtLeast(asGraph.edges().size()); assertThat(network.nodeOrder()).isEqualTo(asGraph.nodeOrder()); assertThat(network.isDirected()).isEqualTo(asGraph.isDirected()); assertThat(network.allowsSelfLoops()).isEqualTo(asGraph.allowsSelfLoops()); N nodeU = endpointPair.nodeU(); N nodeV = endpointPair.nodeV(); assertThat(asGraph.edges()).contains(EndpointPair.of(network, nodeU, nodeV)); assertThat(network.edgesConnecting(nodeU, nodeV)).contains(edge); assertThat(network.successors(nodeU)).contains(nodeV); assertThat(nodeString).contains(node.toString()); assertThat(network.adjacentNodes(node)).isEqualTo(asGraph.adjacentNodes(node)); assertThat(network.predecessors(node)).isEqualTo(asGraph.predecessors(node)); assertThat(network.successors(node)).isEqualTo(asGraph.successors(node));
/** * Returns the set of nodes that are reachable from {@code node}. Node B is defined as reachable * from node A if there exists a path (a sequence of adjacent outgoing edges) starting at node A * and ending at node B. Note that a node is always reachable from itself via a zero-length path. * * <p>This is a "snapshot" based on the current topology of {@code graph}, rather than a live view * of the set of nodes reachable from {@code node}. In other words, the returned {@link Set} will * not be updated after modifications to {@code graph}. * * @throws IllegalArgumentException if {@code node} is not present in {@code graph} */ public static <N> Set<N> reachableNodes(Graph<N> graph, N node) { checkArgument(graph.nodes().contains(node), NODE_NOT_IN_GRAPH, node); Set<N> visitedNodes = new LinkedHashSet<N>(); Queue<N> queuedNodes = new ArrayDeque<N>(); visitedNodes.add(node); queuedNodes.add(node); // Perform a breadth-first traversal rooted at the input node. while (!queuedNodes.isEmpty()) { N currentNode = queuedNodes.remove(); for (N successor : graph.successors(currentNode)) { if (visitedNodes.add(successor)) { queuedNodes.add(successor); } } } return Collections.unmodifiableSet(visitedNodes); }
/** Creates a mutable copy of {@code graph} with the same nodes and edges. */ public static <N> MutableGraph<N> copyOf(Graph<N> graph) { MutableGraph<N> copy = GraphBuilder.from(graph).expectedNodeCount(graph.nodes().size()).build(); for (N node : graph.nodes()) { copy.addNode(node); } for (EndpointPair<N> edge : graph.edges()) { copy.putEdge(edge.nodeU(), edge.nodeV()); } return copy; }
checkArgument(graph.isDirected(), "the graph must be directed"); checkArgument(!graph.allowsSelfLoops(), "the graph cannot allow self loops"); for(final T node : graph.nodes()) { for(final T successor : graph.successors(node)) { requiredCounts.merge(successor, 1, (a, b) -> a + b); final List<T> results = new ArrayList<>(); for(final T node : graph.nodes()) { if(!requiredCounts.containsKey(node)) { processing.add(node); for(final T successor : graph.successors(now)) { final int newCount = requiredCounts.get(successor) - 1; if(newCount == 0) {
g.nodes().containsAll(nodes), "Input graph must contain all specified nodes"); ValueGraphBuilder<Object, Object> builder = g.isDirected() ? ValueGraphBuilder.directed() : ValueGraphBuilder.undirected(); MutableValueGraph<N, Set<N>> newGraph = builder.expectedNodeCount(nodes.size()).nodeOrder(g.nodeOrder()).build(); for (N s : g.successors(node)) { for (N t : g.successors(s)) { if (!nodes.contains(t) || t.equals(node)) { continue;
@Test public void transpose_directedGraph() { MutableGraph<Integer> directedGraph = GraphBuilder.directed().allowsSelfLoops(true).build(); directedGraph.putEdge(N1, N3); directedGraph.putEdge(N3, N1); directedGraph.putEdge(N1, N2); directedGraph.putEdge(N1, N1); directedGraph.putEdge(N3, N4); MutableGraph<Integer> expectedTranspose = GraphBuilder.directed().allowsSelfLoops(true).build(); expectedTranspose.putEdge(N3, N1); expectedTranspose.putEdge(N1, N3); expectedTranspose.putEdge(N2, N1); expectedTranspose.putEdge(N1, N1); expectedTranspose.putEdge(N4, N3); Graph<Integer> transpose = transpose(directedGraph); assertThat(transpose).isEqualTo(expectedTranspose); assertThat(transpose(transpose)).isSameAs(directedGraph); AbstractGraphTest.validateGraph(transpose); for (Integer node : directedGraph.nodes()) { assertThat(directedGraph.inDegree(node)).isSameAs(transpose.outDegree(node)); assertThat(directedGraph.outDegree(node)).isSameAs(transpose.inDegree(node)); } assertThat(transpose.successors(N1)).doesNotContain(N2); directedGraph.putEdge(N2, N1); // View should be updated. assertThat(transpose.successors(N1)).contains(N2); AbstractGraphTest.validateGraph(transpose); }
@Override public Set<N> predecessors(N node) { return delegate().successors(node); // transpose }
if (graph.isDirected()) { for (N node : graph.nodes()) { for (N reachableNode : reachableNodes(graph, node)) { transitiveClosure.putEdge(node, reachableNode); for (N node : graph.nodes()) { if (!visitedNodes.contains(node)) { Set<N> reachableNodes = reachableNodes(graph, node);
/** * A graph is "forest-shaped" if it is directed, acyclic, and each node has at most one * predecessor. */ public static <N> boolean isForestShaped(Graph<N> graph) { checkNotNull(graph, "graph"); return graph.isDirected() && !Graphs.hasCycle(graph) && graph.nodes().stream().allMatch(node -> graph.predecessors(node).size() <= 1); }
/** Returns an immutable copy of {@code graph}. */ public static <N> ImmutableGraph<N> copyOf(Graph<N> graph) { return (graph instanceof ImmutableGraph) ? (ImmutableGraph<N>) graph : new ImmutableGraph<N>( new ConfigurableValueGraph<N, Presence>( GraphBuilder.from(graph), getNodeConnections(graph), graph.edges().size())); }
static void assertStronglyEquivalent(Graph<?> graphA, Graph<?> graphB) { // Properties not covered by equals() assertThat(graphA.allowsSelfLoops()).isEqualTo(graphB.allowsSelfLoops()); assertThat(graphA.nodeOrder()).isEqualTo(graphB.nodeOrder()); assertThat(graphA).isEqualTo(graphB); }
/** * Returns the weight of the edge from <code>v1</code> to <code>v2</code> plus the weight of the * edge from <code>v2</code> to <code>v1</code>; if either edge does not exist, it is treated as * an edge with weight 0. Undirected edges are treated as two antiparallel directed edges (that * is, if there is one undirected edge with weight <i>w</i> connecting <code>v1</code> to <code>v2 * </code>, the value returned is 2<i>w</i>). Ignores parallel edges; if there are any such, one * is chosen at random. Throws <code>NullPointerException</code> if either edge is present but not * assigned a weight by the constructor-specified <code>NumberEdgeValue</code>. * * @param v1 the first node of the pair whose property is being measured * @param v2 the second node of the pair whose property is being measured * @return the weights of the edges {@code<v1, v2>} and {@code <v2, v1>} */ protected double mutualWeight(N v1, N v2) { double weight = 0; if (g.isDirected()) { if (g.successors(v1).contains(v2)) { weight += edge_weight.apply(v1, v2).doubleValue(); } if (g.successors(v2).contains(v1)) { weight += edge_weight.apply(v2, v1).doubleValue(); } } else { if (g.adjacentNodes(v1).contains(v2)) { weight += edge_weight.apply(v1, v2).doubleValue(); } } return weight; }
private static <N> ImmutableMap<N, GraphConnections<N, Presence>> getNodeConnections( Graph<N> graph) { // ImmutableMap.Builder maintains the order of the elements as inserted, so the map will have // whatever ordering the graph's nodes do, so ImmutableSortedMap is unnecessary even if the // input nodes are sorted. ImmutableMap.Builder<N, GraphConnections<N, Presence>> nodeConnections = ImmutableMap.builder(); for (N node : graph.nodes()) { nodeConnections.put(node, connectionsOf(graph, node)); } return nodeConnections.build(); }
@Override public Set<N> successors(N node) { return delegate().predecessors(node); // transpose }
/** * Returns a view of {@code graph} with the direction (if any) of every edge reversed. All other * properties remain intact, and further updates to {@code graph} will be reflected in the view. */ public static <N> Graph<N> transpose(Graph<N> graph) { if (!graph.isDirected()) { return graph; // the transpose of an undirected graph is an identical graph } if (graph instanceof TransposedGraph) { return ((TransposedGraph<N>) graph).graph; } return new TransposedGraph<N>(graph); }
public static <N> ImmutableSet<N> roots(Graph<N> graph) { checkNotNull(graph, "graph"); return graph .nodes() .stream() .filter(node -> graph.predecessors(node).isEmpty()) .collect(toImmutableSet()); }