
org.jgrapht.alg.connectivity.TreeDynamicConnectivity Maven / Gradle / Ivy
/*
* (C) Copyright 2020-2021, by Timofey Chudakov and Contributors.
*
* JGraphT : a free Java graph-theory library
*
* See the CONTRIBUTORS.md file distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the
* GNU Lesser General Public License v2.1 or later
* which is available at
* http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
*
* SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
*/
package org.jgrapht.alg.connectivity;
import org.jgrapht.util.*;
import java.util.*;
import java.util.stream.*;
import static org.jgrapht.util.AVLTree.TreeNode;
import static org.jgrapht.util.DoublyLinkedList.ListNode;
/**
* Data structure for storing dynamic trees and querying node connectivity
*
* This data structure supports the following operations:
*
* - Adding an element in $\mathcal{O}(\log 1)$
* - Checking if an element in present in $\mathcal{O}(1)$
* - Connecting two elements in $\mathcal{O}(\log n)$
* - Checking if two elements are connected in $\mathcal{O}(\log n)$
* - Removing connection between two nodes in $\mathcal{O}(\log n)$
* - Removing an element in $\mathcal{O}(deg(element)\cdot\log n + 1)$
*
*
* This data structure doesn't allow to store graphs with cycles. Also, the edges are considered to
* be undirected. The memory complexity is linear in the number of inserted elements. The
* implementation is based on the
* Euler tour technique.
*
* For the description of the Euler tour data structure, we refer to the Monika Rauch Henzinger,
* Valerie King: Randomized dynamic graph algorithms with polylogarithmic time per operation. STOC
* 1995: 519-527
*
* @param element type
* @author Timofey Chudakov
*/
public class TreeDynamicConnectivity
{
/**
* Mapping from tree minimums to the trees they're stored in. This map contains one entry per
* each tree, which has at least two nodes.
*/
private Map, AVLTree> minToTreeMap;
/**
* Mapping from the user specified values to the internal nodes they're represented by
*/
private Map nodeMap;
/**
* Mapping from zero-degree nodes to their trees. This map contains one entry for each
* zero-degree node
*/
private Map> singletonNodes;
/**
* Constructs a new {@code TreeDynamicConnectivity} instance
*/
public TreeDynamicConnectivity()
{
minToTreeMap = new HashMap<>();
nodeMap = new HashMap<>();
singletonNodes = new HashMap<>();
}
/**
* Adds an {@code element} to this data structure. If the {@code element} has been added before,
* this method returns {@code false} and has no effect.
*
* This method has $\mathcal{O}(\log 1)$ running time complexity
*
* @param element an element to add
* @return {@code true} upon successful modification, {@code false} otherwise
*/
public boolean add(T element)
{
if (contains(element)) {
return false;
}
AVLTree newTree = new AVLTree<>();
Node node = new Node(element);
nodeMap.put(element, node);
singletonNodes.put(node, newTree);
return true;
}
/**
* Removes the {@code element} from this data structure. This method has no effect if the
* {@code element} hasn't been added to this data structure
*
* This method has $\mathcal{O}(deg(element)\cdot\log n + 1)$ running time complexity
*
* @param element an element to remove
* @return {@code true} upon successful modification, {@code false} otherwise
*/
public boolean remove(T element)
{
if (!contains(element)) {
return false;
}
Node node = getNode(element);
while (!node.isSingleton()) {
T targetValue = node.arcs.getLast().target.value;
cut(element, targetValue);
}
nodeMap.remove(element);
singletonNodes.remove(node);
return true;
}
/**
* Checks if this data structure contains the {@code element}.
*
* This method has expected $\mathcal{O}(1)$ running time complexity
*
* @param element an element to check presence of
* @return {@code true} if the {@code element} is stored in this data structure, {@code false}
* otherwise
*/
public boolean contains(T element)
{
return nodeMap.containsKey(element);
}
/**
* Adds an edge between the {@code first} and {@code second} elements. The method has no effect
* if the elements are already connected by some path, i.e. belong to the same tree. In the case
* some of the nodes haven't been added before, they're added to this data structure.
*
* This method has $\mathcal{O}(\log n)$ running time complexity
*
* @param first an element
* @param second an element
* @return {@code true} upon successful modification, {@code false} otherwise
*/
public boolean link(T first, T second)
{
/*
* Example: we have two trees [1 - 2] and [3 - 4 - 5]
*
* Euler tour of the first tree: [1 - 2] Euler tour of the second tree: [3 - 4 - 5 - 4]
*
* By invariant used in this implementation, we do not return to the start node
*
* Suppose, that we have a request: link(1, 5)
*/
addIfAbsent(first);
addIfAbsent(second);
if (connected(first, second)) {
return false;
}
Node firstNode = getNode(first);
Node secondNode = getNode(second);
AVLTree firstTree = getTree(firstNode);
AVLTree secondTree = getTree(secondNode);
minToTreeMap.remove(firstTree.getMin());
minToTreeMap.remove(secondTree.getMin());
/*
* First we make the nodes 1 and 5 the roots of the corresponding trees:
*
* [1 - 2] --> [1 - 2] [3 - 4 - 5 - 4] --> [5 - 4 - 3 - 4]
*/
makeRoot(firstTree, firstNode);
makeRoot(secondTree, secondNode);
/*
* Add one more occurrence for the first element to the second tree:
*
* [5 - 4 - 3 - 4] --> [1 - 5 - 4 - 3 - 4]
*/
TreeNode newFirstOccurrence = secondTree.addMin(first);
Arc newFirstArc = new Arc(secondNode, newFirstOccurrence);
if (firstNode.isSingleton()) {
// newFirstArc becomes the first arc of the first node
singletonNodes.remove(firstNode);
firstNode.addArcLast(newFirstArc);
} else {
/*
* Since second element will be not the only element adjacent to the first element, we
* are going to insert the arc to the second element into the circular list of arcs of
* the first node
*
* Since first element is a root currently, we can find out the last outgoing arc by
* simply checking the last element in its Euler tour
*
* In the example above, the last arc is (1, 2), so we're going to append a new arc (1,
* 5) after it.
*
* By invariant we're maintaining, a subtree tour computed by following the arc is
* placed after the arc tree node reference.
*
* For example, the first node will have 2 arcs: (1, 2) and (1, 5). If we follow the arc
* (1, 2), a subtour will be just [2]. If we follow the arc (1, 5), the subtour will be
* [5 - 4 - 3 - 4 - 5]. So, the arc will have the following tree node references
*
* (1, 2) [(1) - 2 - 1 - 5 - 4 - 3 - 4 - 5] | | ------------ (1, 5) [1 - 2 - (1) - 5 - 4
* - 3 - 4 - 5] | | --------------------
*
* If we decide to make the arc (1, 5) the first arc, the method will just take the tree
* node reference of the arc (1, 5) and will place it at the first place (1, 5) [(1) - 5
* - 4 - 3 - 4 - 5 - 1 - 2] | | ------------ (1, 2) [1 - 5 - 4 - 3 - 4 - 5 - (1) - 2] |
* | ------------------------------------
*/
T lastChild = firstTree.getMax().getValue();
Node lastChildNode = getNode(lastChild);
Arc arcToLastChild = firstNode.getArcTo(lastChildNode);
firstNode.addArcAfter(arcToLastChild, newFirstArc);
}
/*
* Add one more occurrence for the second element to the second tree:
*
* [1 - 5 - 4 - 3 - 4] -> [1 - 5 - 4 - 3 - 4 - 5]
*
*/
TreeNode newSecondOccurrence = secondTree.addMax(second);
Arc newSecondArc = new Arc(firstNode, newSecondOccurrence);
if (secondNode.isSingleton()) {
// newSecondArc becomes the first arc of the second node
singletonNodes.remove(secondNode);
secondNode.addArcLast(newSecondArc);
} else {
/*
* Similarly to the first case, we need to find out the last arc of the second node. At
* this moment, the second tree looks like this:
*
* [1 - 5 - 4 - 3 - 4 - 5]
*
* The only arc of the node 5 is (5, 4). After the link operation, the node five will
* have one more arc: (5, 1). The tree node references for the node 5 will look like
* this:
*
* (5, 4) [1 - 2 - 1 - (5) - 4 - 3 - 4 - 5] | | -------------------------- (5, 1) [1 - 2
* - 1 - 5 - 4 - 3 - 4 - (5)] | | ------------------------------------------
*
* Note that the invariant of the arc tree node references has a circular manner: the
* subtree tour of the arc (5, 1) is [1 - 2 - 1], which is right after the tree node
* reference of the arc (5, 1).
*/
T lastChild = secondTree.getMax().getPredecessor().getValue();
Node lastChildNode = getNode(lastChild);
Arc arcToLastChild = secondNode.getArcTo(lastChildNode);
secondNode.addArcAfter(arcToLastChild, newSecondArc);
}
/*
* Merge the first and the second tree to obtain an Euler tour of the combined tree:
*
* [1 - 2] + [1 - 5 - 4 - 3 - 4 - 5] = [1 - 2 - 1 - 5 - 4 - 3 - 4 - 5]
*/
firstTree.mergeAfter(secondTree);
minToTreeMap.put(firstTree.getMin(), firstTree);
return true;
}
/**
* Checks if the {@code first} and {@code second} belong to the same tree. The method will
* return {@code false} if either of the elements hasn't been added to this data structure
*
* This method has $\mathcal{O}(\log n)$ running time complexity
*
* @param first an element
* @param second an element
* @return {@code true} if the elements belong to the same tree, {@code false} otherwise
*/
public boolean connected(T first, T second)
{
if (!contains(first) || !contains(second)) {
return false;
}
Node firstNode = getNode(first);
if (firstNode.isSingleton()) {
return false;
}
Node secondNode = getNode(second);
if (secondNode.isSingleton()) {
return false;
}
return getTree(firstNode) == getTree(secondNode);
}
/**
* Removes an edge between the {@code first} and {@code second}. This method doesn't have any
* effect if there's no edge between these elements
*
* This method has $\mathcal{O}(\log n)$ running time complexity
*
* @param first an element
* @param second an element
* @return {@code true} upon successful modification, {@code false} otherwise
*/
public boolean cut(T first, T second)
{
if (!connected(first, second)) {
return false;
}
/*
* Suppose, we have a tree [2 - [1] - 5 - 4 - 3], which has the following Euler tour:
*
* [1 - 2 - 1 - 5 - 4 - 3 - 4 - 5]
*
* Let's assume that we received a query: cut(1, 2)
*/
Node firstNode = getNode(first);
Node secondNode = getNode(second);
AVLTree tree = getTree(firstNode);
minToTreeMap.remove(tree.getMin());
/*
* The arcToSecond is (1, 2). The operation of making the arc (1, 2) the last arc will
* transform the tree as follows:
*
* (1, 2) [(1) - 2 - 1 - 5 - 4 - 3 - 4 - 5] --> [1 - 5 - 4 - 3 - 4 - 5 - (1) - 2] | | |
* --------------------------------------------------------------------------
*
* After this operation, a subtree of the arc (1, 2) is at the end of the Euler tour
*/
Arc arcToSecond = firstNode.getArcTo(secondNode);
if (arcToSecond == null) {
throw new IllegalArgumentException(
String.format("Elements {%s} and {%s} are not connected", first, second));
}
makeLastArc(tree, firstNode, arcToSecond);
/*
* Now we remove the subtree of the arc (1, 2) from the Euler tour:
*
* |-------> [1 - 5 - 4 - 3 - 4 - 5 - 1] (left part [1 - 5 - 4 - 3 - 4 - 5 - 1 - 2] -----|
* |-------> [2] (right part)
*/
AVLTree right = tree.splitAfter(arcToSecond.arcTreeNode);
/*
* Removing the last occurrence of the element 1 from the Euler tour:
*
* [1 - 5 - 4 - 3 - 4 - 5 - 1] --> [1 - 5 - 4 - 3 - 4 - 5]
*
* Now the left part is a valid Euler tour
*/
tree.removeMax();
firstNode.removeArc(arcToSecond);
if (!firstNode.isSingleton()) {
minToTreeMap.put(tree.getMin(), tree);
} else {
singletonNodes.put(firstNode, tree);
}
/*
* Removing the last occurrence of the element 2 from the right tree:
*
* [2] -> []
*
* The element 2 becomes an element of zero degree (a singleton node). No arcs means an
* empty tree
*
* That's why we place it to the map for zero degree nodes
*/
Arc secondToFirst = secondNode.getArcTo(firstNode);
right.removeMax();
secondNode.removeArc(secondToFirst);
if (!secondNode.isSingleton()) {
minToTreeMap.put(right.getMin(), right);
} else {
singletonNodes.put(secondNode, right);
}
return true;
}
/**
* Makes the {@code node} the root of the tree. In practice, this means that the value of the
* {@code node} is the first in the Euler tour
*
* @param tree a tree the {@code node} is stored in
* @param node a node to make a root
*/
private void makeRoot(AVLTree tree, Node node)
{
if (node.arcs.isEmpty()) {
return;
}
makeFirstArc(tree, node.arcs.get(0));
}
/**
* Makes the {@code arc} the first arc traversed by the Euler tour
*
* @param tree corresponding binary tree the Euler tour is stored in
* @param arc an arc to use for tree re-rooting
*/
private void makeFirstArc(AVLTree tree, Arc arc)
{
AVLTree right = tree.splitBefore(arc.arcTreeNode);
tree.mergeBefore(right);
}
/**
* Makes the {@code arc} the last arc of the {@code node} according to the Euler tour
*
* @param tree corresponding binary tree the Euler tour is stored in
* @param node a new root node
* @param arc an arc incident to the {@code node}
*/
private void makeLastArc(AVLTree tree, Node node, Arc arc)
{
if (node.arcs.size() == 1) {
makeRoot(tree, node);
} else {
Arc nextArc = node.getNextArc(arc);
makeFirstArc(tree, nextArc);
}
}
/**
* Returns an internal representation of the {@code element}
*
* @param element a user specified node element
* @return an internal representation of the {@code element}
*/
private Node getNode(T element)
{
return nodeMap.get(element);
}
/**
* Returns a binary tree, which contains an Euler tour of the tree the {@code node} belong to
*
* @param node a node
* @return a corresponding binary tree an Euler tour is stored in
*/
private AVLTree getTree(Node node)
{
if (node.isSingleton()) {
return singletonNodes.get(node);
}
return minToTreeMap.get(node.arcs.get(0).arcTreeNode.getTreeMin());
}
/**
* Adds the {@code element} to this data structure if it is not already present
*
* @param element a user specified element
*/
private void addIfAbsent(T element)
{
if (!contains(element)) {
add(element);
}
}
/**
* An internal representation of the tree nodes.
*
* Keeps track of the node values and outgoing arcs. The outgoing arcs are placed according to
* the order they are traversed in the Euler tour
*/
private class Node
{
/**
* Node value
*/
T value;
/**
* Arcs list
*/
DoublyLinkedList arcs;
/**
* Target node to arc mapping
*/
Map targetMap;
/**
* Constructs a new node
*
* @param value a user specified element to store in this node
*/
public Node(T value)
{
this.value = value;
arcs = new DoublyLinkedList<>();
targetMap = new HashMap<>();
}
/**
* Removes the {@code arc} from the arc list
*
* @param arc an arc to remove
*/
void removeArc(Arc arc)
{
arcs.removeNode(arc.listNode);
arc.listNode = null;
targetMap.remove(arc.target);
}
/**
* Append the {@code arc} to the arc list
*
* @param arc an arc to add
*/
void addArcLast(Arc arc)
{
arc.listNode = arcs.addElementLast(arc);
targetMap.put(arc.target, arc);
}
/**
* Inserts the {@code newArc} in the arc list after the {@code arc}
*
* @param arc an arc already stored in the arc list
* @param newArc a new arc to add to the arc list
*/
void addArcAfter(Arc arc, Arc newArc)
{
newArc.listNode = arcs.addElementBeforeNode(arc.listNode.getNext(), newArc);
targetMap.put(newArc.target, newArc);
}
/**
* Returns an arc, which target is equal to the {@code node}
*
* @param node a target of the returned arc
* @return an arc, which target is equal to the {@code node}
*/
Arc getArcTo(Node node)
{
return targetMap.get(node);
}
/**
* Returns an arc which is stored right after the {@code arc}. The result may be equal to
* the {@code arc}
*
* @param arc an arc stored in the arc list
* @return an arc which is stored right after the {@code arc}
*/
Arc getNextArc(Arc arc)
{
return arc.listNode.getNext().getValue();
}
/**
* Checks if this node is a zero-degree node
*
* @return {@code true} if this node is a singleton node, {@code false otherwise}
*/
public boolean isSingleton()
{
return arcs.isEmpty();
}
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
return String
.format(
"{%s} -> [%s]", value,
arcs
.stream().map(a -> a.target.value.toString())
.collect(Collectors.joining(",")));
}
}
/**
* An internal representation of the tree edges.
*
* Two arcs are created for every existing tree edge. This complies with the way an Euler tour
* is constructed.
*/
private class Arc
{
/**
* The target of this arc
*/
Node target;
/**
* A list node this arc is stored in. This is needed for constant time query time on the
* doubly linked list.
*/
ListNode listNode;
/**
* The occurrence of the source node, which precedes the subtree Euler tour stored in the
* binary tree
*/
TreeNode arcTreeNode;
/**
* Constructs a new arc with the target node {@code target} and the tree node reference
* {@code arcTreeNode}
*
* @param target target node of this arc
* @param arcTreeNode source tree node reference
*/
public Arc(Node target, TreeNode arcTreeNode)
{
this.target = target;
this.arcTreeNode = arcTreeNode;
}
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
return String.format("{%s} -> {%s}", arcTreeNode.getValue(), target.value);
}
}
}