/** * Performs a traversal of the nodes reachable from {@code node}. If we ever reach a node we've * already visited (following only outgoing edges and without reusing edges), we know there's a * cycle in the graph. */ private static <N> boolean subgraphHasCycle( Graph<N> graph, Map<Object, NodeVisitState> visitedNodes, N node, @Nullable N previousNode) { NodeVisitState state = visitedNodes.get(node); if (state == NodeVisitState.COMPLETE) { return false; } if (state == NodeVisitState.PENDING) { return true; } visitedNodes.put(node, NodeVisitState.PENDING); for (N nextNode : graph.successors(node)) { if (canTraverseWithoutReusingEdge(graph, nextNode, previousNode) && subgraphHasCycle(graph, visitedNodes, nextNode, node)) { return true; } } visitedNodes.put(node, NodeVisitState.COMPLETE); return false; }
/** * Specifies the expected number of nodes in the graph. * * @throws IllegalArgumentException if {@code expectedNodeCount} is negative */ public ValueGraphBuilder<N, V> expectedNodeCount(int expectedNodeCount) { this.expectedNodeCount = Optional.of(checkNonNegative(expectedNodeCount)); return this; }
@Test public void copyOf_nullArgument() { try { copyOf((Graph<?>) null); fail("Should have rejected a null graph."); } catch (NullPointerException expected) { } }
@Override public void addInEdge(E edge, N node, boolean isSelfLoop) { if (isSelfLoop) { checkPositive(++selfLoopCount); } N previousNode = inEdgeMap.put(edge, node); checkState(previousNode == null); }
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); visitedNodes.addAll(reachableNodes); int pairwiseMatch = 1; // start at 1 to include self-loops
private static <N> void checkTransitiveClosure(Graph<N> originalGraph, Graph<N> expectedClosure) { for (N node : originalGraph.nodes()) { assertThat(reachableNodes(originalGraph, node)).isEqualTo(expectedClosure.successors(node)); } assertThat(transitiveClosure(originalGraph)).isEqualTo(expectedClosure); }
/** * 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; }
@Test public void copyOf_directedGraph() { Graph<Integer> directedGraph = buildDirectedGraph(); Graph<Integer> copy = copyOf(directedGraph); assertThat(copy).isEqualTo(directedGraph); }
@Override public void addPredecessor(N node, V unused) { Object previousValue = adjacentNodeValues.put(node, PRED); if (previousValue == null) { checkPositive(++predecessorCount); } else if (previousValue instanceof PredAndSucc) { // Restore previous PredAndSucc object. adjacentNodeValues.put(node, previousValue); } else if (previousValue != PRED) { // successor // Do NOT use method parameter value 'unused'. In directed graphs, successors store the value. adjacentNodeValues.put(node, new PredAndSucc(previousValue)); checkPositive(++predecessorCount); } }
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); visitedNodes.addAll(reachableNodes); int pairwiseMatch = 1; // start at 1 to include self-loops
private static <N> void checkTransitiveClosure(Graph<N> originalGraph, Graph<N> expectedClosure) { for (N node : originalGraph.nodes()) { assertThat(reachableNodes(originalGraph, node)).isEqualTo(expectedClosure.successors(node)); } assertThat(transitiveClosure(originalGraph)).isEqualTo(expectedClosure); }
/** * 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; }
@Test public void copyOf_undirectedGraph() { Graph<Integer> undirectedGraph = buildUndirectedGraph(); Graph<Integer> copy = copyOf(undirectedGraph); assertThat(copy).isEqualTo(undirectedGraph); }
/** * Specifies the expected number of nodes in the network. * * @throws IllegalArgumentException if {@code expectedNodeCount} is negative */ public NetworkBuilder<N, E> expectedNodeCount(int expectedNodeCount) { this.expectedNodeCount = Optional.of(checkNonNegative(expectedNodeCount)); return this; }
@Override public void addInEdge(E edge, N node, boolean isSelfLoop) { if (isSelfLoop) { checkPositive(++selfLoopCount); } N previousNode = inEdgeMap.put(edge, node); checkState(previousNode == null); }
/** * Performs a traversal of the nodes reachable from {@code node}. If we ever reach a node we've * already visited (following only outgoing edges and without reusing edges), we know there's a * cycle in the graph. */ private static <N> boolean subgraphHasCycle( Graph<N> graph, Map<Object, NodeVisitState> visitedNodes, N node, @NullableDecl N previousNode) { NodeVisitState state = visitedNodes.get(node); if (state == NodeVisitState.COMPLETE) { return false; } if (state == NodeVisitState.PENDING) { return true; } visitedNodes.put(node, NodeVisitState.PENDING); for (N nextNode : graph.successors(node)) { if (canTraverseWithoutReusingEdge(graph, nextNode, previousNode) && subgraphHasCycle(graph, visitedNodes, nextNode, node)) { return true; } } visitedNodes.put(node, NodeVisitState.COMPLETE); return false; }
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); visitedNodes.addAll(reachableNodes); int pairwiseMatch = 1; // start at 1 to include self-loops
/** * 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; }