diff --git a/src/main/java/com/github/btrekkie/LICENSE b/src/main/java/com/github/btrekkie/LICENSE
new file mode 100644
index 000000000..b9f0f2eb5
--- /dev/null
+++ b/src/main/java/com/github/btrekkie/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 btrekkie
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/main/java/com/github/btrekkie/README.md b/src/main/java/com/github/btrekkie/README.md
new file mode 100644
index 000000000..d7a917212
--- /dev/null
+++ b/src/main/java/com/github/btrekkie/README.md
@@ -0,0 +1,9 @@
+Original readme --> https://github.com/btrekkie/dynamic-connectivity/
+
+I am experimenting with using dynamic connectivity to help with Baritone builder 2
+
+https://en.wikipedia.org/wiki/Dynamic_connectivity
+
+If the experiments in this repo go well, I want to integrate it
+into https://github.com/cabaletta/baritone/tree/builder-2/src/main/java/baritone/builder (i think MIT is
+compatible with LGPL so this is ok)
\ 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..4c9a4f8d7
--- /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..1b8ed65e3
--- /dev/null
+++ b/src/main/java/com/github/btrekkie/connectivity/ConnEdge.java
@@ -0,0 +1,65 @@
+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..2a813153f
--- /dev/null
+++ b/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java
@@ -0,0 +1,1239 @@
+package com.github.btrekkie.connectivity;
+
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.longs.LongSet;
+import it.unimi.dsi.fastutil.longs.LongSets;
+
+import java.util.*;
+
+/**
+ * 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
+ * https://ocw.mit.edu/courses/6-851-advanced-data-structures-spring-2012/resources/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 pointers 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 hash lookups 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 Long2ObjectOpenHashMap vertexInfo = new Long2ObjectOpenHashMap<>();
+
+ /**
+ * 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;
+
+ /**
+ * 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(long 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++;
+ }
+ 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(long vertex) {
+ vertexInfo.remove(vertex);
+ 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 an EulerTourVertex's lists (graphListHead or forestListHead) or 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);
+ }
+
+ /**
+ * Removes the specified edge from the Euler tour forest F_i.
+ */
+ private void removeForestEdge(EulerTourEdge edge) {
+ EulerTourNode firstNode;
+ EulerTourNode secondNode;
+ if (edge.visit1.compareTo(edge.visit2) < 0) {
+ firstNode = edge.visit1;
+ secondNode = edge.visit2;
+ } else {
+ firstNode = edge.visit2;
+ secondNode = edge.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();
+ }
+
+ /**
+ * 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, long destVertex) {
+ srcInfo.edges.put(destVertex, edge);
+ }
+
+ @Deprecated
+ public boolean addEdge(ConnVertex connVertex1, ConnVertex connVertex2) {
+ return addEdge(connVertex1.getIdentity(), connVertex2.getIdentity());
+ }
+
+ /**
+ * 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(long connVertex1, long 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, long destVertex) {
+ ConnEdge edge = srcInfo.edges.remove(destVertex);
+ return edge;
+ }
+
+ @Deprecated
+ public boolean removeEdge(ConnVertex vertex1, ConnVertex vertex2) {
+ return removeEdge(vertex1.getIdentity(), vertex2.getIdentity());
+ }
+
+ /**
+ * 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(long vertex1, long 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) {
+ for (EulerTourEdge levelEdge = edge.eulerTourEdge; levelEdge != null; levelEdge = levelEdge.higherEdge) {
+ removeForestEdge(levelEdge);
+ }
+ 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();
+
+ // Optimization: if hasGraphEdge is false for one of the roots, then there definitely isn't a
+ // replacement edge at this level
+ if (root1.hasGraphEdge && root2.hasGraphEdge) {
+ 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;
+ }
+
+ @Deprecated
+ public boolean connected(ConnVertex vertex1, ConnVertex vertex2) {
+ return connected(vertex1.getIdentity(), vertex2.getIdentity());
+ }
+
+ /**
+ * Returns whether the specified vertices are connected - whether there is a path between them. Returns true if
+ * vertex1 == vertex2. This method takes O(log N) time with high probability.
+ */
+ public boolean connected(long vertex1, long 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 LongSet adjacentVertices(long vertex) {
+ VertexInfo info = vertexInfo.get(vertex);
+ if (info != null) {
+ return info.edges.keySet();
+ } else {
+ return LongSets.emptySet();
+ }
+ }
+
+ @Deprecated
+ public Object setVertexAugmentation(ConnVertex connVertex, Object vertexAugmentation) {
+ return setVertexAugmentation(connVertex.getIdentity(), vertexAugmentation);
+ }
+
+ /**
+ * 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(long 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;
+ }
+
+ @Deprecated
+ public Object removeVertexAugmentation(ConnVertex connVertex) {
+ return removeVertexAugmentation(connVertex.getIdentity());
+ }
+
+ /**
+ * 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(long 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;
+ }
+
+ @Deprecated
+ public Object getVertexAugmentation(ConnVertex vertex) {
+ return getVertexAugmentation(vertex.getIdentity());
+ }
+
+ /**
+ * 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(long vertex) {
+ assertIsAugmented();
+ VertexInfo info = vertexInfo.get(vertex);
+ if (info != null) {
+ return info.vertex.augmentation;
+ } else {
+ return null;
+ }
+ }
+
+ @Deprecated
+ public Object getComponentAugmentation(ConnVertex vertex) {
+ return getComponentAugmentation(vertex.getIdentity());
+ }
+
+ /**
+ * 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(long vertex) {
+ assertIsAugmented();
+ VertexInfo info = vertexInfo.get(vertex);
+ if (info != null) {
+ return info.vertex.arbitraryVisit.root().augmentation;
+ } else {
+ return null;
+ }
+ }
+
+ @Deprecated
+ public boolean vertexHasAugmentation(ConnVertex vertex) {
+ return vertexHasAugmentation(vertex.getIdentity());
+ }
+
+ /**
+ * Returns whether the specified vertex has any associated augmentation. At present, this method takes constant
+ * expected time. Contrast with componentHasAugmentation.
+ */
+ public boolean vertexHasAugmentation(long vertex) {
+ assertIsAugmented();
+ VertexInfo info = vertexInfo.get(vertex);
+ if (info != null) {
+ return info.vertex.hasAugmentation;
+ } else {
+ return false;
+ }
+ }
+
+ @Deprecated
+ public boolean componentHasAugmentation(ConnVertex vertex) {
+ return componentHasAugmentation(vertex.getIdentity());
+ }
+
+ /**
+ * 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(long 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 Long2ObjectOpenHashMap<>();
+ maxLogVertexCountSinceRebuild = 0;
+ }
+
+ /**
+ * Pushes all forest edges as far down as possible, so that any further pushes would violate the constraint on the
+ * size of connected components. The current implementation of this method takes O(V log^2 V) time.
+ */
+ private void optimizeForestEdges() {
+ 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--) {
+ // Compute the total size if we combine the Euler tour trees
+ int combinedSize = 1;
+ if (lowerVertex1.lowerVertex != null) {
+ combinedSize += lowerVertex1.lowerVertex.arbitraryVisit.root().size;
+ } else {
+ combinedSize++;
+ }
+ if (lowerVertex2.lowerVertex != null) {
+ combinedSize += lowerVertex2.lowerVertex.arbitraryVisit.root().size;
+ } else {
+ combinedSize++;
+ }
+
+ // X EulerTourVertices = (2 * X - 1) EulerTourNodes
+ if (combinedSize > 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++;
+ }
+ }
+ }
+
+ /**
+ * Pushes each non-forest edge down to the lowest level where the endpoints are in the same connected component. The
+ * current implementation of this method takes O(V log V + E log V log log V) time.
+ */
+ private void optimizeGraphEdges() {
+ 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;
+ }
+ }
+ }
+
+ /**
+ * 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();
+ optimizeForestEdges();
+ optimizeGraphEdges();
+ }
+}
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..4e562f73f
--- /dev/null
+++ b/src/main/java/com/github/btrekkie/connectivity/ConnVertex.java
@@ -0,0 +1,47 @@
+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.
+ */
+ 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 long hash;
+
+ public ConnVertex() {
+ hash = random.get().nextLong();
+ }
+
+ /**
+ * 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.nextLong();
+ }
+
+ @Override
+ public int hashCode() {
+ throw new UnsupportedOperationException();
+ }
+
+ public long getIdentity(){
+ 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..356f98733
--- /dev/null
+++ b/src/main/java/com/github/btrekkie/connectivity/EulerTourNode.java
@@ -0,0 +1,120 @@
+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..e2412e189
--- /dev/null
+++ b/src/main/java/com/github/btrekkie/connectivity/EulerTourVertex.java
@@ -0,0 +1,47 @@
+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..ff9ff0b08
--- /dev/null
+++ b/src/main/java/com/github/btrekkie/connectivity/VertexInfo.java
@@ -0,0 +1,28 @@
+package com.github.btrekkie.connectivity;
+
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+
+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 Long2ObjectOpenHashMap edges = new Long2ObjectOpenHashMap<>();
+
+ public VertexInfo(EulerTourVertex vertex) {
+ this.vertex = vertex;
+ }
+}
diff --git a/src/main/java/com/github/btrekkie/optimization_ideas.txt b/src/main/java/com/github/btrekkie/optimization_ideas.txt
new file mode 100644
index 000000000..6feb84b0d
--- /dev/null
+++ b/src/main/java/com/github/btrekkie/optimization_ideas.txt
@@ -0,0 +1,124 @@
+Thoughts concerning optimization:
+
+- I should not optimize until I have access to real-world input samples. Any
+ optimizations are bound to result in increased code complexity, which is not
+ worth it unless I can demonstrate a non-trivial reduction in running time in a
+ real-world setting.
+- I should profile the program and see if there are any quick wins.
+- Most obviously, I could implement the optimization of using a B-tree in the
+ top level, and see what effect this has on performance. It would definitely
+ improve the query time, and hopefully it wouldn't affect the update time too
+ much.
+- If I do implement the B-tree optimization, I should also store a binary search
+ tree representation of the same forest in the top level, for the user-defined
+ augmentation. That way, updates will require O(log N) calls to
+ Augmentation.combine, rather than O(log^2 N / log log N) calls. We have no
+ control over how long Augmentation.combine takes, so we should minimize calls
+ to that method. It will take a bit of care to ensure that fetching the
+ augmentation for a connected component takes O(log N / log log N) time,
+ however.
+- Alternatively, in each B-tree node, we could store a binary search tree that
+ combines the augmentation information for the items in that node. This might
+ even benefit ConnGraphs that do not have user-supplied augmentation.
+- Actually, there is a way of speeding up queries by an O(log log N) factor that
+ should be cleaner and easier to implement than a B-tree. We could change each
+ Euler tour node in the top level to store its kth ancestor (e.g. if k = 3,
+ then each node stores a pointer to its great grandparent). Each time we change
+ a node's parent, we have to change the pointers for all of the descendants
+ that are less than k levels below the node - up to 2^k - 1 nodes. Since there
+ are O(log N) parent pointer changes per update, and updates already take
+ O(log^2 N) amortized time, we can afford a k value of up to lg lg N + O(1)
+ (but interestingly, not O(log log N)). However, I think this would be
+ significantly slower than a B-tree in practice.
+- If I don't implement the B-tree optimization, there are a couple of small
+ optimizations I could try. First, I could move the field
+ EulerTourNode.augmentationFunc to EulerTourVertex, in order to save a little
+ bit of space. Second, I could create EulerTourNode and EulerTourVertex
+ subclasses specifically for the top level, and offload the fields related to
+ user augmentation to those subclasses. These changes seem inelegant, but it
+ might be worth checking the effect they have on performance.
+- I looked at the heuristics recommended in
+ http://people.csail.mit.edu/karger/Papers/impconn.pdf (Iyer, et al. (2001): An
+ Experimental Study of Poly-Logarithmic Fully-Dynamic Connectivity Algorithms).
+ The first heuristic is: before pushing down all of the same-level forest
+ edges, which is an expensive operation, sample O(log N) same-level non-forest
+ edges to see if we can get lucky and find a replacement edge without pushing
+ anything. The second heuristic is to refrain from pushing edges in
+ sufficiently small components.
+
+ The first heuristic seems reasonable. However, I don't get the second
+ heuristic. The concept seems to be basically the same as the first - don't
+ push down any edges if we can cheaply find a replacement edge. However, the
+ execution is cruder. Rather than limiting the number of edges to search, which
+ is closely related to the cost of the search, the second heuristic is based on
+ the number of vertices, which is not as closely related.
+- I have a few ideas for implementing this first heuristic, which could be
+ attempted and their effects on performance measured. The first is that to
+ sample the graph edges in a semi-random order, I could augment each Euler tour
+ node with the sum across all canonical visits to vertices of the number of
+ adjacent same-level graph edges. Then, to obtain a sample, we select each
+ vertex with a probability proportional to this adjacency number. This is
+ fairly straightforward: at each node, we decide to go left, go right, or use
+ the current vertex in proportion to the augmentation.
+
+ This is not exactly random, because after we select a vertex, we choose an
+ arbitrary adjacent edge. However, it seems close enough, and it's not easy to
+ do better.
+- After sampling an edge, we should remove it from the adjacency list, to avoid
+ repeatedly sampling the same edge. We should then store it in an array of
+ edges we sampled, so that later, we can either re-add the edges to the
+ adjacency lists or push all of them down as necessary.
+- If the sampling budget (i.e. the number of edges we intend to sample) is at
+ least the number of same-level graph edges, then we should forgo pushing down
+ any edges, regardless of whether we find a replacement edge.
+- We don't need to sample edges from the smaller of the two post-cut trees. We
+ can sample them from the larger one if it has fewer same-level graph edges.
+ This increases our chances of finding a replacement edge if there is one. As
+ long as we don't push down any forest edges in the larger tree, we're safe.
+- With an extra augmentation, we can determine whether there is probably a
+ replacement edge. This helps us because if there is probably no replacement
+ edge, then we can save some time by skipping edge sampling entirely. (If the
+ sampling budget is at least the number of same-level graph edges, then we
+ should also refrain from pushing down any edges, as in a previously mentioned
+ optimization.)
+
+ Assign each ConnEdge a random integer ID. Store the XOR of the IDs of all
+ adjacent same-level graph edges in each of the vertices. Augment the Euler
+ tour trees with the XOR of those values for all canonical visits to vertices.
+ The XOR stored in a post-cut tree's root node is equal to the XOR of all of
+ the replacement edges' IDs, because each non-replacement edge is in two
+ adjacency lists and cancels itself out, while each replacement edge is in one
+ adjacency list. Thus, the XOR is 0 if there is no replacement edge, and it is
+ non-zero with probability 1 - 1 / 2^32 if there is at least one replacement
+ edge.
+- If one of the post-cut trees has a same-level forest edge and the other does
+ not, and the difference in the number of same-level graph edges is not that
+ large, we should favor the one that does not, because it's expensive to push
+ forest edges. Also, there's no need to sample if we pick a tree that has no
+ same-level forest edges.
+- I wonder if there's a good way to estimate the cost of pushing down a given
+ set of forest edges. For example, is there a strong correlation between the
+ number of forest edges and the cost of pushing them? We could use a larger
+ sampling budget the greater this cost is.
+- During sampling, it might help to push down edges whose endpoints are
+ connected in the next-lower level, and to not count them against the sampling
+ budget. By paying for some of the edges, we're able to sample more edges, so
+ we're less likely to have to push down the forest edges.
+
+ The downside is that checking whether the endpoints are in the same connected
+ component takes O(log N) time. To mitigate this, we should refrain from
+ attempting to push down edges until necessary. That is, we should spend the
+ entire sampling budget first, then search the edges we sampled for an edge
+ that we can push down. After finding one such edge, we should sample another
+ edge, then search for another edge to push down, etc.
+- When we are done sampling edges, it might still be beneficial to iterate over
+ the rest of the same-level graph edges in a (semi-)random order. This does
+ increase the amount of time that iteration takes. However, using a random
+ order could be helpful if the sequence of updates has some sort of pattern
+ affecting the location of replacement edges. For example, if there is a
+ tendency to put replacement edges near the end of the Euler tour, or to
+ cluster replacement edges so that they are close to each other in the Euler
+ tour, then random iteration should tend to locate a replacement edge faster
+ than in-order iteration. (If we know from a previously mentioned optimization
+ that there is probably no replacement edge, then we shouldn't bother to
+ iterate over the edges in random order.)
diff --git a/src/main/java/com/github/btrekkie/red_black_node/RedBlackNode.java b/src/main/java/com/github/btrekkie/red_black_node/RedBlackNode.java
new file mode 100644
index 000000000..1484562cd
--- /dev/null
+++ b/src/main/java/com/github/btrekkie/red_black_node/RedBlackNode.java
@@ -0,0 +1,1408 @@
+// from: https://github.com/btrekkie/RedBlackNode/blob/master/src/main/java/com/github/btrekkie/red_black_node/RedBlackNode.java
+// also MIT: https://github.com/btrekkie/RedBlackNode/blob/master/LICENSE
+package com.github.btrekkie.red_black_node;
+
+import java.lang.reflect.Array;
+import java.util.*;
+
+/**
+ * A node in a red-black tree ( https://en.wikipedia.org/wiki/Red%E2%80%93black_tree ). Compared to a class like Java's
+ * TreeMap, RedBlackNode is a low-level data structure. The internals of a node are exposed as public fields, allowing
+ * clients to directly observe and manipulate the structure of the tree. This gives clients flexibility, although it
+ * also enables them to violate the red-black or BST properties. The RedBlackNode class provides methods for performing
+ * various standard operations, such as insertion and removal.
+ *
+ * Unlike most implementations of binary search trees, RedBlackNode supports arbitrary augmentation. By subclassing
+ * RedBlackNode, clients can add arbitrary data and augmentation information to each node. For example, if we were to
+ * use a RedBlackNode subclass to implement a sorted set, the subclass would have a field storing an element in the set.
+ * If we wanted to keep track of the number of non-leaf nodes in each subtree, we would store this as a "size" field and
+ * override augment() to update this field. All RedBlackNode methods (such as "insert" and remove()) call augment() as
+ * necessary to correctly maintain the augmentation information, unless otherwise indicated.
+ *
+ * The values of the tree are stored in the non-leaf nodes. RedBlackNode does not support use cases where values must be
+ * stored in the leaf nodes. It is recommended that all of the leaf nodes in a given tree be the same (black)
+ * RedBlackNode instance, to save space. The root of an empty tree is a leaf node, as opposed to null.
+ *
+ * For reference, a red-black tree is a binary search tree satisfying the following properties:
+ *
+ * - Every node is colored red or black.
+ * - The leaf nodes, which are dummy nodes that do not store any values, are colored black.
+ * - The root is black.
+ * - Both children of each red node are black.
+ * - Every path from the root to a leaf contains the same number of black nodes.
+ *
+ * @param The type of node in the tree. For example, we might have
+ * "class FooNode extends RedBlackNode>".
+ * @author Bill Jacobs
+ */
+public abstract class RedBlackNode> implements Comparable {
+ /**
+ * A Comparator that compares Comparable elements using their natural order.
+ */
+ private static final Comparator> NATURAL_ORDER = new Comparator>() {
+ @Override
+ public int compare(Comparable