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