All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy