diff --git a/.classpath b/.classpath new file mode 100644 index 000000000..e912f9f2b --- /dev/null +++ b/.classpath @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..118c59c78 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +.DS_Store +target/** +!target/dynamic-connectivity*.jar diff --git a/.project b/.project new file mode 100644 index 000000000..aee6738ae --- /dev/null +++ b/.project @@ -0,0 +1,23 @@ + + + DynamicConnectivity + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..552c39646 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,14 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 000000000..f897a7f1c --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/README.md b/README.md index 80934f520..8a334d363 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,49 @@ -# dynamic-connectivity -Data structure for dynamic connectivity in undirected graphs. The ConnGraph class supports adding and removing edges and determining whether two vertices are connected (whether there is a path between them) in polylogarithmic time. +# Description +This provides the `ConnGraph` class, which implements an undirected graph with +dynamic connectivity. It supports adding and removing edges and determining +whether two vertices are connected - whether there is a path between them. +Adding and removing edges take O(log2N) amortized time with high +probability, while checking whether two vertices are connected takes O(log N) +time with high probability. Check the source code to see the full API and the +Javadoc documentation. + +# Features +* Efficiently add and remove edges and determine whether vertices are connected. +* A vertex can appear in multiple graphs, with a different set of adjacent + vertices in each graph. +* `ConnGraph` supports arbitrary vertex augmentation. Given a vertex V, + `ConnGraph` can quickly report the result of combining the augmentations of + all of the vertices in the connected component containing V, using a combining + function provided to the constructor. For example, if a `ConnGraph` represents + a game map, then given the location of the player, we can quickly determine + the amount of gold the player can access, or the strongest monster that can + reach him. Retrieving the combined augmentation for a connected component + takes O(log N) time with high probability. +* Compatible with Java 6.0 and above. + +# Limitations +* `ConnGraph` does not directly support augmenting edges. However, this can be + accomplished by imputing each edge's augmentation to an adjacent vertex. For + example, if each edge contains a certain amount of gold, then we can augment + each vertex with the amount of gold in the adjacent edges. We can then + calculate the amount of gold in a connected component by retrieving the + component's augmentation and dividing by two. A more general approach would be + to store the edges adjacent to each vertex in an augmented self-balancing + binary search tree (see + [RedBlackNode](https://github.com/btrekkie/RedBlackNode)), and to use this to + assign an augmentation to each vertex. +* Careful attention has been paid to the asymptotic running time of each method. + However, beyond this, no special effort has been made to optimize performance. + +# Example usage +```java +ConnGraph graph = new ConnGraph(); +ConnVertex vertex1 = new ConnVertex(); +ConnVertex vertex2 = new ConnVertex(); +ConnVertex vertex3 = new ConnVertex(); +graph.addEdge(vertex1, vertex2); +graph.addEdge(vertex2, vertex3); +graph.connected(vertex1, vertex3); // Returns true +graph.removeEdge(vertex1, vertex2); +graph.connected(vertex1, vertex3); // Returns false +``` diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..604e31212 --- /dev/null +++ b/pom.xml @@ -0,0 +1,55 @@ + + 4.0.0 + com.github.btrekkie.connectivity + dynamic-connectivity + 0.1.0 + dynamic-connectivity + Data structure for dynamic connectivity in undirected graphs + + + + maven-assembly-plugin + + + package + + single + + + + + + jar-with-dependencies + + + + + maven-compiler-plugin + 3.8.0 + + 1.6 + 1.6 + + + + + + + jitpack.io + https://jitpack.io + + + + + com.github.btrekkie + RedBlackNode + 1.0.0 + + + junit + junit + 4.12 + test + + + \ No newline at end of file diff --git a/src/main/java/com/github/btrekkie/connectivity/Augmentation.java b/src/main/java/com/github/btrekkie/connectivity/Augmentation.java new file mode 100644 index 000000000..1d2a443c8 --- /dev/null +++ b/src/main/java/com/github/btrekkie/connectivity/Augmentation.java @@ -0,0 +1,39 @@ +package com.github.btrekkie.connectivity; + +/** + * A combining function for taking the augmentations associated with a set of ConnVertices and reducing them to a single + * result. The function is a binary operation for combining two values into one. For example, given vertices with + * augmentations A1, A2, A3, and A4, the combined result may be obtained by computing + * combine(combine(combine(A1, A2), A3), A4). In order for an augmentation result to be meaningful, the combining + * function must be commutative, meaning combine(x, y) is equivalent to combine(y, x), and associative, meaning + * combine(x, combine(y, z)) is equivalent to combine(combine(x, y), z). + * + * If a ConnGraph represents a game map, then one example of an augmentation would be the amount of gold accessible from + * a certain location. Each vertex would be augmented with the amount of gold in that location, and the combining + * function would add the two amounts of gold passed in as arguments. Another example would be the strongest monster + * that can reach a particular location. Each vertex with at least one monster would be augmented with a pointer to the + * strongest monster at that location, and the combining function would return the stronger of the two monsters passed + * in as arguments. A third example would be the number of locations accessible from a given vertex. Each vertex would + * be augmented with the number 1, and the combining function would add the two numbers of vertices passed in as + * arguments. + * + * ConnGraph treats two augmentation values X and Y as interchangeable if they are equal, as in + * X != null ? X.equals(Y) : Y == null. The same holds for two combined augmentation values, and for one combined + * augmentation value and one augmentation value. + * + * See the comments for ConnGraph. + */ +public interface Augmentation { + /** + * Returns the result of combining the specified values into one. Each argument is either the augmentation + * information associated with a vertex, or the result of a previous call to "combine". + * + * Note that a value of null is never passed in to indicate the absence of augmentation information. The fact that + * ConnGraph.getVertexAugmentation, for example, may return null when there is no associated augmentation might lead + * you to believe that a null argument indicates the absence of augmentation information, but again, it does not. A + * null argument can only mean that a vertex is explicitly associated with null augmentation information, due to a + * prior call to ConnGraph.setVertexAugmentation(vertex, null), or that the "combine" method previously returned + * null. + */ + public Object combine(Object value1, Object value2); +} diff --git a/src/main/java/com/github/btrekkie/connectivity/ConnEdge.java b/src/main/java/com/github/btrekkie/connectivity/ConnEdge.java new file mode 100644 index 000000000..691aa6ed7 --- /dev/null +++ b/src/main/java/com/github/btrekkie/connectivity/ConnEdge.java @@ -0,0 +1,61 @@ +package com.github.btrekkie.connectivity; + +/** + * Represents an edge in a ConnGraph, at the level of the edge (i.e. at the lowest level i for which G_i contains the + * edge). Every graph edge has exactly one corresponding ConnEdge object, regardless of the number of levels it appears + * in. See the comments for the implementation of ConnGraph. + * + * ConnEdges are stored in the linked lists suggested by EulerTourVertex.graphListHead and + * EulerTourVertex.forestListHead. Each ConnEdge is in two linked lists, so care must be taken when traversing the + * linked lists. prev1 and next1 are the links for the list starting at vertex1.graphListHead or vertex1.forestListHead, + * while prev2 and next2 are the links for vertex2. But the vertex1 and vertex2 fields of a given edge are different + * from the vertex1 and vertex2 fields of the linked edges. For example, the edge after next1 is not necessarily + * next1.next1. It depends on whether next1.vertex1 is the same as vertex1. If next1.vertex1 == vertex1, then the edge + * after next1 is next1.next1, but otherwise, it is next1.next2. + */ +class ConnEdge { + /** The edge's first endpoint (at the same level as the edge). */ + public EulerTourVertex vertex1; + + /** The edge's second endpoint (at the same level as the edge). */ + public EulerTourVertex vertex2; + + /** + * The EulerTourEdge object describing the edge's presence in an Euler tour tree, at the same level as the edge, or + * null if the edge is not in the Euler tour forest F_i. + */ + public EulerTourEdge eulerTourEdge; + + /** + * The edge preceding this in a linked list of same-level edges adjacent to vertex1, if any. The edge is either part + * of a list of non-forest edges starting with vertex1.graphListHead, or part of a list of forest edges starting + * with vertex1.forestListHead. Note that this list excludes any edges that also appear in lower levels. + */ + public ConnEdge prev1; + + /** + * The edge succeeding this in a linked list of same-level edges adjacent to vertex1, if any. The edge is either + * part of a list of non-forest edges starting with vertex1.graphListHead, or part of a list of forest edges + * starting with vertex1.forestListHead. Note that this list excludes any edges that also appear in lower levels. + */ + public ConnEdge next1; + + /** + * The edge preceding this in a linked list of same-level edges adjacent to vertex2, if any. The edge is either part + * of a list of non-forest edges starting with vertex2.graphListHead, or part of a list of forest edges starting + * with vertex2.forestListHead. Note that this list excludes any edges that also appear in lower levels. + */ + public ConnEdge prev2; + + /** + * The edge succeeding this in a linked list of same-level edges adjacent to vertex2, if any. The edge is either + * part of a list of non-forest edges starting with vertex2.graphListHead, or part of a list of forest edges + * starting with vertex2.forestListHead. Note that this list excludes any edges that also appear in lower levels. + */ + public ConnEdge next2; + + public ConnEdge(EulerTourVertex vertex1, EulerTourVertex vertex2) { + this.vertex1 = vertex1; + this.vertex2 = vertex2; + } +} diff --git a/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java b/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java new file mode 100644 index 000000000..4f16deb76 --- /dev/null +++ b/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java @@ -0,0 +1,1183 @@ +package com.github.btrekkie.connectivity; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Implements an undirected graph with dynamic connectivity. It supports adding and removing edges and determining + * whether two vertices are connected - whether there is a path between them. Adding and removing edges take O(log^2 N) + * amortized time with high probability, while checking whether two vertices are connected takes O(log N) time with high + * probability. It uses O(V log V + E) space, where V is the number of vertices and E is the number of edges. Note that + * a ConnVertex may appear in multiple ConnGraphs, with a different set of adjacent vertices in each graph. + * + * ConnGraph optionally supports arbitrary augmentation. Each vertex may have an associated augmentation, or value. + * Given a vertex V, ConnGraph can quickly report the result of combining the augmentations of all of the vertices in + * the connected component containing V, using a combining function provided to the constructor. For example, if a + * ConnGraph represents a game map, then given the location of the player, we can quickly determine the amount of gold + * the player can access, or the strongest monster that can reach him. Augmentation does not affect the running time or + * space of ConnGraph in terms of big O notation, assuming the augmentation function takes a constant amount of time and + * the augmentation takes a constant amount of space. Retrieving the combined augmentation for a connected component + * takes O(log N) time with high probability. (Although ConnGraph does not directly support augmenting edges, this can + * also be accomplished, by imputing each edge's augmentation to an adjacent vertex.) + * + * When a vertex no longer has any adjacent edges, and it has no augmentation information, ConnGraph stops keeping track + * of the vertex. This reduces the time and space bounds of the ConnGraph, and it enables the ConnVertex to be garbage + * collected. If you know you are finished with a vertex, and that vertex has an augmentation, then you should call + * removeVertexAugmentation on the vertex, so that the graph can release it. + * + * As a side note, it would be more proper if ConnGraph had a generic type parameter indicating the type of the + * augmentation values. However, it is expected that it is more common not to use augmentation, so by not using a type + * parameter, we make usage of the ConnGraph class more convenient and less confusing in the common case. + */ +/* ConnGraph is implemented using a data structure described in + * http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.89.919&rep=rep1&type=pdf (Holm, de Lichtenberg, and Thorup + * (1998): Poly-Logarithmic Deterministic Fully-Dynamic Algorithms for Connectivity, Minimum Spanning Tree, 2-Edge, and + * Biconnectivity). However, ConnGraph does not include the optimization of using a B-tree in the top level, so queries + * take O(log N) time rather than O(log N / log log N) time. + * + * This implementation is actually based on a slightly modified description of the data structure given in + * http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-851-advanced-data-structures-spring-2012/lecture-videos/session-20-dynamic-graphs-ii/ . + * The description in the video differs from the data structure in the paper in that the levels are numbered in reverse + * order, the constraint on tree sizes is different, and the augmentation uses booleans in place of edges. In addition, + * the video defines subgraphs G_i. The change in the constraint on tree sizes is beneficial because it makes it easier + * to delete vertices. + * + * Note that the data structure described in the video is faulty. In the procedure for deleting an edge, it directs us + * to push down some edges. When we push an edge from level i to level i - 1, we would need to add the edge to + * F_{i - 1}, if the endpoints were not already connected in G_{i - 1}. However, this would violate the invariant that + * F_i be a superset of F_{i - 1}. To fix this problem, before searching for a replacement edge in level i, ConnGraph + * first pushes all level-i edges in the relevant tree down to level i - 1 and adds them to F_{i - 1}, as in the + * original paper. That way, when we subsequently push down edges, we can safely add them to G_{i - 1} without also + * adding them to F_{i - 1}. In order to do this efficiently, each vertex stores a second adjacency list, consisting of + * the level-i edges that are in F_i. In addition, we augment each Euler tour tree node with an a second boolean, + * indicating whether the subtree rooted at the node contains a canonical visit to a vertex with at least one level-i + * edge that is in F_i. + * + * The implementation of rerooting an Euler tour tree described in the video lecture appears to be incorrect as well. It + * breaks the references to the vertices' first and last visits. To fix this, we do not store references to the + * vertices' first and last visits. Instead, we have each vertex store a reference to an arbitrary visit to that vertex. + * We also maintain edge objects for each of the edges in the Euler tours. Each such edge stores a pointer to the two + * visits that precede the traversal of the edge in the Euler tour. These do not change when we perform a reroot. The + * remove edge operation then requires a pointer to the edge object, rather than a pointer to the vertices. Given the + * edge object, we can splice out the range of nodes between the two visits that precede the edge. + * + * Rather than explicitly giving each edge a level number, the level numbers are implicit through links from each level + * to the level below it. For purposes of analysis, the level number of the top level is equal to + * maxLogVertexCountSinceRebuild, the ceiling of log base 2 of the maximum number of vertices in the graph since the + * last rebuild operation. Once the ratio between the maximum number of vertices since the last rebuild and the current + * number of vertices becomes large enough, we rebuild the data structure. This ensures that the level number of the top + * level is O(log V). + * + * Most methods' time bounds are probabilistic. For example, "connected" takes O(log N) time with high probability. The + * reason they are probabilistic is that they involve hash lookups, using the vertexInfo and VertexInfo.edges hash maps. + * Given that each ConnVertex has a random hash code, it is easy to demonstrate that lookups take O(1) expected time. + * Furthermore, I claim that they take O(log N / log log N) time with high probability. This claim is sufficient to + * establish that all time bounds that are at least O(log N / log log N) if we exclude the hash lookup can be sustained + * if we add the qualifier "with high probability." + * + * This claim is based on information presented in + * https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-851-advanced-data-structures-spring-2012/lecture-videos/session-10-dictionaries/ . + * According to that video, in a hash map with chaining, if the hash function is totally random, then the longest chain + * length is O(log N / log log N) with high probability. A totally random hash function is a slightly different concept + * than having ConnVertex.hashCode() return a random value, due to the particular definition of "hash function" used in + * the video. Nevertheless, the analysis is the same. A random hashCode() implementation ultimately results in + * independently hashing each entry to a random bucket, which is equivalent to a totally random hash function. + * + * However, the claim depends on certain features of the implementation of HashMap, gleaned from reading the source + * code. In particular, it assumes that HashMap resolves collisions using chaining. (Newer versions of Java sometimes + * store the entries that hash to the same bucket in binary search trees rather than linked lists, but this can't hurt + * the asymptotic performance.) Note that the implementation of HashMap transforms the return value of hashCode(), by + * "spreading" the higher-order bits to lower-order positions. However, this transform is a permutation of the integers. + * If the input to a transform is selected uniformly at random, and the transform is a permutation, than the output also + * has a uniform random distribution. + */ +public class ConnGraph { + /** + * The difference between ceiling of log base 2 of the maximum number of vertices in the graph since the last call + * to rebuild() or clear() and ceiling of log base 2 of the current number of vertices, at or above which we call + * rebuild(). (There is special handling for 0 vertices.) + */ + private static final int REBUILD_CHANGE = 2; + + /** + * The maximum number of vertices we can store in a ConnGraph. This is limited by the fact that EulerTourNode.size + * is an int. Since the size of an Euler tour tree is one less than twice the number of vertices in the tree, the + * number of vertices may be at most (int)((((long)Integer.MAX_VALUE) + 1) / 2). + * + * Of course, we could simply change the "size" field to be a long. But more fundamentally, the number of vertices + * is limited by the fact that vertexInfo and VertexInfo.edges use HashMaps. Using a HashMap becomes problematic at + * around Integer.MAX_VALUE entries. HashMap buckets entries based on 32-bit hash codes, so in principle, it can + * only hash the entries to at most 2^32 buckets. In order to support a significantly greater limit on the number of + * vertices, we would need to use a more elaborate mapping approach. + */ + private static final int MAX_VERTEX_COUNT = 1 << 30; + + /** The augmentation function for the graph, if any. */ + private final Augmentation augmentation; + + /** + * A map from each vertex in this graph to information about the vertex in this graph. If a vertex has no adjacent + * edges and no associated augmentation, we remove it from vertexInfo, to save time and space. Lookups take O(1) + * expected time and O(log N / log log N) time with high probability, because vertexInfo is a HashMap, and + * ConnVertex.hashCode() returns a random integer. + */ + private Map vertexInfo = new HashMap(); + + /** + * Ceiling of log base 2 of the maximum number of vertices in this graph since the last rebuild. This is 0 if that + * number is 0. + */ + private int maxLogVertexCountSinceRebuild; + + /** + * The maximum number of entries in vertexInfo since the last time we copied that field to a new HashMap. We do this + * when the number of vertices drops sufficiently, in order to limit space usage. (The capacity of a HashMap is not + * automatically reduced as the number of entries decreases, so we have to limit space usage manually.) + */ + private int maxVertexInfoSize; + + /** Constructs a new ConnGraph with no augmentation. */ + public ConnGraph() { + augmentation = null; + } + + /** Constructs an augmented ConnGraph, using the specified function to combine augmentation values. */ + public ConnGraph(Augmentation augmentation) { + this.augmentation = augmentation; + } + + /** Equivalent implementation is contractual. */ + private void assertIsAugmented() { + if (augmentation == null) { + throw new RuntimeException( + "You may only call augmentation-related methods on ConnGraph if the graph is augmented, i.e. if an " + + "Augmentation was passed to the constructor"); + } + } + + /** + * Returns the VertexInfo containing information about the specified vertex in this graph. If the vertex is not in + * this graph (i.e. it does not have an entry in vertexInfo), this method adds it to the graph, and creates a + * VertexInfo object for it. + */ + private VertexInfo ensureInfo(ConnVertex vertex) { + VertexInfo info = vertexInfo.get(vertex); + if (info != null) { + return info; + } + + if (vertexInfo.size() == MAX_VERTEX_COUNT) { + throw new RuntimeException( + "Sorry, ConnGraph has too many vertices to perform this operation. ConnGraph does not support " + + "storing more than ~2^30 vertices at a time."); + } + + EulerTourVertex eulerTourVertex = new EulerTourVertex(); + EulerTourNode node = new EulerTourNode(eulerTourVertex, augmentation); + eulerTourVertex.arbitraryVisit = node; + node.left = EulerTourNode.LEAF; + node.right = EulerTourNode.LEAF; + node.augment(); + + info = new VertexInfo(eulerTourVertex); + vertexInfo.put(vertex, info); + if (vertexInfo.size() > 1 << maxLogVertexCountSinceRebuild) { + maxLogVertexCountSinceRebuild++; + } + maxVertexInfoSize = Math.max(maxVertexInfoSize, vertexInfo.size()); + return info; + } + + /** + * Takes the specified vertex out of this graph. We should call this method as soon as a vertex does not have any + * adjacent edges and does not have any augmentation information. This method assumes that the vertex is currently + * in the graph. + */ + private void remove(ConnVertex vertex) { + vertexInfo.remove(vertex); + if (4 * vertexInfo.size() <= maxVertexInfoSize && maxVertexInfoSize > 12) { + // The capacity of a HashMap is not automatically reduced as the number of entries decreases. To avoid + // violating our O(V log V + E) space guarantee, we copy vertexInfo to a new HashMap, which will have a + // suitable capacity. + vertexInfo = new HashMap(vertexInfo); + maxVertexInfoSize = vertexInfo.size(); + } + if (vertexInfo.size() << REBUILD_CHANGE <= 1 << maxLogVertexCountSinceRebuild) { + rebuild(); + } + } + + /** + * Collapses an adjacency list (either graphListHead or forestListHead) for an EulerTourVertex into the adjacency + * list for an EulerTourVertex that represents the same underlying ConnVertex, but at a higher level. This has the + * effect of prepending the list for the lower level to the beginning of the list for the higher level, and + * replacing all links to the lower-level vertex in the ConnEdges with links to the higher-level vertex. + * @param head The first node in the list for the higher-level vertex. + * @param lowerHead The first node in the list for the lower-level vertex. + * @param vertex The higher-level vertex. + * @param lowerVertex The lower-level vertex. + * @return The head of the combined linked list. + */ + private ConnEdge collapseEdgeList( + ConnEdge head, ConnEdge lowerHead, EulerTourVertex vertex, EulerTourVertex lowerVertex) { + if (lowerHead == null) { + return head; + } + + ConnEdge prevLowerEdge = null; + ConnEdge lowerEdge = lowerHead; + while (lowerEdge != null) { + prevLowerEdge = lowerEdge; + if (lowerEdge.vertex1 == lowerVertex) { + lowerEdge.vertex1 = vertex; + lowerEdge = lowerEdge.next1; + } else { + lowerEdge.vertex2 = vertex; + lowerEdge = lowerEdge.next2; + } + } + + if (prevLowerEdge.vertex1 == vertex) { + prevLowerEdge.next1 = head; + } else { + prevLowerEdge.next2 = head; + } + if (head != null) { + if (head.vertex1 == vertex) { + head.prev1 = prevLowerEdge; + } else { + head.prev2 = prevLowerEdge; + } + } + return lowerHead; + } + + /** + * Equivalent implementation is contractual. + * + * This method is useful for when a node's lists (graphListHead or forestListHead) or a vertex's arbitrary visit + * change, as these affect the hasGraphEdge and hasForestEdge augmentations. + */ + private void augmentAncestorFlags(EulerTourNode node) { + for (EulerTourNode parent = node; parent != null; parent = parent.parent) { + if (!parent.augmentFlags()) { + break; + } + } + } + + /** + * Rebuilds the data structure so that the number of levels is at most the ceiling of log base 2 of the number of + * vertices in the graph (or zero in the case of zero vertices). The current implementation of rebuild() takes + * O(V + E) time, assuming a constant difference between maxLogVertexCountSinceRebuild and the result of the + * logarithm. + */ + private void rebuild() { + // Rebuild the graph by collapsing the top deleteCount + 1 levels into the top level + + if (vertexInfo.isEmpty()) { + maxLogVertexCountSinceRebuild = 0; + return; + } + int deleteCount = 0; + while (2 * vertexInfo.size() <= 1 << maxLogVertexCountSinceRebuild) { + maxLogVertexCountSinceRebuild--; + deleteCount++; + } + if (deleteCount == 0) { + return; + } + + for (VertexInfo info : vertexInfo.values()) { + EulerTourVertex vertex = info.vertex; + EulerTourVertex lowerVertex = vertex; + for (int i = 0; i < deleteCount; i++) { + lowerVertex = lowerVertex.lowerVertex; + if (lowerVertex == null) { + break; + } + + vertex.graphListHead = + collapseEdgeList(vertex.graphListHead, lowerVertex.graphListHead, vertex, lowerVertex); + if (lowerVertex.forestListHead != null) { + // Change the eulerTourEdge links + ConnEdge lowerEdge = lowerVertex.forestListHead; + while (lowerEdge != null) { + if (lowerEdge.vertex1 == lowerVertex) { + // We'll address this edge when we visit lowerEdge.vertex2 + lowerEdge = lowerEdge.next1; + } else { + EulerTourEdge edge = lowerEdge.eulerTourEdge.higherEdge; + for (int j = 0; j < i; j++) { + edge = edge.higherEdge; + } + lowerEdge.eulerTourEdge = edge; + lowerEdge = lowerEdge.next2; + } + } + + vertex.forestListHead = + collapseEdgeList(vertex.forestListHead, lowerVertex.forestListHead, vertex, lowerVertex); + } + } + + if (lowerVertex != null) { + lowerVertex = lowerVertex.lowerVertex; + } + vertex.lowerVertex = lowerVertex; + if (lowerVertex != null) { + lowerVertex.higherVertex = vertex; + } + augmentAncestorFlags(vertex.arbitraryVisit); + } + } + + /** + * Adds the specified edge to the graph adjacency list of edge.vertex1, as in EulerTourVertex.graphListHead. + * Assumes it is not currently in any lists, except possibly the graph adjacency list of edge.vertex2. + */ + private void addToGraphLinkedList1(ConnEdge edge) { + edge.prev1 = null; + edge.next1 = edge.vertex1.graphListHead; + if (edge.next1 != null) { + if (edge.next1.vertex1 == edge.vertex1) { + edge.next1.prev1 = edge; + } else { + edge.next1.prev2 = edge; + } + } + edge.vertex1.graphListHead = edge; + } + + /** + * Adds the specified edge to the graph adjacency list of edge.vertex2, as in EulerTourVertex.graphListHead. + * Assumes it is not currently in any lists, except possibly the graph adjacency list of edge.vertex1. + */ + private void addToGraphLinkedList2(ConnEdge edge) { + edge.prev2 = null; + edge.next2 = edge.vertex2.graphListHead; + if (edge.next2 != null) { + if (edge.next2.vertex1 == edge.vertex2) { + edge.next2.prev1 = edge; + } else { + edge.next2.prev2 = edge; + } + } + edge.vertex2.graphListHead = edge; + } + + /** + * Adds the specified edge to the graph adjacency lists of edge.vertex1 and edge.vertex2, as in + * EulerTourVertex.graphListHead. Assumes it is not currently in any lists. + */ + private void addToGraphLinkedLists(ConnEdge edge) { + addToGraphLinkedList1(edge); + addToGraphLinkedList2(edge); + } + + /** + * Adds the specified edge to the forest adjacency lists of edge.vertex1 and edge.vertex2, as in + * EulerTourVertex.forestListHead. Assumes it is not currently in any lists. + */ + private void addToForestLinkedLists(ConnEdge edge) { + edge.prev1 = null; + edge.next1 = edge.vertex1.forestListHead; + if (edge.next1 != null) { + if (edge.next1.vertex1 == edge.vertex1) { + edge.next1.prev1 = edge; + } else { + edge.next1.prev2 = edge; + } + } + edge.vertex1.forestListHead = edge; + + edge.prev2 = null; + edge.next2 = edge.vertex2.forestListHead; + if (edge.next2 != null) { + if (edge.next2.vertex1 == edge.vertex2) { + edge.next2.prev1 = edge; + } else { + edge.next2.prev2 = edge; + } + } + edge.vertex2.forestListHead = edge; + } + + /** + * Removes the specified edge from an adjacency list of edge.vertex1, as in graphListHead and forestListHead. + * Assumes it is initially in exactly one of the lists for edge.vertex1. + */ + private void removeFromLinkedList1(ConnEdge edge) { + if (edge.prev1 != null) { + if (edge.prev1.vertex1 == edge.vertex1) { + edge.prev1.next1 = edge.next1; + } else { + edge.prev1.next2 = edge.next1; + } + } else if (edge == edge.vertex1.graphListHead) { + edge.vertex1.graphListHead = edge.next1; + } else { + edge.vertex1.forestListHead = edge.next1; + } + if (edge.next1 != null) { + if (edge.next1.vertex1 == edge.vertex1) { + edge.next1.prev1 = edge.prev1; + } else { + edge.next1.prev2 = edge.prev1; + } + } + } + + /** + * Removes the specified edge from an adjacency list of edge.vertex2, as in graphListHead and forestListHead. + * Assumes it is initially in exactly one of the lists for edge.vertex2. + */ + private void removeFromLinkedList2(ConnEdge edge) { + if (edge.prev2 != null) { + if (edge.prev2.vertex1 == edge.vertex2) { + edge.prev2.next1 = edge.next2; + } else { + edge.prev2.next2 = edge.next2; + } + } else if (edge == edge.vertex2.graphListHead) { + edge.vertex2.graphListHead = edge.next2; + } else { + edge.vertex2.forestListHead = edge.next2; + } + if (edge.next2 != null) { + if (edge.next2.vertex1 == edge.vertex2) { + edge.next2.prev1 = edge.prev2; + } else { + edge.next2.prev2 = edge.prev2; + } + } + } + + /** + * Removes the specified edge from the adjacency lists of edge.vertex1 and edge.vertex2, as in graphListHead and + * forestListHead. Assumes it is initially in exactly one of the lists for edge.vertex1 and exactly one of the lists + * for edge.vertex2. + */ + private void removeFromLinkedLists(ConnEdge edge) { + removeFromLinkedList1(edge); + removeFromLinkedList2(edge); + } + + /** + * Add an edge between the specified vertices to the Euler tour forest F_i. Assumes that the edge's endpoints are + * initially in separate trees. Returns the created edge. + */ + private EulerTourEdge addForestEdge(EulerTourVertex vertex1, EulerTourVertex vertex2) { + // We need to be careful about where we split and where we add and remove nodes, so as to avoid breaking any + // EulerTourEdge.visit* fields + EulerTourNode root = vertex2.arbitraryVisit.root(); + EulerTourNode max = root.max(); + if (max.vertex != vertex2) { + // Reroot + EulerTourNode min = root.min(); + if (max.vertex.arbitraryVisit == max) { + max.vertex.arbitraryVisit = min; + augmentAncestorFlags(min); + augmentAncestorFlags(max); + } + root = max.remove(); + EulerTourNode[] splitRoots = root.split(vertex2.arbitraryVisit); + root = splitRoots[1].concatenate(splitRoots[0]); + EulerTourNode newNode = new EulerTourNode(vertex2, root.augmentationFunc); + newNode.left = EulerTourNode.LEAF; + newNode.right = EulerTourNode.LEAF; + newNode.isRed = true; + EulerTourNode parent = root.max(); + parent.right = newNode; + newNode.parent = parent; + root = newNode.fixInsertion(); + max = newNode; + } + + EulerTourNode[] splitRoots = vertex1.arbitraryVisit.root().split(vertex1.arbitraryVisit); + EulerTourNode before = splitRoots[0]; + EulerTourNode after = splitRoots[1]; + EulerTourNode newNode = new EulerTourNode(vertex1, root.augmentationFunc); + before.concatenate(root, newNode).concatenate(after); + return new EulerTourEdge(newNode, max); + } + + /** + * Adds the specified edge to the edge map for srcInfo (srcInfo.edges). Assumes that the edge is not currently in + * the map. + * @param edge The edge. + * @param srcInfo The source vertex's info. + * @param destVertex The destination vertex, i.e. the edge's key in srcInfo.edges. + */ + private void addToEdgeMap(ConnEdge edge, VertexInfo srcInfo, ConnVertex destVertex) { + srcInfo.edges.put(destVertex, edge); + if (srcInfo.edges.size() > srcInfo.maxEdgeCountSinceRebuild) { + srcInfo.maxEdgeCountSinceRebuild = srcInfo.edges.size(); + } + } + + /** + * Adds an edge between the specified vertices, if such an edge is not already present. Taken together with + * removeEdge, this method takes O(log^2 N) amortized time with high probability. + * @return Whether there was no edge between the vertices. + */ + public boolean addEdge(ConnVertex connVertex1, ConnVertex connVertex2) { + if (connVertex1 == connVertex2) { + throw new IllegalArgumentException("Self-loops are not allowed"); + } + if (vertexInfo.size() >= MAX_VERTEX_COUNT - 1) { + throw new RuntimeException( + "Sorry, ConnGraph has too many vertices to perform this operation. ConnGraph does not support " + + "storing more than ~2^30 vertices at a time."); + } + VertexInfo info1 = ensureInfo(connVertex1); + if (info1.edges.containsKey(connVertex2)) { + return false; + } + VertexInfo info2 = ensureInfo(connVertex2); + + EulerTourVertex vertex1 = info1.vertex; + EulerTourVertex vertex2 = info2.vertex; + ConnEdge edge = new ConnEdge(vertex1, vertex2); + + if (vertex1.arbitraryVisit.root() == vertex2.arbitraryVisit.root()) { + addToGraphLinkedLists(edge); + } else { + addToForestLinkedLists(edge); + edge.eulerTourEdge = addForestEdge(vertex1, vertex2); + } + augmentAncestorFlags(vertex1.arbitraryVisit); + augmentAncestorFlags(vertex2.arbitraryVisit); + + addToEdgeMap(edge, info1, connVertex2); + addToEdgeMap(edge, info2, connVertex1); + return true; + } + + /** + * Returns vertex.lowerVertex. If this is null, ensureLowerVertex sets vertex.lowerVertex to a new vertex and + * returns it. + */ + private EulerTourVertex ensureLowerVertex(EulerTourVertex vertex) { + EulerTourVertex lowerVertex = vertex.lowerVertex; + if (lowerVertex == null) { + lowerVertex = new EulerTourVertex(); + EulerTourNode lowerNode = new EulerTourNode(lowerVertex, null); + lowerVertex.arbitraryVisit = lowerNode; + vertex.lowerVertex = lowerVertex; + lowerVertex.higherVertex = vertex; + + lowerNode.left = EulerTourNode.LEAF; + lowerNode.right = EulerTourNode.LEAF; + lowerNode.augment(); + } + return lowerVertex; + } + + /** + * Pushes all level-i forest edges in the tree rooted at the specified node down to level i - 1, and adds them to + * F_{i - 1}, where i is the level of the tree. + */ + private void pushForestEdges(EulerTourNode root) { + // Iterate over all of the nodes that have hasForestEdge == true + if (!root.hasForestEdge || root.size == 1) { + return; + } + EulerTourNode node; + for (node = root; node.left.hasForestEdge; node = node.left); + while (node != null) { + EulerTourVertex vertex = node.vertex; + ConnEdge edge = vertex.forestListHead; + if (edge != null) { + EulerTourVertex lowerVertex = ensureLowerVertex(vertex); + ConnEdge prevEdge = null; + while (edge != null) { + if (edge.vertex2 == vertex || edge.vertex2 == lowerVertex) { + // We address this edge when we visit edge.vertex1 + prevEdge = edge; + edge = edge.next2; + } else { + edge.vertex1 = lowerVertex; + edge.vertex2 = ensureLowerVertex(edge.vertex2); + EulerTourEdge lowerEdge = addForestEdge(edge.vertex1, edge.vertex2); + lowerEdge.higherEdge = edge.eulerTourEdge; + edge.eulerTourEdge = lowerEdge; + prevEdge = edge; + edge = edge.next1; + } + } + + // Prepend vertex.forestListHead to the beginning of lowerVertex.forestListHead + if (prevEdge.vertex1 == lowerVertex) { + prevEdge.next1 = lowerVertex.forestListHead; + } else { + prevEdge.next2 = lowerVertex.forestListHead; + } + if (lowerVertex.forestListHead != null) { + if (lowerVertex.forestListHead.vertex1 == lowerVertex) { + lowerVertex.forestListHead.prev1 = prevEdge; + } else { + lowerVertex.forestListHead.prev2 = prevEdge; + } + } + lowerVertex.forestListHead = vertex.forestListHead; + vertex.forestListHead = null; + augmentAncestorFlags(lowerVertex.arbitraryVisit); + } + + // Iterate to the next node with hasForestEdge == true, clearing hasForestEdge as we go + if (node.right.hasForestEdge) { + for (node = node.right; node.left.hasForestEdge; node = node.left); + } else { + node.hasForestEdge = false; + while (node.parent != null && node.parent.right == node) { + node = node.parent; + node.hasForestEdge = false; + } + node = node.parent; + } + } + } + + /** + * Searches for a level-i edge connecting a vertex in the tree rooted at the specified node to a vertex in another + * tree, where i is the level of the tree. This is a "replacement" edge because it replaces the edge that was + * previously connecting the two trees. We push any level-i edges we encounter that do not connect to another tree + * down to level i - 1, adding them to G_{i - 1}. This method assumes that root.hasForestEdge is false. + * @param root The root of the tree. + * @return The replacement edge, or null if there is no replacement edge. + */ + private ConnEdge findReplacementEdge(EulerTourNode root) { + // Iterate over all of the nodes that have hasGraphEdge == true + if (!root.hasGraphEdge) { + return null; + } + EulerTourNode node; + for (node = root; node.left.hasGraphEdge; node = node.left); + while (node != null) { + EulerTourVertex vertex = node.vertex; + ConnEdge edge = vertex.graphListHead; + if (edge != null) { + ConnEdge replacementEdge = null; + ConnEdge prevEdge = null; + while (edge != null) { + EulerTourVertex adjVertex; + ConnEdge nextEdge; + if (edge.vertex1 == vertex) { + adjVertex = edge.vertex2; + nextEdge = edge.next1; + } else { + adjVertex = edge.vertex1; + nextEdge = edge.next2; + } + + if (adjVertex.arbitraryVisit.root() != root) { + replacementEdge = edge; + break; + } + + // Remove the edge from the adjacency list of adjVertex. We will remove it from the adjacency list + // of "vertex" later. + if (edge.vertex1 == adjVertex) { + removeFromLinkedList1(edge); + } else { + removeFromLinkedList2(edge); + } + augmentAncestorFlags(adjVertex.arbitraryVisit); + + // Push the edge down to level i - 1 + edge.vertex1 = ensureLowerVertex(edge.vertex1); + edge.vertex2 = ensureLowerVertex(edge.vertex2); + + // Add the edge to the adjacency list of adjVertex.lowerVertex. We will add it to the adjacency list + // of lowerVertex later. + if (edge.vertex1 != vertex.lowerVertex) { + addToGraphLinkedList1(edge); + } else { + addToGraphLinkedList2(edge); + } + augmentAncestorFlags(adjVertex.lowerVertex.arbitraryVisit); + + prevEdge = edge; + edge = nextEdge; + } + + // Prepend the linked list up to prevEdge to the beginning of vertex.lowerVertex.graphListHead + if (prevEdge != null) { + EulerTourVertex lowerVertex = vertex.lowerVertex; + if (prevEdge.vertex1 == lowerVertex) { + prevEdge.next1 = lowerVertex.graphListHead; + } else { + prevEdge.next2 = lowerVertex.graphListHead; + } + if (lowerVertex.graphListHead != null) { + if (lowerVertex.graphListHead.vertex1 == lowerVertex) { + lowerVertex.graphListHead.prev1 = prevEdge; + } else { + lowerVertex.graphListHead.prev2 = prevEdge; + } + } + lowerVertex.graphListHead = vertex.graphListHead; + augmentAncestorFlags(lowerVertex.arbitraryVisit); + } + vertex.graphListHead = edge; + if (edge == null) { + augmentAncestorFlags(vertex.arbitraryVisit); + } else if (edge.vertex1 == vertex) { + edge.prev1 = null; + } else { + edge.prev2 = null; + } + + if (replacementEdge != null) { + return replacementEdge; + } + } + + // Iterate to the next node with hasGraphEdge == true. Note that nodes' hasGraphEdge fields can change as we + // push down edges. + if (node.right.hasGraphEdge) { + for (node = node.right; node.left.hasGraphEdge; node = node.left); + } else { + while (node.parent != null && (node.parent.right == node || !node.parent.hasGraphEdge)) { + node = node.parent; + } + node = node.parent; + } + } + return null; + } + + /** + * Removes the edge from srcInfo to destVertex from the edge map for srcInfo (srcInfo.edges), if it is present. + * Returns the edge that we removed, if any. + */ + private ConnEdge removeFromEdgeMap(VertexInfo srcInfo, ConnVertex destVertex) { + ConnEdge edge = srcInfo.edges.remove(destVertex); + if (edge != null && 4 * srcInfo.edges.size() <= srcInfo.maxEdgeCountSinceRebuild && + srcInfo.maxEdgeCountSinceRebuild > 6) { + // The capacity of a HashMap is not automatically reduced as the number of entries decreases. To avoid + // violating our O(V log V + E) space guarantee, we copy srcInfo.edges to a new HashMap, which will have a + // suitable capacity. + srcInfo.edges = new HashMap(srcInfo.edges); + srcInfo.maxEdgeCountSinceRebuild = srcInfo.edges.size(); + } + return edge; + } + + /** + * Removes the edge between the specified vertices, if there is such an edge. Taken together with addEdge, this + * method takes O(log^2 N) amortized time with high probability. + * @return Whether there was an edge between the vertices. + */ + public boolean removeEdge(ConnVertex vertex1, ConnVertex vertex2) { + if (vertex1 == vertex2) { + throw new IllegalArgumentException("Self-loops are not allowed"); + } + + VertexInfo info1 = vertexInfo.get(vertex1); + if (info1 == null) { + return false; + } + ConnEdge edge = removeFromEdgeMap(info1, vertex2); + if (edge == null) { + return false; + } + VertexInfo info2 = vertexInfo.get(vertex2); + removeFromEdgeMap(info2, vertex1); + + removeFromLinkedLists(edge); + augmentAncestorFlags(edge.vertex1.arbitraryVisit); + augmentAncestorFlags(edge.vertex2.arbitraryVisit); + + if (edge.eulerTourEdge != null) { + // Remove the edge from all of the Euler tour trees that contain it + for (EulerTourEdge levelEdge = edge.eulerTourEdge; levelEdge != null; levelEdge = levelEdge.higherEdge) { + EulerTourNode firstNode; + EulerTourNode secondNode; + if (levelEdge.visit1.compareTo(levelEdge.visit2) < 0) { + firstNode = levelEdge.visit1; + secondNode = levelEdge.visit2; + } else { + firstNode = levelEdge.visit2; + secondNode = levelEdge.visit1; + } + + if (firstNode.vertex.arbitraryVisit == firstNode) { + EulerTourNode successor = secondNode.successor(); + firstNode.vertex.arbitraryVisit = successor; + augmentAncestorFlags(firstNode); + augmentAncestorFlags(successor); + } + + EulerTourNode root = firstNode.root(); + EulerTourNode[] firstSplitRoots = root.split(firstNode); + EulerTourNode before = firstSplitRoots[0]; + EulerTourNode[] secondSplitRoots = firstSplitRoots[1].split(secondNode.successor()); + before.concatenate(secondSplitRoots[1]); + firstNode.removeWithoutGettingRoot(); + } + edge.eulerTourEdge = null; + + // Search for a replacement edge + ConnEdge replacementEdge = null; + EulerTourVertex levelVertex1 = edge.vertex1; + EulerTourVertex levelVertex2 = edge.vertex2; + while (levelVertex1 != null) { + EulerTourNode root1 = levelVertex1.arbitraryVisit.root(); + EulerTourNode root2 = levelVertex2.arbitraryVisit.root(); + EulerTourNode root; + if (root1.size < root2.size) { + root = root1; + } else { + root = root2; + } + + pushForestEdges(root); + replacementEdge = findReplacementEdge(root); + if (replacementEdge != null) { + break; + } + + // To save space, get rid of trees with one node + if (root1.size == 1 && levelVertex1.higherVertex != null) { + levelVertex1.higherVertex.lowerVertex = null; + } + if (root2.size == 1 && levelVertex2.higherVertex != null) { + levelVertex2.higherVertex.lowerVertex = null; + } + + levelVertex1 = levelVertex1.higherVertex; + levelVertex2 = levelVertex2.higherVertex; + } + + if (replacementEdge != null) { + // Add the replacement edge to all of the forests at or above the current level + removeFromLinkedLists(replacementEdge); + addToForestLinkedLists(replacementEdge); + EulerTourVertex replacementVertex1 = replacementEdge.vertex1; + EulerTourVertex replacementVertex2 = replacementEdge.vertex2; + augmentAncestorFlags(replacementVertex1.arbitraryVisit); + augmentAncestorFlags(replacementVertex2.arbitraryVisit); + EulerTourEdge lowerEdge = null; + while (replacementVertex1 != null) { + EulerTourEdge levelEdge = addForestEdge(replacementVertex1, replacementVertex2); + if (lowerEdge == null) { + replacementEdge.eulerTourEdge = levelEdge; + } else { + lowerEdge.higherEdge = levelEdge; + } + + lowerEdge = levelEdge; + replacementVertex1 = replacementVertex1.higherVertex; + replacementVertex2 = replacementVertex2.higherVertex; + } + } + } + + if (info1.edges.isEmpty() && !info1.vertex.hasAugmentation) { + remove(vertex1); + } + if (info2.edges.isEmpty() && !info2.vertex.hasAugmentation) { + remove(vertex2); + } + return true; + } + + /** + * Returns whether the specified vertices are connected - whether there is a path between them. Returns true if + * vertex1 == vertex2. This method takes O(lg N) time with high probability. + */ + public boolean connected(ConnVertex vertex1, ConnVertex vertex2) { + if (vertex1 == vertex2) { + return true; + } + VertexInfo info1 = vertexInfo.get(vertex1); + if (info1 == null) { + return false; + } + VertexInfo info2 = vertexInfo.get(vertex2); + return info2 != null && info1.vertex.arbitraryVisit.root() == info2.vertex.arbitraryVisit.root(); + } + + /** Returns the vertices that are directly adjacent to the specified vertex. */ + public Collection adjacentVertices(ConnVertex vertex) { + VertexInfo info = vertexInfo.get(vertex); + if (info != null) { + return new ArrayList(info.edges.keySet()); + } else { + return Collections.emptyList(); + } + } + + /** + * Sets the augmentation associated with the specified vertex. This method takes O(log N) time with high + * probability. + * + * Note that passing a null value for the second argument is not the same as removing the augmentation. For that, + * you need to call removeVertexAugmentation. + * + * @return The augmentation that was previously associated with the vertex. Returns null if it did not have any + * associated augmentation. + */ + public Object setVertexAugmentation(ConnVertex connVertex, Object vertexAugmentation) { + assertIsAugmented(); + EulerTourVertex vertex = ensureInfo(connVertex).vertex; + Object oldAugmentation = vertex.augmentation; + if (!vertex.hasAugmentation || + (vertexAugmentation != null ? !vertexAugmentation.equals(oldAugmentation) : oldAugmentation != null)) { + vertex.augmentation = vertexAugmentation; + vertex.hasAugmentation = true; + for (EulerTourNode node = vertex.arbitraryVisit; node != null; node = node.parent) { + if (!node.augment()) { + break; + } + } + } + return oldAugmentation; + } + + /** + * Removes any augmentation associated with the specified vertex. This method takes O(log N) time with high + * probability. + * @return The augmentation that was previously associated with the vertex. Returns null if it did not have any + * associated augmentation. + */ + public Object removeVertexAugmentation(ConnVertex connVertex) { + assertIsAugmented(); + VertexInfo info = vertexInfo.get(connVertex); + if (info == null) { + return null; + } + + EulerTourVertex vertex = info.vertex; + Object oldAugmentation = vertex.augmentation; + if (info.edges.isEmpty()) { + remove(connVertex); + } else if (vertex.hasAugmentation) { + vertex.augmentation = null; + vertex.hasAugmentation = false; + for (EulerTourNode node = vertex.arbitraryVisit; node != null; node = node.parent) { + if (!node.augment()) { + break; + } + } + } + return oldAugmentation; + } + + /** + * Returns the augmentation associated with the specified vertex. Returns null if it does not have any associated + * augmentation. At present, this method takes constant expected time. Contrast with getComponentAugmentation. + */ + public Object getVertexAugmentation(ConnVertex vertex) { + assertIsAugmented(); + VertexInfo info = vertexInfo.get(vertex); + if (info != null) { + return info.vertex.augmentation; + } else { + return null; + } + } + + /** + * Returns the result of combining the augmentations associated with all of the vertices in the connected component + * containing the specified vertex. Returns null if none of those vertices has any associated augmentation. This + * method takes O(log N) time with high probability. + */ + public Object getComponentAugmentation(ConnVertex vertex) { + assertIsAugmented(); + VertexInfo info = vertexInfo.get(vertex); + if (info != null) { + return info.vertex.arbitraryVisit.root().augmentation; + } else { + return null; + } + } + + /** + * Returns whether the specified vertex has any associated augmentation. At present, this method takes constant + * expected time. Contrast with componentHasAugmentation. + */ + public boolean vertexHasAugmentation(ConnVertex vertex) { + assertIsAugmented(); + VertexInfo info = vertexInfo.get(vertex); + if (info != null) { + return info.vertex.hasAugmentation; + } else { + return false; + } + } + + /** + * Returns whether any of the vertices in the connected component containing the specified vertex has any associated + * augmentation. This method takes O(log N) time with high probability. + */ + public boolean componentHasAugmentation(ConnVertex vertex) { + assertIsAugmented(); + VertexInfo info = vertexInfo.get(vertex); + if (info != null) { + return info.vertex.arbitraryVisit.root().hasAugmentation; + } else { + return false; + } + } + + /** + * Clears this graph, by removing all edges and vertices, and removing all augmentation information from the + * vertices. + */ + public void clear() { + // Note that we construct a new HashMap rather than calling vertexInfo.clear() in order to ensure a reduction in + // space + vertexInfo = new HashMap(); + maxLogVertexCountSinceRebuild = 0; + maxVertexInfoSize = 0; + } + + /** + * Attempts to optimize the internal representation of the graph so that future updates will take less time. This + * method does not affect how long queries such as "connected" will take. You may find it beneficial to call + * optimize() when there is some downtime. Note that this method generally increases the amount of space the + * ConnGraph uses, but not beyond the bound of O(V log V + E). + */ + public void optimize() { + // The current implementation of optimize() takes O(V log^2 V + E log V log log V) time + + rebuild(); + + // Greedily push each forest edge as far down as possible - to the lowest level where the constraint on the + // size of connected components isn't violated + for (VertexInfo info : vertexInfo.values()) { + int level = maxLogVertexCountSinceRebuild; + EulerTourVertex vertex; + for (vertex = info.vertex; vertex.lowerVertex != null; vertex = vertex.lowerVertex) { + level--; + } + + while (vertex != null) { + EulerTourNode node = vertex.arbitraryVisit; + ConnEdge edge = vertex.forestListHead; + while (edge != null) { + if (vertex == edge.vertex2) { + // We'll address this edge when we visit edge.vertex1 + edge = edge.next2; + continue; + } + ConnEdge nextEdge = edge.next1; + + EulerTourVertex lowerVertex1 = vertex; + EulerTourVertex lowerVertex2 = edge.vertex2; + for (int lowerLevel = level - 1; lowerLevel > 0; lowerLevel--) { + int size = 0; + if (lowerVertex1.lowerVertex != null) { + size = lowerVertex1.lowerVertex.arbitraryVisit.root().size; + } else { + size = 1; + } + if (lowerVertex2.lowerVertex != null) { + size += lowerVertex2.lowerVertex.arbitraryVisit.root().size; + } else { + size++; + } + + // X EulerTourVertices = (2 * X - 1) EulerTourNodes + if (size > 2 * (1 << lowerLevel) - 1) { + break; + } + + lowerVertex1 = ensureLowerVertex(lowerVertex1); + lowerVertex2 = ensureLowerVertex(lowerVertex2); + EulerTourEdge lowerEdge = addForestEdge(lowerVertex1, lowerVertex2); + lowerEdge.higherEdge = edge.eulerTourEdge; + edge.eulerTourEdge = lowerEdge; + } + + if (lowerVertex1 != vertex) { + // We pushed the edge down at least one level + removeFromLinkedLists(edge); + augmentAncestorFlags(node); + augmentAncestorFlags(edge.vertex2.arbitraryVisit); + + edge.vertex1 = lowerVertex1; + edge.vertex2 = lowerVertex2; + addToForestLinkedLists(edge); + augmentAncestorFlags(lowerVertex1.arbitraryVisit); + augmentAncestorFlags(lowerVertex2.arbitraryVisit); + } + + edge = nextEdge; + } + + vertex = vertex.higherVertex; + level++; + } + } + + // Push each non-forest edge down to the lowest level where the endpoints are in the same connected component + for (VertexInfo info : vertexInfo.values()) { + EulerTourVertex vertex; + for (vertex = info.vertex; vertex.lowerVertex != null; vertex = vertex.lowerVertex); + while (vertex != null) { + EulerTourNode node = vertex.arbitraryVisit; + ConnEdge edge = vertex.graphListHead; + while (edge != null) { + if (vertex == edge.vertex2) { + // We'll address this edge when we visit edge.vertex1 + edge = edge.next2; + continue; + } + ConnEdge nextEdge = edge.next1; + + // Use binary search to identify the lowest level where the two vertices are in the same connected + // component + int maxLevelsDown = 0; + EulerTourVertex lowerVertex1 = vertex.lowerVertex; + EulerTourVertex lowerVertex2 = edge.vertex2.lowerVertex; + while (lowerVertex1 != null && lowerVertex2 != null) { + maxLevelsDown++; + lowerVertex1 = lowerVertex1.lowerVertex; + lowerVertex2 = lowerVertex2.lowerVertex; + } + EulerTourVertex levelVertex1 = vertex; + EulerTourVertex levelVertex2 = edge.vertex2; + while (maxLevelsDown > 0) { + int levelsDown = (maxLevelsDown + 1) / 2; + lowerVertex1 = levelVertex1; + lowerVertex2 = levelVertex2; + for (int i = 0; i < levelsDown; i++) { + lowerVertex1 = lowerVertex1.lowerVertex; + lowerVertex2 = lowerVertex2.lowerVertex; + } + + if (lowerVertex1.arbitraryVisit.root() != lowerVertex2.arbitraryVisit.root()) { + maxLevelsDown = levelsDown - 1; + } else { + levelVertex1 = lowerVertex1; + levelVertex2 = lowerVertex2; + maxLevelsDown -= levelsDown; + } + } + + if (levelVertex1 != vertex) { + removeFromLinkedLists(edge); + augmentAncestorFlags(node); + augmentAncestorFlags(edge.vertex2.arbitraryVisit); + + edge.vertex1 = levelVertex1; + edge.vertex2 = levelVertex2; + addToGraphLinkedLists(edge); + augmentAncestorFlags(levelVertex1.arbitraryVisit); + augmentAncestorFlags(levelVertex2.arbitraryVisit); + } + + edge = nextEdge; + } + vertex = vertex.higherVertex; + } + } + } +} diff --git a/src/main/java/com/github/btrekkie/connectivity/ConnVertex.java b/src/main/java/com/github/btrekkie/connectivity/ConnVertex.java new file mode 100644 index 000000000..f32860958 --- /dev/null +++ b/src/main/java/com/github/btrekkie/connectivity/ConnVertex.java @@ -0,0 +1,41 @@ +package com.github.btrekkie.connectivity; + +import java.util.Random; + +/** A vertex in a ConnGraph. See the comments for ConnGraph. */ +public class ConnVertex { + /** + * The thread-local random number generator we use by default to set the "hash" field. We could use + * ThreadLocalRandom instead, but ThreadLocalRandom isn't available in Java 6.0 and below. + */ + private static final ThreadLocal random = new ThreadLocal() { + @Override + protected Random initialValue() { + return new Random(); + } + }; + + /** + * A randomly generated integer to use as the return value of hashCode(). ConnGraph relies on random hash codes for + * its performance guarantees. + */ + private final int hash; + + public ConnVertex() { + hash = random.get().nextInt(); + } + + /** + * Constructs a new ConnVertex. + * @param random The random number generator to use to produce a random hash code. ConnGraph relies on random hash + * codes for its performance guarantees. + */ + public ConnVertex(Random random) { + hash = random.nextInt(); + } + + @Override + public int hashCode() { + return hash; + } +} diff --git a/src/main/java/com/github/btrekkie/connectivity/EulerTourEdge.java b/src/main/java/com/github/btrekkie/connectivity/EulerTourEdge.java new file mode 100644 index 000000000..367399361 --- /dev/null +++ b/src/main/java/com/github/btrekkie/connectivity/EulerTourEdge.java @@ -0,0 +1,30 @@ +package com.github.btrekkie.connectivity; + +/** + * The representation of a forest edge in some Euler tour forest F_i at some particular level i. Each forest edge has + * one EulerTourEdge object for each level it appears in. See the comments for the implementation of ConnGraph. + */ +class EulerTourEdge { + /** + * One of the two visits preceding the edge in the Euler tour, in addition to visit2. (The node is at the same level + * as the EulerTourEdge.) + */ + public final EulerTourNode visit1; + + /** + * One of the two visits preceding the edge in the Euler tour, in addition to visit1. (The node is at the same level + * as the EulerTourEdge.) + */ + public final EulerTourNode visit2; + + /** + * The representation of this edge in the next-higher level. higherEdge is null if this edge is in the highest + * level. + */ + public EulerTourEdge higherEdge; + + public EulerTourEdge(EulerTourNode visit1, EulerTourNode visit2) { + this.visit1 = visit1; + this.visit2 = visit2; + } +} diff --git a/src/main/java/com/github/btrekkie/connectivity/EulerTourNode.java b/src/main/java/com/github/btrekkie/connectivity/EulerTourNode.java new file mode 100644 index 000000000..9d1f068f9 --- /dev/null +++ b/src/main/java/com/github/btrekkie/connectivity/EulerTourNode.java @@ -0,0 +1,112 @@ +package com.github.btrekkie.connectivity; + +import com.github.btrekkie.red_black_node.RedBlackNode; + +/** + * A node in an Euler tour tree for ConnGraph (at some particular level i). See the comments for the implementation of + * ConnGraph. + */ +class EulerTourNode extends RedBlackNode { + /** The dummy leaf node. */ + public static final EulerTourNode LEAF = new EulerTourNode(null, null); + + /** The vertex this node visits. */ + public final EulerTourVertex vertex; + + /** The number of nodes in the subtree rooted at this node. */ + public int size; + + /** + * Whether the subtree rooted at this node contains a node "node" for which + * node.vertex.arbitraryNode == node && node.vertex.graphListHead != null. + */ + public boolean hasGraphEdge; + + /** + * Whether the subtree rooted at this node contains a node "node" for which + * node.vertex.arbitraryNode == node && node.vertex.forestListHead != null. + */ + public boolean hasForestEdge; + + /** + * The combining function for combining user-provided augmentations. augmentationFunc is null if this node is not in + * the highest level. + */ + public final Augmentation augmentationFunc; + + /** + * The combined augmentation for the subtree rooted at this node. This is the result of combining the augmentation + * values node.vertex.augmentation for all nodes "node" in the subtree rooted at this node for which + * node.vertex.arbitraryVisit == node, using augmentationFunc. This is null if hasAugmentation is false. + */ + public Object augmentation; + + /** + * Whether the subtree rooted at this node contains at least one augmentation value. This indicates whether there is + * some node "node" in the subtree rooted at this node for which node.vertex.hasAugmentation is true and + * node.vertex.arbitraryVisit == node. + */ + public boolean hasAugmentation; + + public EulerTourNode(EulerTourVertex vertex, Augmentation augmentationFunc) { + this.vertex = vertex; + this.augmentationFunc = augmentationFunc; + } + + /** Like augment(), but only updates the augmentation fields hasGraphEdge and hasForestEdge. */ + public boolean augmentFlags() { + boolean newHasGraphEdge = + left.hasGraphEdge || right.hasGraphEdge || (vertex.arbitraryVisit == this && vertex.graphListHead != null); + boolean newHasForestEdge = + left.hasForestEdge || right.hasForestEdge || + (vertex.arbitraryVisit == this && vertex.forestListHead != null); + if (newHasGraphEdge == hasGraphEdge && newHasForestEdge == hasForestEdge) { + return false; + } else { + hasGraphEdge = newHasGraphEdge; + hasForestEdge = newHasForestEdge; + return true; + } + } + + @Override + public boolean augment() { + int newSize = left.size + right.size + 1; + boolean augmentedFlags = augmentFlags(); + + Object newAugmentation = null; + boolean newHasAugmentation = false; + if (augmentationFunc != null) { + if (left.hasAugmentation) { + newAugmentation = left.augmentation; + newHasAugmentation = true; + } + if (vertex.hasAugmentation && vertex.arbitraryVisit == this) { + if (newHasAugmentation) { + newAugmentation = augmentationFunc.combine(newAugmentation, vertex.augmentation); + } else { + newAugmentation = vertex.augmentation; + newHasAugmentation = true; + } + } + if (right.hasAugmentation) { + if (newHasAugmentation) { + newAugmentation = augmentationFunc.combine(newAugmentation, right.augmentation); + } else { + newAugmentation = right.augmentation; + newHasAugmentation = true; + } + } + } + + if (newSize == size && !augmentedFlags && hasAugmentation == newHasAugmentation && + (newAugmentation != null ? newAugmentation.equals(augmentation) : augmentation == null)) { + return false; + } else { + size = newSize; + augmentation = newAugmentation; + hasAugmentation = newHasAugmentation; + return true; + } + } +} diff --git a/src/main/java/com/github/btrekkie/connectivity/EulerTourVertex.java b/src/main/java/com/github/btrekkie/connectivity/EulerTourVertex.java new file mode 100644 index 000000000..2b02940b0 --- /dev/null +++ b/src/main/java/com/github/btrekkie/connectivity/EulerTourVertex.java @@ -0,0 +1,43 @@ +package com.github.btrekkie.connectivity; + +/** + * The representation of a ConnVertex at some particular level i. Each vertex has one EulerTourVertex object for each + * level it appears in. Note that different vertices may appear in different numbers of levels, as EulerTourVertex + * objects are only created for lower levels as needed. See the comments for the implementation of ConnGraph. + */ +class EulerTourVertex { + /** + * The representation of this edge in the next-lower level. lowerVertex is null if this is the lowest-level + * representation of this vertex. + */ + public EulerTourVertex lowerVertex; + + /** + * The representation of this edge in the next-higher level. This is null if this vertex is in the highest level. + */ + public EulerTourVertex higherVertex; + + /** + * An arbitrarily selected visit to the vertex in the Euler tour tree that contains it (at the same level as this). + */ + public EulerTourNode arbitraryVisit; + + /** + * The first edge in the linked list of level-i edges that are adjacent to the vertex in G_i, but are not in the + * Euler tour forest F_i, where i is the level of the vertex. Note that this list excludes any edges that also + * appear in lower levels. + */ + public ConnEdge graphListHead; + + /** + * The first edge in the linked list of level-i edges adjacent to the vertex that are in F_i, where i is the level + * of the vertex. Note that this list excludes any edges that also appear in lower levels. + */ + public ConnEdge forestListHead; + + /** The augmentation associated with this vertex, if any. This is null instead if higherVertex != null. */ + public Object augmentation; + + /** Whether there is any augmentation associated with this vertex. This is false instead if higherVertex != null. */ + public boolean hasAugmentation; +} diff --git a/src/main/java/com/github/btrekkie/connectivity/VertexInfo.java b/src/main/java/com/github/btrekkie/connectivity/VertexInfo.java new file mode 100644 index 000000000..1593a2040 --- /dev/null +++ b/src/main/java/com/github/btrekkie/connectivity/VertexInfo.java @@ -0,0 +1,32 @@ +package com.github.btrekkie.connectivity; + +import java.util.HashMap; +import java.util.Map; + +/** + * Describes a ConnVertex, with respect to a particular ConnGraph. There is exactly one VertexInfo object per vertex in + * a given graph, regardless of how many levels the vertex is in. See the comments for the implementation of ConnGraph. + */ +class VertexInfo { + /** The representation of the vertex in the highest level. */ + public EulerTourVertex vertex; + + /** + * A map from each ConnVertex adjacent to this vertex to the ConnEdge object for the edge connecting it to this + * vertex. Lookups take O(1) expected time and O(log N / log log N) time with high probability, because "edges" is a + * HashMap, and ConnVertex.hashCode() returns a random integer. + */ + public Map edges = new HashMap(); + + /** + * The maximum number of entries in "edges" since the last time we "rebuilt" that field. When the number of edges + * drops sufficiently, we rebuild "edges" by copying its contents to a new HashMap. We do this to ensure that + * "edges" uses O(K) space, where K is the number of vertices adjacent to this. (The capacity of a HashMap is not + * automatically reduced as the number of entries decreases, so we have to limit space usage manually.) + */ + public int maxEdgeCountSinceRebuild; + + public VertexInfo(EulerTourVertex vertex) { + this.vertex = vertex; + } +} diff --git a/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java b/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java new file mode 100644 index 000000000..30feeae6a --- /dev/null +++ b/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java @@ -0,0 +1,992 @@ +package com.github.btrekkie.connectivity.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +import org.junit.Test; + +import com.github.btrekkie.connectivity.ConnGraph; +import com.github.btrekkie.connectivity.ConnVertex; + +/* Note that most of the ConnGraphTest test methods use the one-argument ConnVertex constructor, in order to make their + * behavior more predictable. That way, there are consistent test results, and test failures are easier to debug. + */ +public class ConnGraphTest { + /** Tests ConnectivityGraph on a small forest and a binary tree-like subgraph. */ + @Test + public void testForestAndBinaryTree() { + ConnGraph graph = new ConnGraph(); + Random random = new Random(6170); + ConnVertex vertex1 = new ConnVertex(random); + ConnVertex vertex2 = new ConnVertex(random); + assertTrue(graph.addEdge(vertex1, vertex2)); + ConnVertex vertex3 = new ConnVertex(random); + assertTrue(graph.addEdge(vertex3, vertex1)); + ConnVertex vertex4 = new ConnVertex(random); + assertTrue(graph.addEdge(vertex1, vertex4)); + ConnVertex vertex5 = new ConnVertex(random); + ConnVertex vertex6 = new ConnVertex(random); + ConnVertex vertex7 = new ConnVertex(random); + assertTrue(graph.addEdge(vertex6, vertex7)); + assertTrue(graph.addEdge(vertex6, vertex5)); + assertTrue(graph.addEdge(vertex4, vertex5)); + assertFalse(graph.addEdge(vertex1, vertex3)); + ConnVertex vertex8 = new ConnVertex(random); + ConnVertex vertex9 = new ConnVertex(random); + assertTrue(graph.addEdge(vertex8, vertex9)); + ConnVertex vertex10 = new ConnVertex(random); + assertTrue(graph.addEdge(vertex8, vertex10)); + assertFalse(graph.removeEdge(vertex7, vertex1)); + assertTrue(graph.connected(vertex1, vertex4)); + assertTrue(graph.connected(vertex1, vertex1)); + assertTrue(graph.connected(vertex1, vertex2)); + assertTrue(graph.connected(vertex3, vertex6)); + assertTrue(graph.connected(vertex7, vertex4)); + assertTrue(graph.connected(vertex8, vertex9)); + assertTrue(graph.connected(vertex5, vertex2)); + assertTrue(graph.connected(vertex8, vertex10)); + assertTrue(graph.connected(vertex9, vertex10)); + assertFalse(graph.connected(vertex1, vertex8)); + assertFalse(graph.connected(vertex2, vertex10)); + assertTrue(graph.removeEdge(vertex4, vertex5)); + assertTrue(graph.connected(vertex1, vertex3)); + assertTrue(graph.connected(vertex2, vertex4)); + assertTrue(graph.connected(vertex5, vertex6)); + assertTrue(graph.connected(vertex5, vertex7)); + assertTrue(graph.connected(vertex8, vertex9)); + assertTrue(graph.connected(vertex3, vertex3)); + assertFalse(graph.connected(vertex1, vertex5)); + assertFalse(graph.connected(vertex4, vertex7)); + assertFalse(graph.connected(vertex1, vertex8)); + assertFalse(graph.connected(vertex6, vertex9)); + + Set expectedAdjVertices = new HashSet(); + expectedAdjVertices.add(vertex2); + expectedAdjVertices.add(vertex3); + expectedAdjVertices.add(vertex4); + assertEquals(expectedAdjVertices, new HashSet(graph.adjacentVertices(vertex1))); + expectedAdjVertices.clear(); + expectedAdjVertices.add(vertex5); + expectedAdjVertices.add(vertex7); + assertEquals(expectedAdjVertices, new HashSet(graph.adjacentVertices(vertex6))); + assertEquals(Collections.singleton(vertex8), new HashSet(graph.adjacentVertices(vertex9))); + assertEquals(Collections.emptySet(), new HashSet(graph.adjacentVertices(new ConnVertex(random)))); + graph.optimize(); + + List vertices = new ArrayList(1000); + for (int i = 0; i < 1000; i++) { + vertices.add(new ConnVertex(random)); + } + for (int i = 0; i < 1000; i++) { + if (i > 0 && Integer.bitCount(i) <= 3) { + graph.addEdge(vertices.get(i), vertices.get((i - 1) / 2)); + } + } + for (int i = 0; i < 1000; i++) { + if (Integer.bitCount(i) > 3) { + graph.addEdge(vertices.get((i - 1) / 2), vertices.get(i)); + } + } + for (int i = 15; i < 31; i++) { + graph.removeEdge(vertices.get(i), vertices.get((i - 1) / 2)); + } + assertTrue(graph.connected(vertices.get(0), vertices.get(0))); + assertTrue(graph.connected(vertices.get(11), vertices.get(2))); + assertTrue(graph.connected(vertices.get(7), vertices.get(14))); + assertTrue(graph.connected(vertices.get(0), vertices.get(10))); + assertFalse(graph.connected(vertices.get(0), vertices.get(15))); + assertFalse(graph.connected(vertices.get(15), vertices.get(16))); + assertFalse(graph.connected(vertices.get(14), vertices.get(15))); + assertFalse(graph.connected(vertices.get(7), vertices.get(605))); + assertFalse(graph.connected(vertices.get(5), vertices.get(87))); + assertTrue(graph.connected(vertices.get(22), vertices.get(22))); + assertTrue(graph.connected(vertices.get(16), vertices.get(70))); + assertTrue(graph.connected(vertices.get(113), vertices.get(229))); + assertTrue(graph.connected(vertices.get(21), vertices.get(715))); + assertTrue(graph.connected(vertices.get(175), vertices.get(715))); + assertTrue(graph.connected(vertices.get(30), vertices.get(999))); + assertTrue(graph.connected(vertices.get(991), vertices.get(999))); + } + + /** Tests ConnectivityGraph on a small graph that has cycles. */ + @Test + public void testSmallCycles() { + ConnGraph graph = new ConnGraph(); + Random random = new Random(6170); + ConnVertex vertex1 = new ConnVertex(random); + ConnVertex vertex2 = new ConnVertex(random); + ConnVertex vertex3 = new ConnVertex(random); + ConnVertex vertex4 = new ConnVertex(random); + ConnVertex vertex5 = new ConnVertex(random); + assertTrue(graph.addEdge(vertex1, vertex2)); + assertTrue(graph.addEdge(vertex2, vertex3)); + assertTrue(graph.addEdge(vertex1, vertex3)); + assertTrue(graph.addEdge(vertex2, vertex4)); + assertTrue(graph.addEdge(vertex3, vertex4)); + assertTrue(graph.addEdge(vertex4, vertex5)); + assertTrue(graph.connected(vertex5, vertex1)); + assertTrue(graph.connected(vertex1, vertex4)); + assertTrue(graph.removeEdge(vertex4, vertex5)); + assertFalse(graph.connected(vertex4, vertex5)); + assertFalse(graph.connected(vertex5, vertex1)); + assertTrue(graph.connected(vertex1, vertex4)); + assertTrue(graph.removeEdge(vertex1, vertex2)); + assertTrue(graph.removeEdge(vertex3, vertex4)); + assertTrue(graph.connected(vertex1, vertex4)); + assertTrue(graph.removeEdge(vertex2, vertex3)); + assertTrue(graph.connected(vertex1, vertex3)); + assertTrue(graph.connected(vertex2, vertex4)); + assertFalse(graph.connected(vertex1, vertex4)); + } + + /** Tests ConnectivityGraph on a grid-based graph. */ + @Test + public void testGrid() { + ConnGraph graph = new ConnGraph(); + Random random = new Random(6170); + ConnVertex vertex = new ConnVertex(random); + assertTrue(graph.connected(vertex, vertex)); + + graph = new ConnGraph(SumAndMax.AUGMENTATION); + List> vertices = new ArrayList>(20); + for (int y = 0; y < 20; y++) { + List row = new ArrayList(20); + for (int x = 0; x < 20; x++) { + row.add(new ConnVertex(random)); + } + vertices.add(row); + } + for (int y = 0; y < 19; y++) { + for (int x = 0; x < 19; x++) { + assertTrue(graph.addEdge(vertices.get(y).get(x), vertices.get(y).get(x + 1))); + assertTrue(graph.addEdge(vertices.get(y).get(x), vertices.get(y + 1).get(x))); + } + } + graph.optimize(); + + assertTrue(graph.connected(vertices.get(0).get(0), vertices.get(15).get(12))); + assertTrue(graph.connected(vertices.get(0).get(0), vertices.get(18).get(19))); + assertFalse(graph.connected(vertices.get(0).get(0), vertices.get(19).get(19))); + assertFalse(graph.removeEdge(vertices.get(18).get(19), vertices.get(19).get(19))); + assertFalse(graph.removeEdge(vertices.get(0).get(0), vertices.get(2).get(2))); + + assertTrue(graph.removeEdge(vertices.get(12).get(8), vertices.get(11).get(8))); + assertTrue(graph.removeEdge(vertices.get(12).get(9), vertices.get(11).get(9))); + assertTrue(graph.removeEdge(vertices.get(12).get(8), vertices.get(12).get(7))); + assertTrue(graph.removeEdge(vertices.get(13).get(8), vertices.get(13).get(7))); + assertTrue(graph.removeEdge(vertices.get(13).get(8), vertices.get(14).get(8))); + assertTrue(graph.removeEdge(vertices.get(12).get(9), vertices.get(12).get(10))); + assertTrue(graph.removeEdge(vertices.get(13).get(9), vertices.get(13).get(10))); + assertTrue(graph.connected(vertices.get(2).get(1), vertices.get(12).get(8))); + assertTrue(graph.connected(vertices.get(12).get(8), vertices.get(13).get(9))); + assertTrue(graph.removeEdge(vertices.get(13).get(9), vertices.get(14).get(9))); + assertFalse(graph.connected(vertices.get(2).get(1), vertices.get(12).get(8))); + assertTrue(graph.connected(vertices.get(12).get(8), vertices.get(13).get(9))); + assertFalse(graph.connected(vertices.get(11).get(8), vertices.get(12).get(8))); + assertTrue(graph.connected(vertices.get(16).get(18), vertices.get(6).get(15))); + assertTrue(graph.removeEdge(vertices.get(12).get(9), vertices.get(12).get(8))); + assertTrue(graph.removeEdge(vertices.get(12).get(8), vertices.get(13).get(8))); + assertFalse(graph.connected(vertices.get(2).get(1), vertices.get(12).get(8))); + assertFalse(graph.connected(vertices.get(12).get(8), vertices.get(13).get(9))); + assertFalse(graph.connected(vertices.get(11).get(8), vertices.get(12).get(8))); + assertTrue(graph.connected(vertices.get(13).get(8), vertices.get(12).get(9))); + + assertTrue(graph.removeEdge(vertices.get(6).get(15), vertices.get(5).get(15))); + assertTrue(graph.removeEdge(vertices.get(6).get(15), vertices.get(7).get(15))); + assertTrue(graph.removeEdge(vertices.get(6).get(15), vertices.get(6).get(14))); + assertTrue(graph.removeEdge(vertices.get(6).get(15), vertices.get(6).get(16))); + assertFalse(graph.removeEdge(vertices.get(6).get(15), vertices.get(5).get(15))); + assertFalse(graph.connected(vertices.get(16).get(18), vertices.get(6).get(15))); + assertFalse(graph.connected(vertices.get(7).get(15), vertices.get(6).get(15))); + graph.addEdge(vertices.get(6).get(15), vertices.get(7).get(15)); + assertTrue(graph.connected(vertices.get(16).get(18), vertices.get(6).get(15))); + + for (int y = 1; y < 19; y++) { + for (int x = 1; x < 19; x++) { + graph.removeEdge(vertices.get(y).get(x), vertices.get(y).get(x + 1)); + graph.removeEdge(vertices.get(y).get(x), vertices.get(y + 1).get(x)); + } + } + + assertTrue(graph.addEdge(vertices.get(5).get(6), vertices.get(0).get(7))); + assertTrue(graph.addEdge(vertices.get(12).get(8), vertices.get(5).get(6))); + assertTrue(graph.connected(vertices.get(5).get(6), vertices.get(14).get(0))); + assertTrue(graph.connected(vertices.get(12).get(8), vertices.get(0).get(17))); + assertFalse(graph.connected(vertices.get(3).get(5), vertices.get(0).get(9))); + assertFalse(graph.connected(vertices.get(14).get(2), vertices.get(11).get(18))); + + assertNull(graph.getVertexAugmentation(vertices.get(13).get(8))); + assertNull(graph.getVertexAugmentation(vertices.get(6).get(4))); + assertNull(graph.getComponentAugmentation(vertices.get(13).get(8))); + assertNull(graph.getComponentAugmentation(vertices.get(6).get(4))); + assertFalse(graph.vertexHasAugmentation(vertices.get(13).get(8))); + assertFalse(graph.vertexHasAugmentation(vertices.get(6).get(4))); + assertFalse(graph.componentHasAugmentation(vertices.get(13).get(8))); + assertFalse(graph.componentHasAugmentation(vertices.get(6).get(4))); + } + + /** Tests a graph with a hub-and-spokes subgraph and a clique subgraph. */ + @Test + public void testWheelAndClique() { + ConnGraph graph = new ConnGraph(SumAndMax.AUGMENTATION); + Random random = new Random(6170); + ConnVertex hub = new ConnVertex(random); + List spokes1 = new ArrayList(10); + List spokes2 = new ArrayList(10); + for (int i = 0; i < 10; i++) { + ConnVertex spoke1 = new ConnVertex(random); + ConnVertex spoke2 = new ConnVertex(random); + assertTrue(graph.addEdge(spoke1, spoke2)); + assertNull(graph.setVertexAugmentation(spoke1, new SumAndMax(i, i))); + assertNull(graph.setVertexAugmentation(spoke2, new SumAndMax(i, i + 10))); + spokes1.add(spoke1); + spokes2.add(spoke2); + } + for (int i = 0; i < 10; i++) { + assertTrue(graph.addEdge(spokes1.get(i), hub)); + } + for (int i = 0; i < 10; i++) { + assertTrue(graph.addEdge(hub, spokes2.get(i))); + } + + List clique = new ArrayList(10); + for (int i = 0; i < 10; i++) { + ConnVertex vertex = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(vertex, new SumAndMax(i, i + 20))); + clique.add(vertex); + } + for (int i = 0; i < 10; i++) { + for (int j = i + 1; j < 10; j++) { + assertTrue(graph.addEdge(clique.get(i), clique.get(j))); + } + } + assertTrue(graph.addEdge(hub, clique.get(0))); + + assertTrue(graph.connected(spokes1.get(5), clique.get(3))); + assertTrue(graph.connected(spokes1.get(3), spokes2.get(8))); + assertTrue(graph.connected(spokes1.get(4), spokes2.get(4))); + assertTrue(graph.connected(clique.get(5), hub)); + SumAndMax expectedAugmentation = new SumAndMax(135, 29); + assertEquals(expectedAugmentation, graph.getComponentAugmentation(spokes2.get(8))); + assertTrue(graph.componentHasAugmentation(spokes2.get(8))); + assertEquals(expectedAugmentation, graph.getComponentAugmentation(hub)); + assertEquals(expectedAugmentation, graph.getComponentAugmentation(clique.get(9))); + assertEquals(new SumAndMax(4, 4), graph.getVertexAugmentation(spokes1.get(4))); + assertTrue(graph.vertexHasAugmentation(spokes1.get(4))); + assertNull(graph.getVertexAugmentation(hub)); + assertFalse(graph.vertexHasAugmentation(hub)); + + assertTrue(graph.removeEdge(spokes1.get(5), hub)); + assertTrue(graph.connected(spokes1.get(5), clique.get(2))); + assertTrue(graph.connected(spokes1.get(5), spokes1.get(8))); + assertTrue(graph.connected(spokes1.get(5), spokes2.get(5))); + assertEquals(new SumAndMax(135, 29), graph.getComponentAugmentation(hub)); + assertTrue(graph.removeEdge(spokes2.get(5), hub)); + assertFalse(graph.connected(spokes1.get(5), clique.get(2))); + assertFalse(graph.connected(spokes1.get(5), spokes1.get(8))); + assertTrue(graph.connected(spokes1.get(5), spokes2.get(5))); + assertEquals(new SumAndMax(125, 29), graph.getComponentAugmentation(hub)); + assertTrue(graph.addEdge(spokes1.get(5), hub)); + assertTrue(graph.connected(spokes1.get(5), clique.get(2))); + assertTrue(graph.connected(spokes1.get(5), spokes1.get(8))); + assertTrue(graph.connected(spokes1.get(5), spokes2.get(5))); + assertEquals(new SumAndMax(135, 29), graph.getComponentAugmentation(hub)); + + assertTrue(graph.removeEdge(hub, clique.get(0))); + assertFalse(graph.connected(spokes1.get(3), clique.get(4))); + assertTrue(graph.connected(spokes2.get(7), hub)); + assertFalse(graph.connected(hub, clique.get(0))); + assertTrue(graph.connected(spokes2.get(9), spokes1.get(5))); + assertEquals(new SumAndMax(90, 19), graph.getComponentAugmentation(hub)); + assertEquals(new SumAndMax(90, 19), graph.getComponentAugmentation(spokes2.get(4))); + assertEquals(new SumAndMax(45, 29), graph.getComponentAugmentation(clique.get(1))); + + assertEquals(new SumAndMax(9, 29), graph.setVertexAugmentation(clique.get(9), new SumAndMax(-20, 4))); + for (int i = 0; i < 10; i++) { + assertEquals( + new SumAndMax(i, i + 10), graph.setVertexAugmentation(spokes2.get(i), new SumAndMax(i - 1, i))); + } + assertNull(graph.removeVertexAugmentation(hub)); + assertEquals(new SumAndMax(4, 4), graph.removeVertexAugmentation(spokes1.get(4))); + assertEquals(new SumAndMax(6, 7), graph.removeVertexAugmentation(spokes2.get(7))); + + assertEquals(new SumAndMax(70, 9), graph.getComponentAugmentation(hub)); + assertTrue(graph.componentHasAugmentation(hub)); + assertEquals(new SumAndMax(70, 9), graph.getComponentAugmentation(spokes1.get(6))); + assertEquals(new SumAndMax(16, 28), graph.getComponentAugmentation(clique.get(4))); + + assertTrue(graph.addEdge(hub, clique.get(1))); + expectedAugmentation = new SumAndMax(86, 28); + assertEquals(expectedAugmentation, graph.getComponentAugmentation(hub)); + assertTrue(graph.componentHasAugmentation(hub)); + assertEquals(expectedAugmentation, graph.getComponentAugmentation(spokes2.get(7))); + assertEquals(expectedAugmentation, graph.getComponentAugmentation(clique.get(3))); + + for (int i = 0; i < 10; i++) { + assertTrue(graph.removeEdge(hub, spokes1.get(i))); + if (i != 5) { + assertTrue(graph.removeEdge(hub, spokes2.get(i))); + } + } + assertFalse(graph.connected(hub, spokes1.get(8))); + assertFalse(graph.connected(hub, spokes2.get(4))); + assertTrue(graph.connected(hub, clique.get(5))); + + graph.clear(); + assertTrue(graph.addEdge(hub, spokes1.get(0))); + assertTrue(graph.addEdge(hub, spokes2.get(0))); + assertTrue(graph.addEdge(spokes1.get(0), spokes2.get(0))); + assertTrue(graph.connected(hub, spokes1.get(0))); + assertFalse(graph.connected(hub, spokes2.get(4))); + assertTrue(graph.connected(clique.get(5), clique.get(5))); + assertNull(graph.getComponentAugmentation(hub)); + assertNull(graph.getVertexAugmentation(spokes2.get(8))); + } + + /** + * Sets the matching between vertices.get(columnIndex) and vertices.get(columnIndex + 1) to the permutation + * suggested by newPermutation. See the comments for the implementation of testPermutations(). + * @param graph The graph. + * @param vertices The vertices. + * @param columnIndex The index of the column. + * @param oldPermutation The permutation for the current matching between vertices.get(columnIndex) and + * vertices.get(columnIndex + 1). setPermutation removes the edges in this matching. If there are currently no + * edges between those columns, then oldPermutation is null. + * @param newPermutation The permutation for the new matching. + * @return newPermutation. + */ + private int[] setPermutation( + ConnGraph graph, List> vertices, int columnIndex, + int[] oldPermutation, int[] newPermutation) { + List column1 = vertices.get(columnIndex); + List column2 = vertices.get(columnIndex + 1); + if (oldPermutation != null) { + for (int i = 0; i < oldPermutation.length; i++) { + assertTrue(graph.removeEdge(column1.get(i), column2.get(oldPermutation[i]))); + } + } + for (int i = 0; i < newPermutation.length; i++) { + assertTrue(graph.addEdge(column1.get(i), column2.get(newPermutation[i]))); + } + return newPermutation; + } + + /** + * Asserts that the specified permutation is the correct composite permutation for the specified column, i.e. that + * for all i, vertices.get(0).get(i) is in the same connected component as + * vertices.get(columnIndex + 1).get(expectedPermutation[i]). See the comments for the implementation of + * testPermutations(). + */ + private void checkPermutation( + ConnGraph graph, List> vertices, int columnIndex, int[] expectedPermutation) { + List firstColumn = vertices.get(0); + List column = vertices.get(columnIndex + 1); + for (int i = 0; i < expectedPermutation.length; i++) { + assertTrue(graph.connected(firstColumn.get(i), column.get(expectedPermutation[i]))); + } + } + + /** + * Asserts that the specified permutation differs from the correct composite permutation for the specified column in + * every position, i.e. that for all i, vertices.get(0).get(i) is in a different connected component from + * vertices.get(columnIndex + 1).get(wrongPermutation[i]). See the comments for the implementation of + * testPermutations(). + */ + private void checkWrongPermutation( + ConnGraph graph, List> vertices, int columnIndex, int[] wrongPermutation) { + List firstColumn = vertices.get(0); + List column = vertices.get(columnIndex + 1); + for (int i = 0; i < wrongPermutation.length; i++) { + assertFalse(graph.connected(firstColumn.get(i), column.get(wrongPermutation[i]))); + } + } + + /** + * Tests a graph in the style used to prove lower bounds on the performance of dynamic connectivity, as presented in + * https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-851-advanced-data-structures-spring-2012/lecture-videos/session-21-dynamic-connectivity-lower-bound/ . + */ + @Test + public void testPermutations() { + // The graph used in testPermutations() uses an 8 x 9 grid of vertices, such that vertices.get(i).get(j) is the + // vertex at row j, column i. There is a perfect matching between each pair of columns i and i + 1 - that is, + // there are eight non-adjacent edges from vertices in column i to vertices in column i + 1. These form a + // permutation, so that the element j of the permutation is the row number of the vertex in column i + 1 that is + // adjacent to the vertex at row j, column i. + ConnGraph graph = new ConnGraph(); + Random random = new Random(6170); + List> vertices = new ArrayList>(9); + for (int i = 0; i < 9; i++) { + List column = new ArrayList(8); + for (int j = 0; j < 8; j++) { + column.add(new ConnVertex(random)); + } + vertices.add(column); + } + + int[] permutation0 = setPermutation(graph, vertices, 0, null, new int[]{2, 5, 0, 4, 7, 1, 3, 6}); + int[] permutation1 = setPermutation(graph, vertices, 1, null, new int[]{6, 5, 0, 7, 1, 2, 4, 3}); + int[] permutation2 = setPermutation(graph, vertices, 2, null, new int[]{2, 1, 7, 5, 6, 0, 4, 3}); + int[] permutation3 = setPermutation(graph, vertices, 3, null, new int[]{5, 2, 4, 6, 3, 0, 7, 1}); + int[] permutation4 = setPermutation(graph, vertices, 4, null, new int[]{5, 0, 2, 7, 4, 3, 1, 6}); + int[] permutation5 = setPermutation(graph, vertices, 5, null, new int[]{4, 7, 0, 1, 3, 6, 2, 5}); + int[] permutation6 = setPermutation(graph, vertices, 6, null, new int[]{4, 5, 3, 1, 7, 6, 2, 0}); + int[] permutation7 = setPermutation(graph, vertices, 7, null, new int[]{6, 7, 3, 0, 5, 1, 2, 4}); + + permutation0 = setPermutation(graph, vertices, 0, permutation0, new int[]{7, 5, 3, 0, 4, 2, 1, 6}); + checkWrongPermutation(graph, vertices, 0, new int[]{5, 3, 0, 4, 2, 1, 6, 7}); + checkPermutation(graph, vertices, 0, new int[]{7, 5, 3, 0, 4, 2, 1, 6}); + permutation4 = setPermutation(graph, vertices, 4, permutation4, new int[]{2, 7, 0, 6, 5, 4, 1, 3}); + checkWrongPermutation(graph, vertices, 4, new int[]{7, 1, 6, 0, 5, 4, 3, 2}); + checkPermutation(graph, vertices, 4, new int[]{2, 7, 1, 6, 0, 5, 4, 3}); + permutation2 = setPermutation(graph, vertices, 2, permutation2, new int[]{3, 5, 6, 1, 4, 2, 7, 0}); + checkWrongPermutation(graph, vertices, 2, new int[]{6, 0, 7, 5, 3, 2, 4, 1}); + checkPermutation(graph, vertices, 2, new int[]{1, 6, 0, 7, 5, 3, 2, 4}); + permutation6 = setPermutation(graph, vertices, 6, permutation6, new int[]{4, 7, 1, 3, 6, 0, 5, 2}); + checkWrongPermutation(graph, vertices, 6, new int[]{7, 3, 0, 4, 2, 5, 1, 6}); + checkPermutation(graph, vertices, 6, new int[]{6, 7, 3, 0, 4, 2, 5, 1}); + permutation1 = setPermutation(graph, vertices, 1, permutation1, new int[]{2, 4, 0, 5, 6, 3, 7, 1}); + checkWrongPermutation(graph, vertices, 1, new int[]{3, 5, 2, 6, 0, 4, 7, 1}); + checkPermutation(graph, vertices, 1, new int[]{1, 3, 5, 2, 6, 0, 4, 7}); + permutation5 = setPermutation(graph, vertices, 5, permutation5, new int[]{5, 3, 2, 0, 7, 1, 6, 4}); + checkWrongPermutation(graph, vertices, 5, new int[]{5, 1, 0, 4, 3, 6, 7, 2}); + checkPermutation(graph, vertices, 5, new int[]{2, 5, 1, 0, 4, 3, 6, 7}); + permutation3 = setPermutation(graph, vertices, 3, permutation3, new int[]{1, 7, 3, 0, 4, 5, 6, 2}); + checkWrongPermutation(graph, vertices, 3, new int[]{7, 3, 6, 2, 0, 4, 1, 5}); + checkPermutation(graph, vertices, 3, new int[]{5, 7, 3, 6, 2, 0, 4, 1}); + permutation7 = setPermutation(graph, vertices, 7, permutation7, new int[]{4, 7, 5, 6, 2, 0, 1, 3}); + checkWrongPermutation(graph, vertices, 7, new int[]{2, 0, 6, 4, 7, 3, 1, 5}); + checkPermutation(graph, vertices, 7, new int[]{5, 2, 0, 6, 4, 7, 3, 1}); + } + + /** Tests a graph based on the United States. */ + @Test + public void testUnitedStates() { + ConnGraph graph = new ConnGraph(SumAndMax.AUGMENTATION); + Random random = new Random(6170); + ConnVertex alabama = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(alabama, new SumAndMax(7, 1819))); + ConnVertex alaska = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(alaska, new SumAndMax(1, 1959))); + ConnVertex arizona = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(arizona, new SumAndMax(9, 1912))); + ConnVertex arkansas = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(arkansas, new SumAndMax(4, 1836))); + ConnVertex california = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(california, new SumAndMax(53, 1850))); + ConnVertex colorado = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(colorado, new SumAndMax(7, 1876))); + ConnVertex connecticut = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(connecticut, new SumAndMax(5, 1788))); + ConnVertex delaware = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(delaware, new SumAndMax(1, 1787))); + ConnVertex florida = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(florida, new SumAndMax(27, 1845))); + ConnVertex georgia = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(georgia, new SumAndMax(14, 1788))); + ConnVertex hawaii = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(hawaii, new SumAndMax(2, 1959))); + ConnVertex idaho = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(idaho, new SumAndMax(2, 1890))); + ConnVertex illinois = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(illinois, new SumAndMax(18, 1818))); + ConnVertex indiana = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(indiana, new SumAndMax(9, 1816))); + ConnVertex iowa = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(iowa, new SumAndMax(4, 1846))); + ConnVertex kansas = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(kansas, new SumAndMax(4, 1861))); + ConnVertex kentucky = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(kentucky, new SumAndMax(6, 1792))); + ConnVertex louisiana = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(louisiana, new SumAndMax(6, 1812))); + ConnVertex maine = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(maine, new SumAndMax(2, 1820))); + ConnVertex maryland = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(maryland, new SumAndMax(8, 1788))); + ConnVertex massachusetts = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(massachusetts, new SumAndMax(9, 1788))); + ConnVertex michigan = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(michigan, new SumAndMax(14, 1837))); + ConnVertex minnesota = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(minnesota, new SumAndMax(8, 1858))); + ConnVertex mississippi = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(mississippi, new SumAndMax(4, 1817))); + ConnVertex missouri = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(missouri, new SumAndMax(8, 1821))); + ConnVertex montana = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(montana, new SumAndMax(1, 1889))); + ConnVertex nebraska = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(nebraska, new SumAndMax(3, 1867))); + ConnVertex nevada = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(nevada, new SumAndMax(4, 1864))); + ConnVertex newHampshire = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(newHampshire, new SumAndMax(2, 1788))); + ConnVertex newJersey = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(newJersey, new SumAndMax(12, 1787))); + ConnVertex newMexico = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(newMexico, new SumAndMax(3, 1912))); + ConnVertex newYork = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(newYork, new SumAndMax(27, 1788))); + ConnVertex northCarolina = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(northCarolina, new SumAndMax(13, 1789))); + ConnVertex northDakota = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(northDakota, new SumAndMax(1, 1889))); + ConnVertex ohio = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(ohio, new SumAndMax(16, 1803))); + ConnVertex oklahoma = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(oklahoma, new SumAndMax(5, 1907))); + ConnVertex oregon = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(oregon, new SumAndMax(5, 1859))); + ConnVertex pennsylvania = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(pennsylvania, new SumAndMax(18, 1787))); + ConnVertex rhodeIsland = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(rhodeIsland, new SumAndMax(2, 1790))); + ConnVertex southCarolina = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(southCarolina, new SumAndMax(7, 1788))); + ConnVertex southDakota = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(southDakota, new SumAndMax(1, 1889))); + ConnVertex tennessee = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(tennessee, new SumAndMax(9, 1796))); + ConnVertex texas = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(texas, new SumAndMax(36, 1845))); + ConnVertex utah = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(utah, new SumAndMax(4, 1896))); + ConnVertex vermont = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(vermont, new SumAndMax(1, 1791))); + ConnVertex virginia = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(virginia, new SumAndMax(11, 1788))); + ConnVertex washington = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(washington, new SumAndMax(10, 1889))); + ConnVertex westVirginia = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(westVirginia, new SumAndMax(3, 1863))); + ConnVertex wisconsin = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(wisconsin, new SumAndMax(8, 1848))); + ConnVertex wyoming = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(wyoming, new SumAndMax(1, 1890))); + + assertTrue(graph.addEdge(alabama, florida)); + assertTrue(graph.addEdge(alabama, georgia)); + assertTrue(graph.addEdge(alabama, mississippi)); + assertTrue(graph.addEdge(alabama, tennessee)); + assertTrue(graph.addEdge(arizona, california)); + assertTrue(graph.addEdge(arizona, colorado)); + assertTrue(graph.addEdge(arizona, nevada)); + assertTrue(graph.addEdge(arizona, newMexico)); + assertTrue(graph.addEdge(arizona, utah)); + assertTrue(graph.addEdge(arkansas, louisiana)); + assertTrue(graph.addEdge(arkansas, mississippi)); + assertTrue(graph.addEdge(arkansas, missouri)); + assertTrue(graph.addEdge(arkansas, oklahoma)); + assertTrue(graph.addEdge(arkansas, tennessee)); + assertTrue(graph.addEdge(arkansas, texas)); + assertTrue(graph.addEdge(california, nevada)); + assertTrue(graph.addEdge(california, oregon)); + assertTrue(graph.addEdge(colorado, kansas)); + assertTrue(graph.addEdge(colorado, nebraska)); + assertTrue(graph.addEdge(colorado, newMexico)); + assertTrue(graph.addEdge(colorado, oklahoma)); + assertTrue(graph.addEdge(colorado, utah)); + assertTrue(graph.addEdge(colorado, wyoming)); + assertTrue(graph.addEdge(connecticut, massachusetts)); + assertTrue(graph.addEdge(connecticut, newYork)); + assertTrue(graph.addEdge(connecticut, rhodeIsland)); + assertTrue(graph.addEdge(delaware, maryland)); + assertTrue(graph.addEdge(delaware, newJersey)); + assertTrue(graph.addEdge(delaware, pennsylvania)); + assertTrue(graph.addEdge(florida, georgia)); + assertTrue(graph.addEdge(georgia, northCarolina)); + assertTrue(graph.addEdge(georgia, southCarolina)); + assertTrue(graph.addEdge(georgia, tennessee)); + assertTrue(graph.addEdge(idaho, montana)); + assertTrue(graph.addEdge(idaho, nevada)); + assertTrue(graph.addEdge(idaho, oregon)); + assertTrue(graph.addEdge(idaho, utah)); + assertTrue(graph.addEdge(idaho, washington)); + assertTrue(graph.addEdge(idaho, wyoming)); + assertTrue(graph.addEdge(illinois, indiana)); + assertTrue(graph.addEdge(illinois, iowa)); + assertTrue(graph.addEdge(illinois, kentucky)); + assertTrue(graph.addEdge(illinois, missouri)); + assertTrue(graph.addEdge(illinois, wisconsin)); + assertTrue(graph.addEdge(indiana, kentucky)); + assertTrue(graph.addEdge(indiana, michigan)); + assertTrue(graph.addEdge(indiana, ohio)); + assertTrue(graph.addEdge(iowa, minnesota)); + assertTrue(graph.addEdge(iowa, missouri)); + assertTrue(graph.addEdge(iowa, nebraska)); + assertTrue(graph.addEdge(iowa, southDakota)); + assertTrue(graph.addEdge(iowa, wisconsin)); + assertTrue(graph.addEdge(kansas, missouri)); + assertTrue(graph.addEdge(kansas, nebraska)); + assertTrue(graph.addEdge(kansas, oklahoma)); + assertTrue(graph.addEdge(kentucky, missouri)); + assertTrue(graph.addEdge(kentucky, ohio)); + assertTrue(graph.addEdge(kentucky, tennessee)); + assertTrue(graph.addEdge(kentucky, virginia)); + assertTrue(graph.addEdge(kentucky, westVirginia)); + assertTrue(graph.addEdge(louisiana, mississippi)); + assertTrue(graph.addEdge(louisiana, texas)); + assertTrue(graph.addEdge(maine, newHampshire)); + assertTrue(graph.addEdge(maryland, pennsylvania)); + assertTrue(graph.addEdge(maryland, virginia)); + assertTrue(graph.addEdge(maryland, westVirginia)); + assertTrue(graph.addEdge(massachusetts, newHampshire)); + assertTrue(graph.addEdge(massachusetts, newYork)); + assertTrue(graph.addEdge(massachusetts, rhodeIsland)); + assertTrue(graph.addEdge(massachusetts, vermont)); + assertTrue(graph.addEdge(michigan, ohio)); + assertTrue(graph.addEdge(michigan, wisconsin)); + assertTrue(graph.addEdge(minnesota, northDakota)); + assertTrue(graph.addEdge(minnesota, southDakota)); + assertTrue(graph.addEdge(minnesota, wisconsin)); + assertTrue(graph.addEdge(mississippi, tennessee)); + assertTrue(graph.addEdge(missouri, nebraska)); + assertTrue(graph.addEdge(missouri, oklahoma)); + assertTrue(graph.addEdge(missouri, tennessee)); + assertTrue(graph.addEdge(montana, northDakota)); + assertTrue(graph.addEdge(montana, southDakota)); + assertTrue(graph.addEdge(montana, wyoming)); + assertTrue(graph.addEdge(nebraska, southDakota)); + assertTrue(graph.addEdge(nebraska, wyoming)); + assertTrue(graph.addEdge(nevada, oregon)); + assertTrue(graph.addEdge(nevada, utah)); + assertTrue(graph.addEdge(newHampshire, vermont)); + assertTrue(graph.addEdge(newJersey, newYork)); + assertTrue(graph.addEdge(newJersey, pennsylvania)); + assertTrue(graph.addEdge(newMexico, oklahoma)); + assertTrue(graph.addEdge(newMexico, texas)); + assertTrue(graph.addEdge(newMexico, utah)); + assertTrue(graph.addEdge(newYork, pennsylvania)); + assertTrue(graph.addEdge(newYork, vermont)); + assertTrue(graph.addEdge(northCarolina, southCarolina)); + assertTrue(graph.addEdge(northCarolina, tennessee)); + assertTrue(graph.addEdge(northCarolina, virginia)); + assertTrue(graph.addEdge(northDakota, southDakota)); + assertTrue(graph.addEdge(ohio, pennsylvania)); + assertTrue(graph.addEdge(ohio, westVirginia)); + assertTrue(graph.addEdge(oklahoma, texas)); + assertTrue(graph.addEdge(oregon, washington)); + assertTrue(graph.addEdge(pennsylvania, westVirginia)); + assertTrue(graph.addEdge(southDakota, wyoming)); + assertTrue(graph.addEdge(tennessee, virginia)); + assertTrue(graph.addEdge(utah, wyoming)); + assertTrue(graph.addEdge(virginia, westVirginia)); + + assertTrue(graph.connected(florida, washington)); + assertTrue(graph.connected(rhodeIsland, michigan)); + assertTrue(graph.connected(delaware, texas)); + assertFalse(graph.connected(alaska, newYork)); + assertFalse(graph.connected(hawaii, idaho)); + assertEquals(new SumAndMax(432, 1912), graph.getComponentAugmentation(newJersey)); + assertEquals(new SumAndMax(2, 1959), graph.getComponentAugmentation(hawaii)); + + // 2186: Aliens attack, split nation in two using lasers + assertTrue(graph.removeEdge(northDakota, minnesota)); + assertTrue(graph.removeEdge(southDakota, minnesota)); + assertTrue(graph.removeEdge(southDakota, iowa)); + assertTrue(graph.removeEdge(nebraska, iowa)); + assertTrue(graph.removeEdge(nebraska, missouri)); + assertTrue(graph.removeEdge(kansas, missouri)); + assertTrue(graph.removeEdge(oklahoma, missouri)); + assertTrue(graph.removeEdge(oklahoma, arkansas)); + assertTrue(graph.removeEdge(texas, arkansas)); + assertTrue(graph.connected(california, massachusetts)); + assertTrue(graph.connected(montana, virginia)); + assertTrue(graph.connected(idaho, southDakota)); + assertTrue(graph.connected(maine, tennessee)); + assertEquals(new SumAndMax(432, 1912), graph.getComponentAugmentation(vermont)); + assertTrue(graph.removeEdge(texas, louisiana)); + assertFalse(graph.connected(california, massachusetts)); + assertFalse(graph.connected(montana, virginia)); + assertTrue(graph.connected(idaho, southDakota)); + assertTrue(graph.connected(maine, tennessee)); + assertEquals(new SumAndMax(149, 1912), graph.getComponentAugmentation(wyoming)); + assertEquals(new SumAndMax(283, 1863), graph.getComponentAugmentation(vermont)); + + // 2254: California breaks off into ocean, secedes + assertTrue(graph.removeEdge(california, oregon)); + assertTrue(graph.removeEdge(california, nevada)); + assertTrue(graph.removeEdge(california, arizona)); + assertEquals(new SumAndMax(53, 1850), graph.removeVertexAugmentation(california)); + assertFalse(graph.connected(california, utah)); + assertFalse(graph.connected(california, oregon)); + assertNull(graph.getComponentAugmentation(california)); + assertEquals(new SumAndMax(96, 1912), graph.getComponentAugmentation(washington)); + assertEquals(new SumAndMax(283, 1863), graph.getComponentAugmentation(vermont)); + + // 2367: Nuclear armageddon + assertEquals(new SumAndMax(7, 1819), graph.removeVertexAugmentation(alabama)); + assertTrue(graph.removeEdge(alabama, florida)); + assertTrue(graph.removeEdge(alabama, georgia)); + assertTrue(graph.removeEdge(alabama, mississippi)); + assertTrue(graph.removeEdge(alabama, tennessee)); + assertEquals(new SumAndMax(1, 1959), graph.removeVertexAugmentation(alaska)); + assertEquals(new SumAndMax(9, 1912), graph.removeVertexAugmentation(arizona)); + assertTrue(graph.removeEdge(arizona, colorado)); + assertTrue(graph.removeEdge(arizona, nevada)); + assertTrue(graph.removeEdge(arizona, newMexico)); + assertTrue(graph.removeEdge(arizona, utah)); + assertEquals(new SumAndMax(4, 1836), graph.removeVertexAugmentation(arkansas)); + assertTrue(graph.removeEdge(arkansas, louisiana)); + assertTrue(graph.removeEdge(arkansas, mississippi)); + assertTrue(graph.removeEdge(arkansas, missouri)); + assertTrue(graph.removeEdge(arkansas, tennessee)); + assertEquals(new SumAndMax(7, 1876), graph.removeVertexAugmentation(colorado)); + assertTrue(graph.removeEdge(colorado, kansas)); + assertTrue(graph.removeEdge(colorado, nebraska)); + assertTrue(graph.removeEdge(colorado, newMexico)); + assertTrue(graph.removeEdge(colorado, oklahoma)); + assertTrue(graph.removeEdge(colorado, utah)); + assertTrue(graph.removeEdge(colorado, wyoming)); + assertEquals(new SumAndMax(5, 1788), graph.removeVertexAugmentation(connecticut)); + assertTrue(graph.removeEdge(connecticut, massachusetts)); + assertTrue(graph.removeEdge(connecticut, newYork)); + assertTrue(graph.removeEdge(connecticut, rhodeIsland)); + assertEquals(new SumAndMax(1, 1787), graph.removeVertexAugmentation(delaware)); + assertTrue(graph.removeEdge(delaware, maryland)); + assertTrue(graph.removeEdge(delaware, newJersey)); + assertTrue(graph.removeEdge(delaware, pennsylvania)); + assertEquals(new SumAndMax(27, 1845), graph.removeVertexAugmentation(florida)); + assertTrue(graph.removeEdge(florida, georgia)); + assertEquals(new SumAndMax(14, 1788), graph.removeVertexAugmentation(georgia)); + assertTrue(graph.removeEdge(georgia, northCarolina)); + assertTrue(graph.removeEdge(georgia, southCarolina)); + assertTrue(graph.removeEdge(georgia, tennessee)); + assertEquals(new SumAndMax(2, 1959), graph.removeVertexAugmentation(hawaii)); + assertEquals(new SumAndMax(2, 1890), graph.removeVertexAugmentation(idaho)); + assertTrue(graph.removeEdge(idaho, montana)); + assertTrue(graph.removeEdge(idaho, nevada)); + assertTrue(graph.removeEdge(idaho, oregon)); + assertTrue(graph.removeEdge(idaho, utah)); + assertTrue(graph.removeEdge(idaho, washington)); + assertTrue(graph.removeEdge(idaho, wyoming)); + assertEquals(new SumAndMax(18, 1818), graph.removeVertexAugmentation(illinois)); + assertTrue(graph.removeEdge(illinois, indiana)); + assertTrue(graph.removeEdge(illinois, iowa)); + assertTrue(graph.removeEdge(illinois, kentucky)); + assertTrue(graph.removeEdge(illinois, missouri)); + assertTrue(graph.removeEdge(illinois, wisconsin)); + assertEquals(new SumAndMax(9, 1816), graph.removeVertexAugmentation(indiana)); + assertTrue(graph.removeEdge(indiana, kentucky)); + assertTrue(graph.removeEdge(indiana, michigan)); + assertTrue(graph.removeEdge(indiana, ohio)); + assertEquals(new SumAndMax(4, 1846), graph.removeVertexAugmentation(iowa)); + assertTrue(graph.removeEdge(iowa, minnesota)); + assertTrue(graph.removeEdge(iowa, missouri)); + assertTrue(graph.removeEdge(iowa, wisconsin)); + assertEquals(new SumAndMax(4, 1861), graph.removeVertexAugmentation(kansas)); + assertTrue(graph.removeEdge(kansas, nebraska)); + assertTrue(graph.removeEdge(kansas, oklahoma)); + assertEquals(new SumAndMax(6, 1792), graph.removeVertexAugmentation(kentucky)); + assertTrue(graph.removeEdge(kentucky, missouri)); + assertTrue(graph.removeEdge(kentucky, ohio)); + assertTrue(graph.removeEdge(kentucky, tennessee)); + assertTrue(graph.removeEdge(kentucky, virginia)); + assertTrue(graph.removeEdge(kentucky, westVirginia)); + assertEquals(new SumAndMax(6, 1812), graph.removeVertexAugmentation(louisiana)); + assertTrue(graph.removeEdge(louisiana, mississippi)); + assertEquals(new SumAndMax(2, 1820), graph.removeVertexAugmentation(maine)); + assertTrue(graph.removeEdge(maine, newHampshire)); + assertEquals(new SumAndMax(8, 1788), graph.removeVertexAugmentation(maryland)); + assertTrue(graph.removeEdge(maryland, pennsylvania)); + assertTrue(graph.removeEdge(maryland, virginia)); + assertTrue(graph.removeEdge(maryland, westVirginia)); + assertEquals(new SumAndMax(9, 1788), graph.removeVertexAugmentation(massachusetts)); + assertTrue(graph.removeEdge(massachusetts, newHampshire)); + assertTrue(graph.removeEdge(massachusetts, newYork)); + assertTrue(graph.removeEdge(massachusetts, rhodeIsland)); + assertTrue(graph.removeEdge(massachusetts, vermont)); + assertEquals(new SumAndMax(14, 1837), graph.removeVertexAugmentation(michigan)); + assertTrue(graph.removeEdge(michigan, ohio)); + assertTrue(graph.removeEdge(michigan, wisconsin)); + assertEquals(new SumAndMax(8, 1858), graph.removeVertexAugmentation(minnesota)); + assertTrue(graph.removeEdge(minnesota, wisconsin)); + assertEquals(new SumAndMax(4, 1817), graph.removeVertexAugmentation(mississippi)); + assertTrue(graph.removeEdge(mississippi, tennessee)); + assertEquals(new SumAndMax(8, 1821), graph.removeVertexAugmentation(missouri)); + assertTrue(graph.removeEdge(missouri, tennessee)); + assertEquals(new SumAndMax(1, 1889), graph.removeVertexAugmentation(montana)); + assertTrue(graph.removeEdge(montana, northDakota)); + assertTrue(graph.removeEdge(montana, southDakota)); + assertTrue(graph.removeEdge(montana, wyoming)); + assertEquals(new SumAndMax(3, 1867), graph.removeVertexAugmentation(nebraska)); + assertTrue(graph.removeEdge(nebraska, southDakota)); + assertTrue(graph.removeEdge(nebraska, wyoming)); + assertEquals(new SumAndMax(4, 1864), graph.removeVertexAugmentation(nevada)); + assertTrue(graph.removeEdge(nevada, oregon)); + assertTrue(graph.removeEdge(nevada, utah)); + assertEquals(new SumAndMax(2, 1788), graph.removeVertexAugmentation(newHampshire)); + assertTrue(graph.removeEdge(newHampshire, vermont)); + assertEquals(new SumAndMax(12, 1787), graph.removeVertexAugmentation(newJersey)); + assertTrue(graph.removeEdge(newJersey, newYork)); + assertTrue(graph.removeEdge(newJersey, pennsylvania)); + assertEquals(new SumAndMax(3, 1912), graph.removeVertexAugmentation(newMexico)); + assertTrue(graph.removeEdge(newMexico, oklahoma)); + assertTrue(graph.removeEdge(newMexico, texas)); + assertTrue(graph.removeEdge(newMexico, utah)); + assertEquals(new SumAndMax(27, 1788), graph.removeVertexAugmentation(newYork)); + assertTrue(graph.removeEdge(newYork, pennsylvania)); + assertTrue(graph.removeEdge(newYork, vermont)); + assertEquals(new SumAndMax(13, 1789), graph.removeVertexAugmentation(northCarolina)); + assertTrue(graph.removeEdge(northCarolina, southCarolina)); + assertTrue(graph.removeEdge(northCarolina, tennessee)); + assertTrue(graph.removeEdge(northCarolina, virginia)); + assertEquals(new SumAndMax(1, 1889), graph.removeVertexAugmentation(northDakota)); + assertTrue(graph.removeEdge(northDakota, southDakota)); + assertEquals(new SumAndMax(16, 1803), graph.removeVertexAugmentation(ohio)); + assertTrue(graph.removeEdge(ohio, pennsylvania)); + assertTrue(graph.removeEdge(ohio, westVirginia)); + assertEquals(new SumAndMax(5, 1907), graph.removeVertexAugmentation(oklahoma)); + assertTrue(graph.removeEdge(oklahoma, texas)); + assertEquals(new SumAndMax(5, 1859), graph.removeVertexAugmentation(oregon)); + assertTrue(graph.removeEdge(oregon, washington)); + assertEquals(new SumAndMax(18, 1787), graph.removeVertexAugmentation(pennsylvania)); + assertTrue(graph.removeEdge(pennsylvania, westVirginia)); + assertEquals(new SumAndMax(2, 1790), graph.removeVertexAugmentation(rhodeIsland)); + assertEquals(new SumAndMax(7, 1788), graph.removeVertexAugmentation(southCarolina)); + assertEquals(new SumAndMax(1, 1889), graph.removeVertexAugmentation(southDakota)); + assertTrue(graph.removeEdge(southDakota, wyoming)); + assertEquals(new SumAndMax(9, 1796), graph.removeVertexAugmentation(tennessee)); + assertTrue(graph.removeEdge(tennessee, virginia)); + assertEquals(new SumAndMax(36, 1845), graph.removeVertexAugmentation(texas)); + assertEquals(new SumAndMax(4, 1896), graph.removeVertexAugmentation(utah)); + assertTrue(graph.removeEdge(utah, wyoming)); + assertEquals(new SumAndMax(1, 1791), graph.removeVertexAugmentation(vermont)); + assertEquals(new SumAndMax(11, 1788), graph.removeVertexAugmentation(virginia)); + assertTrue(graph.removeEdge(virginia, westVirginia)); + assertEquals(new SumAndMax(10, 1889), graph.removeVertexAugmentation(washington)); + assertEquals(new SumAndMax(3, 1863), graph.removeVertexAugmentation(westVirginia)); + assertEquals(new SumAndMax(8, 1848), graph.removeVertexAugmentation(wisconsin)); + assertEquals(new SumAndMax(1, 1890), graph.removeVertexAugmentation(wyoming)); + + assertFalse(graph.connected(georgia, newMexico)); + assertFalse(graph.connected(wisconsin, michigan)); + assertFalse(graph.connected(ohio, kentucky)); + assertFalse(graph.connected(alaska, connecticut)); + assertNull(graph.getComponentAugmentation(southDakota)); + assertNull(graph.getComponentAugmentation(arkansas)); + } + + /** Tests ConnectivityGraph on the graph for a dodecahedron. */ + @Test + public void testDodecahedron() { + ConnGraph graph = new ConnGraph(); + Random random = new Random(6170); + ConnVertex vertex1 = new ConnVertex(random); + ConnVertex vertex2 = new ConnVertex(random); + ConnVertex vertex3 = new ConnVertex(random); + ConnVertex vertex4 = new ConnVertex(random); + ConnVertex vertex5 = new ConnVertex(random); + ConnVertex vertex6 = new ConnVertex(random); + ConnVertex vertex7 = new ConnVertex(random); + ConnVertex vertex8 = new ConnVertex(random); + ConnVertex vertex9 = new ConnVertex(random); + ConnVertex vertex10 = new ConnVertex(random); + ConnVertex vertex11 = new ConnVertex(random); + ConnVertex vertex12 = new ConnVertex(random); + ConnVertex vertex13 = new ConnVertex(random); + ConnVertex vertex14 = new ConnVertex(random); + ConnVertex vertex15 = new ConnVertex(random); + ConnVertex vertex16 = new ConnVertex(random); + ConnVertex vertex17 = new ConnVertex(random); + ConnVertex vertex18 = new ConnVertex(random); + ConnVertex vertex19 = new ConnVertex(random); + ConnVertex vertex20 = new ConnVertex(random); + + assertTrue(graph.addEdge(vertex1, vertex2)); + assertTrue(graph.addEdge(vertex1, vertex5)); + assertTrue(graph.addEdge(vertex1, vertex6)); + assertTrue(graph.addEdge(vertex2, vertex3)); + assertTrue(graph.addEdge(vertex2, vertex8)); + assertTrue(graph.addEdge(vertex3, vertex4)); + assertTrue(graph.addEdge(vertex3, vertex10)); + assertTrue(graph.addEdge(vertex4, vertex5)); + assertTrue(graph.addEdge(vertex4, vertex12)); + assertTrue(graph.addEdge(vertex5, vertex14)); + assertTrue(graph.addEdge(vertex6, vertex7)); + assertTrue(graph.addEdge(vertex6, vertex15)); + assertTrue(graph.addEdge(vertex7, vertex8)); + assertTrue(graph.addEdge(vertex7, vertex16)); + assertTrue(graph.addEdge(vertex8, vertex9)); + assertTrue(graph.addEdge(vertex9, vertex10)); + assertTrue(graph.addEdge(vertex9, vertex17)); + assertTrue(graph.addEdge(vertex10, vertex11)); + assertTrue(graph.addEdge(vertex11, vertex12)); + assertTrue(graph.addEdge(vertex11, vertex18)); + assertTrue(graph.addEdge(vertex12, vertex13)); + assertTrue(graph.addEdge(vertex13, vertex14)); + assertTrue(graph.addEdge(vertex13, vertex19)); + assertTrue(graph.addEdge(vertex14, vertex15)); + assertTrue(graph.addEdge(vertex15, vertex20)); + assertTrue(graph.addEdge(vertex16, vertex17)); + assertTrue(graph.addEdge(vertex16, vertex20)); + assertTrue(graph.addEdge(vertex17, vertex18)); + assertTrue(graph.addEdge(vertex18, vertex19)); + assertTrue(graph.addEdge(vertex19, vertex20)); + graph.optimize(); + + assertTrue(graph.connected(vertex1, vertex17)); + assertTrue(graph.connected(vertex7, vertex15)); + + assertTrue(graph.removeEdge(vertex5, vertex14)); + assertTrue(graph.removeEdge(vertex6, vertex15)); + assertTrue(graph.removeEdge(vertex7, vertex16)); + assertTrue(graph.removeEdge(vertex12, vertex13)); + assertTrue(graph.removeEdge(vertex16, vertex17)); + assertTrue(graph.connected(vertex1, vertex14)); + assertTrue(graph.connected(vertex4, vertex20)); + assertTrue(graph.connected(vertex14, vertex16)); + + assertTrue(graph.removeEdge(vertex18, vertex19)); + assertFalse(graph.connected(vertex1, vertex14)); + assertFalse(graph.connected(vertex4, vertex20)); + assertTrue(graph.connected(vertex14, vertex16)); + + graph.clear(); + graph.optimize(); + assertTrue(graph.connected(vertex7, vertex7)); + assertFalse(graph.connected(vertex1, vertex2)); + } + + /** Tests the zero-argument ConnVertex constructor. */ + @Test + public void testDefaultConnVertexConstructor() { + ConnGraph graph = new ConnGraph(); + ConnVertex vertex1 = new ConnVertex(); + ConnVertex vertex2 = new ConnVertex(); + ConnVertex vertex3 = new ConnVertex(); + ConnVertex vertex4 = new ConnVertex(); + ConnVertex vertex5 = new ConnVertex(); + ConnVertex vertex6 = new ConnVertex(); + assertTrue(graph.addEdge(vertex1, vertex2)); + assertTrue(graph.addEdge(vertex2, vertex3)); + assertTrue(graph.addEdge(vertex1, vertex3)); + assertTrue(graph.addEdge(vertex4, vertex5)); + assertTrue(graph.connected(vertex1, vertex3)); + assertTrue(graph.connected(vertex4, vertex5)); + assertFalse(graph.connected(vertex1, vertex4)); + + graph.optimize(); + assertTrue(graph.removeEdge(vertex1, vertex3)); + assertTrue(graph.connected(vertex1, vertex3)); + assertTrue(graph.connected(vertex4, vertex5)); + assertFalse(graph.connected(vertex1, vertex4)); + assertTrue(graph.removeEdge(vertex1, vertex2)); + assertFalse(graph.connected(vertex1, vertex3)); + assertTrue(graph.connected(vertex4, vertex5)); + assertFalse(graph.connected(vertex1, vertex4)); + + assertEquals(Collections.singleton(vertex3), new HashSet(graph.adjacentVertices(vertex2))); + assertTrue(graph.adjacentVertices(vertex1).isEmpty()); + assertTrue(graph.adjacentVertices(vertex6).isEmpty()); + } +} diff --git a/src/test/java/com/github/btrekkie/connectivity/test/SumAndMax.java b/src/test/java/com/github/btrekkie/connectivity/test/SumAndMax.java new file mode 100644 index 000000000..393c3599f --- /dev/null +++ b/src/test/java/com/github/btrekkie/connectivity/test/SumAndMax.java @@ -0,0 +1,39 @@ +package com.github.btrekkie.connectivity.test; + +import com.github.btrekkie.connectivity.Augmentation; + +/** Stores two values: a sum and a maximum. Used for testing augmentation in ConnGraph. */ +class SumAndMax { + /** An Augmentation that combines two SumAndMaxes into one. */ + public static final Augmentation AUGMENTATION = new Augmentation() { + @Override + public Object combine(Object value1, Object value2) { + SumAndMax sumAndMax1 = (SumAndMax)value1; + SumAndMax sumAndMax2 = (SumAndMax)value2; + return new SumAndMax(sumAndMax1.sum + sumAndMax2.sum, Math.max(sumAndMax1.max, sumAndMax2.max)); + } + }; + + public final int sum; + + public final int max; + + public SumAndMax(int sum, int max) { + this.sum = sum; + this.max = max; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof SumAndMax)) { + return false; + } + SumAndMax sumAndMax = (SumAndMax)obj; + return sum == sumAndMax.sum && max == sumAndMax.max; + } + + @Override + public int hashCode() { + return 31 * sum + max; + } +} diff --git a/target/dynamic-connectivity-0.1.0-jar-with-dependencies.jar b/target/dynamic-connectivity-0.1.0-jar-with-dependencies.jar new file mode 100644 index 000000000..509855f6b Binary files /dev/null and b/target/dynamic-connectivity-0.1.0-jar-with-dependencies.jar differ diff --git a/target/dynamic-connectivity-0.1.0.jar b/target/dynamic-connectivity-0.1.0.jar new file mode 100644 index 000000000..d097c59ba Binary files /dev/null and b/target/dynamic-connectivity-0.1.0.jar differ