Compare commits

...

2 Commits
0.1.0 ... 0.1.2

Author SHA1 Message Date
William Jacobs
05d678a189 Split removeEdge and optimize() into smaller methods
This moves a bit of functionality from ConnGraph.removeEdge and ConnGraph.optimize() into new methods, in order to improve readability.
2019-03-16 14:42:36 -04:00
William Jacobs
1536209eb3 Optimization: don't push forest edges if there are no non-forest edges
This optimizes the process of searching for a replacement edge to refrain from pushing forest edges down when a tree does not have any same-level non-forest edges. If a tree doesn't have any such edges, then there definitely isn't a replacement edge at that level, so we can avoid doing extra work.

Interestingly, with this optimization, ConnGraph reduces to a single Euler tour forest if the graph is a forest, with addEdge and removeEdge taking O(log N) (non-amortized) time with high probability.
2019-03-06 22:24:50 -05:00
6 changed files with 70 additions and 51 deletions

View File

@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.btrekkie.connectivity</groupId>
<artifactId>dynamic-connectivity</artifactId>
<version>0.1.0</version>
<version>0.1.2</version>
<name>dynamic-connectivity</name>
<description>Data structure for dynamic connectivity in undirected graphs</description>
<build>
@@ -43,7 +43,7 @@
<dependency>
<groupId>com.github.btrekkie</groupId>
<artifactId>RedBlackNode</artifactId>
<version>1.0.0</version>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>

View File

@@ -75,8 +75,8 @@ import java.util.Map;
* 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."
* 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/ .
@@ -258,7 +258,7 @@ public class ConnGraph {
/**
* Equivalent implementation is contractual.
*
* This method is useful for when a node's lists (graphListHead or forestListHead) or a vertex's arbitrary visit
* 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) {
@@ -505,6 +505,33 @@ public class ConnGraph {
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.
@@ -794,31 +821,8 @@ public class ConnGraph {
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();
removeForestEdge(levelEdge);
}
edge.eulerTourEdge = null;
@@ -829,17 +833,22 @@ public class ConnGraph {
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;
// 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
@@ -1039,18 +1048,10 @@ public class ConnGraph {
}
/**
* 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).
* 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.
*/
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
private void optimizeForestEdges() {
for (VertexInfo info : vertexInfo.values()) {
int level = maxLogVertexCountSinceRebuild;
EulerTourVertex vertex;
@@ -1116,8 +1117,13 @@ public class ConnGraph {
level++;
}
}
}
// Push each non-forest edge down to the lowest level where the endpoints are in the same connected component
/**
* 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);
@@ -1180,4 +1186,17 @@ public class ConnGraph {
}
}
}
/**
* 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();
}
}

Binary file not shown.