Compare commits

...

2 Commits
0.1.1 ... 0.1.3

Author SHA1 Message Date
William Jacobs
458cdd3ddd Fixed size calculation in optimizeForestEdges()
This fixes the code in ConnGraph.optimizeForestEdges() that computes the resulting size after combining two Euler tour trees into one. Technically, the old behavior was still correct, but only by accident; this change is still kind of a bug fix.
2019-03-18 13:21:26 -04:00
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
6 changed files with 62 additions and 47 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.1</version>
<version>0.1.3</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;
@@ -1044,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;
@@ -1077,20 +1073,21 @@ public class ConnGraph {
EulerTourVertex lowerVertex1 = vertex;
EulerTourVertex lowerVertex2 = edge.vertex2;
for (int lowerLevel = level - 1; lowerLevel > 0; lowerLevel--) {
int size = 0;
// Compute the total size if we combine the Euler tour trees
int combinedSize = 1;
if (lowerVertex1.lowerVertex != null) {
size = lowerVertex1.lowerVertex.arbitraryVisit.root().size;
combinedSize += lowerVertex1.lowerVertex.arbitraryVisit.root().size;
} else {
size = 1;
combinedSize++;
}
if (lowerVertex2.lowerVertex != null) {
size += lowerVertex2.lowerVertex.arbitraryVisit.root().size;
combinedSize += lowerVertex2.lowerVertex.arbitraryVisit.root().size;
} else {
size++;
combinedSize++;
}
// X EulerTourVertices = (2 * X - 1) EulerTourNodes
if (size > 2 * (1 << lowerLevel) - 1) {
if (combinedSize > 2 * (1 << lowerLevel) - 1) {
break;
}
@@ -1121,8 +1118,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);
@@ -1185,4 +1187,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.