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

org.chocosolver.util.objects.tree.IntervalTree Maven / Gradle / Ivy

There is a newer version: 4.10.17
Show newest version
/*
 * This file is part of choco-solver, http://choco-solver.org/
 *
 * Copyright (c) 2022, IMT Atlantique. All rights reserved.
 *
 * Licensed under the BSD 4-clause license.
 *
 * See LICENSE file in the project root for full license information.
 */
package org.chocosolver.util.objects.tree;

import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.Consumer;

/**
 * The following class is adapted from: a
 * balanced binary-search tree keyed by
 * Interval objects.
 *
 * 

The underlying data-structure is a red-black tree largely implemented from CLRS (Introduction * to Algorithms, 2nd edition) with the interval-tree extensions mentioned in section 14.3 * * @param - the type of Interval this tree contains * @author Mason M Lai */ public class IntervalTree implements Iterable { private Node root; // The root Node. private final Node nil; // The sentinel Node to represent the absence of a node. private int size; // Size of the tree. Updated by insert() and Node#delete() /** * Constructs an empty IntervalTree. */ public IntervalTree() { nil = new Node(); root = nil; size = 0; } /////////////////////////////////// // Tree -- General query methods // /////////////////////////////////// /** * Whether this IntervalTree is empty or not. */ public boolean isEmpty() { return root.isNil(); } /** * The number of intervals stored in this IntervalTree. */ public int size() { return size; } /** * The Node in this IntervalTree that contains the given Interval.

This method returns the * nil Node if the Interval t cannot be found. * * @param t - the Interval to search for. */ private Node search(T t) { return root.search(t); } /** * Whether or not this IntervalTree contains the given Interval. * * @param t - the Interval to search for */ public boolean contains(T t) { return !search(t).isNil(); } /** * Whether or not this IntervalTree contains the given Interval. * * @param s - the starting time of the Interval to search for * @param e - the ending time of the Interval to search for */ public T get(int s, int e) { Node nod = root.search(s, e); if(!nod.isNil()){ return nod.interval(); } return null; } /** * The minimum value in this IntervalTree * * @return an Optional containing, if it exists, the minimum value in this IntervalTree; * otherwise (i.e., if this is empty), an empty Optional. */ public Optional minimum() { Node n = root.minimumNode(); return n.isNil() ? Optional.empty() : Optional.of(n.interval()); } /** * The maximum value in this IntervalTree * * @return an Optional containing, if it exists, the maximum value in this IntervalTree; * otherwise (i.e., if this is empty), an empty Optional. */ public Optional maximum() { Node n = root.maximumNode(); return n.isNil() ? Optional.empty() : Optional.of(n.interval()); } /** * An Iterator which traverses the tree in ascending order. */ public Iterator iterator() { return new TreeIterator(root); } /** * An Iterator over the Intervals in this IntervalTree that overlap the given Interval * * @param start - the starting point of the overlapping Interval * @param end - the ending point of the overlapping Interval */ public Iterator overlappers(int start, int end) { return root.overlappers(start, end); } public void forAllBelow(int lb, Consumer ex){ Node n = root.minimumNode(); while(!n.isNil() && n.interval.overlaps(Integer.MIN_VALUE, lb + 1)){ ex.accept(n.interval); n = n.successor(); } } public void forAllAbove(int ub, Consumer ex){ Node n = root.maximumNode(); while(!n.isNil() && n.interval.overlaps(ub - 1, Integer.MAX_VALUE)){ ex.accept(n.interval); n = n.predecessor(); } } /////////////////////////////// // Tree -- Insertion methods // /////////////////////////////// /** * Inserts the given value into the IntervalTree.

This method constructs a new Node * containing the given value and places it into the tree. If the value already exists within * the tree, the tree remains unchanged. * * @param t - the value to place into the tree * @return if the value did not already exist, i.e., true if the tree was changed, false if it * was not */ public boolean insert(T t) { Node z = new Node(t); Node y = nil; Node x = root; while (!x.isNil()) { // Traverse the tree down to a leaf. y = x; x.maxEnd = Math.max(x.maxEnd, z.maxEnd); // Update maxEnd on the way down. int cmp = z.compareTo(x); if (cmp == 0) { return false; // Value already in tree. Do nothing. } x = cmp < 0 ? x.left : x.right; } z.parent = y; if (y.isNil()) { root = z; root.blacken(); } else { // Set the parent of n. int cmp = z.compareTo(y); if (cmp < 0) { y.left = z; } else { assert (cmp > 0); y.right = z; } z.left = nil; z.right = nil; z.redden(); z.insertFixup(); } size++; return true; } ////////////////////////////// // Tree -- Deletion methods // ////////////////////////////// /** * Deletes the given value from this IntervalTree.

If the value does not exist, this * IntervalTree remains unchanged. * * @param t - the Interval to delete from the tree * @return whether or not an Interval was removed from this IntervalTree */ public boolean delete(T t) { // Node#delete does nothing and returns return search(t).delete(); // false if t.isNil() } /** * A representation of a node in an interval tree. */ private class Node implements Interval{ /* Most of the "guts" of the interval tree are actually methods called * by nodes. For example, IntervalTree#delete(val) searches up the Node * containing val; then that Node deletes itself with Node#delete(). */ private T interval; private Node parent; private Node left; private Node right; private boolean isBlack; private int maxEnd; /** * Constructs a Node with no data.

This Node has a null interval field, is black, and * has all pointers pointing at itself. This is intended to be used as the sentinel node in * the tree ("nil" in CLRS). */ private Node() { parent = this; left = this; right = this; blacken(); } /** * Constructs a Node containing the given Interval. * * @param interval - the Interval to be contained within this Node */ public Node(T interval) { this.interval = interval; parent = nil; left = nil; right = nil; maxEnd = interval.end(); redden(); } /** * The Interval in this Node */ public T interval() { return interval; } /** * The start of the Interval in this Node */ @Override public int start() { return interval.start(); } /** * The end of the Interval in this Node */ @Override public int end() { return interval.end(); } /////////////////////////////////// // Node -- General query methods // /////////////////////////////////// /** * Searches the subtree rooted at this Node for the given Interval. * * @param t - the Interval to search for * @return the Node with the given Interval, if it exists; otherwise, the sentinel Node */ private Node search(T t) { Node n = this; while (!n.isNil() && t.compareTo(n) != 0) { n = t.compareTo(n) < 0 ? n.left : n.right; } return n; } /** * Searches the subtree rooted at this Node for the given Interval. * * @param s - the starting time of the Interval to search for * @param e - the ending time of the Interval to search for * @return the Node with the given Interval, if it exists; otherwise, the sentinel Node */ private Node search(int s, int e) { Node n = this; int c; while (!n.isNil() && (c = n.compareTo(s, e)) != 0) { n = c > 0 ? n.left : n.right; } return n; } /** * Searches the subtree rooted at this Node for its minimum Interval. * * @return the Node with the minimum Interval, if it exists; otherwise, the sentinel Node */ private Node minimumNode() { Node n = this; while (!n.left.isNil()) { n = n.left; } return n; } /** * Searches the subtree rooted at this Node for its maximum Interval. * * @return the Node with the maximum Interval, if it exists; otherwise, the sentinel Node */ private Node maximumNode() { Node n = this; while (!n.right.isNil()) { n = n.right; } return n; } /** * The successor of this Node. * * @return the Node following this Node, if it exists; otherwise the sentinel Node */ private Node successor() { if (!right.isNil()) { return right.minimumNode(); } Node x = this; Node y = parent; while (!y.isNil() && x == y.right) { x = y; y = y.parent; } return y; } /** * The predecessor of this Node. * * @return the Node preceding this Node, if it exists; otherwise the sentinel Node */ private Node predecessor() { if (!left.isNil()) { return left.maximumNode(); } Node x = this; Node y = parent; while (!y.isNil() && x == y.left) { x = y; y = y.parent; } return y; } /////////////////////////////////////// // Node -- Overlapping query methods // /////////////////////////////////////// /** * Returns the minimum Node from this Node's subtree that overlaps the given Interval. * * @param start - the starting point of the Interval to consider * @param end - the ending point of the Interval to consider * @return the minimum Node from this Node's subtree that overlaps the Interval t, if one * exists; otherwise, the sentinel Node */ private Node minimumOverlappingNode(int start, int end) { Node result = nil; Node n = this; if (!n.isNil() && n.maxEnd > start) { while (true) { if (n.overlaps(start, end)) { // This node overlaps. There may be a lesser overlapper // down the left subtree. No need to consider the right // as all overlappers there will be greater. result = n; n = n.left; if (n.isNil() || n.maxEnd <= start) { // Either no left subtree, or nodes can't overlap. break; } } else { // This node doesn't overlap. // Check the left subtree if an overlapper may be there Node left = n.left; if (!left.isNil() && left.maxEnd > start) { n = left; } else { // Left subtree cannot contain an overlapper. Check the // right sub-tree. if (n.start() >= end) { // Nothing in the right subtree can overlap break; } n = n.right; if (n.isNil() || n.maxEnd <= start) { // No right subtree, or nodes can't overlap. break; } } } } } return result; } /** * Returns the minimum Node from this Node's subtree that overlaps the given Interval. * * @param t - the given Interval * @return the minimum Node from this Node's subtree that overlaps the Interval t, if one * exists; otherwise, the sentinel Node */ private Node minimumOverlappingNode(T t) { Node result = nil; Node n = this; if (!n.isNil() && n.maxEnd > t.start()) { while (true) { if (n.overlaps(t)) { // This node overlaps. There may be a lesser overlapper // down the left subtree. No need to consider the right // as all overlappers there will be greater. result = n; n = n.left; if (n.isNil() || n.maxEnd <= t.start()) { // Either no left subtree, or nodes can't overlap. break; } } else { // This node doesn't overlap. // Check the left subtree if an overlapper may be there Node left = n.left; if (!left.isNil() && left.maxEnd > t.start()) { n = left; } else { // Left subtree cannot contain an overlapper. Check the // right sub-tree. if (n.start() >= t.end()) { // Nothing in the right subtree can overlap break; } n = n.right; if (n.isNil() || n.maxEnd <= t.start()) { // No right subtree, or nodes can't overlap. break; } } } } } return result; } /** * An Iterator over all values in this Node's subtree that overlap the given Interval t. * * @param start - the starting point of the overlapping Interval * @param end - the ending point of the overlapping Interval */ private Iterator overlappers(int start, int end) { return new OverlapperIterator(this, start, end); } /** * The next Node (relative to this Node) which overlaps the given Interval [start,end] * * @param start - the starting point of the overlapping Interval * @param end - the ending point of the overlapping Interval * @return the next Node that overlaps the Interval [start,end], if one exists; otherwise, * the sentinel Node */ private Node nextOverlappingNode(int start, int end) { Node x = this; Node rtrn = nil; // First, check the right subtree for its minimum overlapper. if (!right.isNil()) { rtrn = x.right.minimumOverlappingNode(start, end); } // If we didn't find it in the right subtree, walk up the tree and // check the parents of left-children as well as their right subtrees. while (!x.parent.isNil() && rtrn.isNil()) { if (x.isLeftChild()) { rtrn = x.parent.overlaps(start, end) ? x.parent : x.parent.right.minimumOverlappingNode(start, end); } x = x.parent; } return rtrn; } private Node nextOverlappingNode(T t) { Node x = this; Node rtrn = nil; // First, check the right subtree for its minimum overlapper. if (!right.isNil()) { rtrn = x.right.minimumOverlappingNode(t); } // If we didn't find it in the right subtree, walk up the tree and // check the parents of left-children as well as their right subtrees. while (!x.parent.isNil() && rtrn.isNil()) { if (x.isLeftChild()) { rtrn = x.parent.overlaps(t) ? x.parent : x.parent.right.minimumOverlappingNode(t); } x = x.parent; } return rtrn; } ////////////////////////////// // Node -- Deletion methods // ////////////////////////////// //TODO: Should we rewire the Nodes rather than copying data? // I suspect this method causes some code which seems like it // should work to fail. /** * Deletes this Node from its tree.

More specifically, removes the data held within this * Node from the tree. Depending on the structure of the tree at this Node, this particular * Node instance may not be removed; rather, a different Node may be deleted and that Node's * contents copied into this one, overwriting the previous contents. */ private boolean delete() { if (isNil()) { // Can't delete the sentinel node. return false; } Node y = this; if (hasTwoChildren()) { // If the node to remove has two children, y = successor(); // copy the successor's data into it and copyData(y); // remove the successor. The successor is maxEndFixup(); // guaranteed to both exist and have at most } // one child, so we've converted the two- // child case to a one- or no-child case. Node x = y.left.isNil() ? y.right : y.left; x.parent = y.parent; if (y.isRoot()) { root = x; } else if (y.isLeftChild()) { y.parent.left = x; y.maxEndFixup(); } else { y.parent.right = x; y.maxEndFixup(); } if (y.isBlack) { x.deleteFixup(); } size--; return true; } //////////////////////////////////////////////// // Node -- Tree-invariant maintenance methods // //////////////////////////////////////////////// /** * Whether or not this Node is the root of its tree. */ public boolean isRoot() { return (!isNil() && parent.isNil()); } /** * Whether or not this Node is the sentinel node. */ public boolean isNil() { return this == nil; } /** * Whether or not this Node is the left child of its parent. */ public boolean isLeftChild() { return this == parent.left; } /** * Whether or not this Node is the right child of its parent. */ public boolean isRightChild() { return this == parent.right; } /** * Whether or not this Node has no children, i.e., is a leaf. */ public boolean hasNoChildren() { return left.isNil() && right.isNil(); } /** * Whether or not this Node has two children, i.e., neither of its children are leaves. */ public boolean hasTwoChildren() { return !left.isNil() && !right.isNil(); } /** * Sets this Node's color to black. */ private void blacken() { isBlack = true; } /** * Sets this Node's color to red. */ private void redden() { isBlack = false; } /** * Whether or not this Node's color is red. */ public boolean isRed() { return !isBlack; } /** * A pointer to the grandparent of this Node. */ private Node grandparent() { return parent.parent; } /** * Sets the maxEnd value for this Node.

The maxEnd value should be the highest of:

    *
  • the end value of this node's data
  • the maxEnd value of this node's left child, if * not null
  • the maxEnd value of this node's right child, if not null

This * method will be correct only if the left and right children have correct maxEnd values. */ private void resetMaxEnd() { int val = interval.end(); if (!left.isNil()) { val = Math.max(val, left.maxEnd); } if (!right.isNil()) { val = Math.max(val, right.maxEnd); } maxEnd = val; } /** * Sets the maxEnd value for this Node, and all Nodes up to the root of the tree. */ private void maxEndFixup() { Node n = this; n.resetMaxEnd(); while (!n.parent.isNil()) { n = n.parent; n.resetMaxEnd(); } } /** * Performs a left-rotation on this Node. * * @see - Cormen et al. "Introduction to Algorithms", 2nd ed, pp. 277-279. */ private void leftRotate() { Node y = right; right = y.left; if (!y.left.isNil()) { y.left.parent = this; } y.parent = parent; if (parent.isNil()) { root = y; } else if (isLeftChild()) { parent.left = y; } else { parent.right = y; } y.left = this; parent = y; resetMaxEnd(); y.resetMaxEnd(); } /** * Performs a right-rotation on this Node. * * @see - Cormen et al. "Introduction to Algorithms", 2nd ed, pp. 277-279. */ private void rightRotate() { Node y = left; left = y.right; if (!y.right.isNil()) { y.right.parent = this; } y.parent = parent; if (parent.isNil()) { root = y; } else if (isLeftChild()) { parent.left = y; } else { parent.right = y; } y.right = this; parent = y; resetMaxEnd(); y.resetMaxEnd(); } /** * Copies the data from a Node into this Node. * * @param o - the other Node containing the data to be copied */ private void copyData(Node o) { interval = o.interval; } @Override public String toString() { if (isNil()) { return "nil"; } else { String color = isBlack ? "black" : "red"; return "start = " + start() + "\nend = " + end() + "\nmaxEnd = " + maxEnd + "\ncolor = " + color; } } /** * Ensures that red-black constraints and interval-tree constraints are maintained after an * insertion. */ private void insertFixup() { Node z = this; while (z.parent.isRed()) { if (z.parent.isLeftChild()) { Node y = z.parent.parent.right; if (y.isRed()) { z.parent.blacken(); y.blacken(); z.grandparent().redden(); z = z.grandparent(); } else { if (z.isRightChild()) { z = z.parent; z.leftRotate(); } z.parent.blacken(); z.grandparent().redden(); z.grandparent().rightRotate(); } } else { Node y = z.grandparent().left; if (y.isRed()) { z.parent.blacken(); y.blacken(); z.grandparent().redden(); z = z.grandparent(); } else { if (z.isLeftChild()) { z = z.parent; z.rightRotate(); } z.parent.blacken(); z.grandparent().redden(); z.grandparent().leftRotate(); } } } root.blacken(); } /** * Ensures that red-black constraints and interval-tree constraints are maintained after * deletion. */ private void deleteFixup() { Node x = this; while (!x.isRoot() && x.isBlack) { if (x.isLeftChild()) { Node w = x.parent.right; if (w.isRed()) { w.blacken(); x.parent.redden(); x.parent.leftRotate(); w = x.parent.right; } if (w.left.isBlack && w.right.isBlack) { w.redden(); x = x.parent; } else { if (w.right.isBlack) { w.left.blacken(); w.redden(); w.rightRotate(); w = x.parent.right; } w.isBlack = x.parent.isBlack; x.parent.blacken(); w.right.blacken(); x.parent.leftRotate(); x = root; } } else { Node w = x.parent.left; if (w.isRed()) { w.blacken(); x.parent.redden(); x.parent.rightRotate(); w = x.parent.left; } if (w.left.isBlack && w.right.isBlack) { w.redden(); x = x.parent; } else { if (w.left.isBlack) { w.right.blacken(); w.redden(); w.leftRotate(); w = x.parent.left; } w.isBlack = x.parent.isBlack; x.parent.blacken(); w.left.blacken(); x.parent.rightRotate(); x = root; } } } x.blacken(); } /////////////////////////////// // Node -- Debugging methods // /////////////////////////////// /** * Whether or not the subtree rooted at this Node is a valid binary-search tree. * * @param min - a lower-bound Node * @param max - an upper-bound Node */ private boolean isBST(Node min, Node max) { if (isNil()) { return true; // Leaves are a valid BST, trivially. } if (min != null && compareTo(min) <= 0) { return false; // This Node must be greater than min } if (max != null && compareTo(max) >= 0) { return false; // and less than max. } // Children recursively call method with updated min/max. return left.isBST(min, this) && right.isBST(this, max); } /** * Whether or not the subtree rooted at this Node is balanced.

Balance determination is * done by calculating the black-height. * * @param black - the expected black-height of this subtree */ private boolean isBalanced(int black) { if (isNil()) { return black == 0; // Leaves have a black-height of zero, } // even though they are black. if (isBlack) { black--; } return left.isBalanced(black) && right.isBalanced(black); } /** * Whether or not the subtree rooted at this Node has a valid red-coloring.

A red-black * tree has a valid red-coloring if every red node has two black children. */ private boolean hasValidRedColoring() { if (isNil()) { return true; } else if (isBlack) { return left.hasValidRedColoring() && right.hasValidRedColoring(); } else { return left.isBlack && right.isBlack && left.hasValidRedColoring() && right.hasValidRedColoring(); } } /** * Whether or not the subtree rooted at this Node has consistent maxEnd values.

The * maxEnd value of an interval-tree Node is equal to the maximum of the end-values of all * intervals contained in the Node's subtree. */ private boolean hasConsistentMaxEnds() { if (isNil()) { // 1. sentinel node return true; } if (hasNoChildren()) { // 2. leaf node return maxEnd == end(); } else { boolean consistent = maxEnd >= end(); if (hasTwoChildren()) { // 3. two children return consistent && maxEnd >= left.maxEnd && maxEnd >= right.maxEnd && left.hasConsistentMaxEnds() && right.hasConsistentMaxEnds(); } else if (left.isNil()) { // 4. one child -- right return consistent && maxEnd >= right.maxEnd && right.hasConsistentMaxEnds(); } else { return consistent && // 5. one child -- left maxEnd >= left.maxEnd && left.hasConsistentMaxEnds(); } } } } /////////////////////// // Tree -- Iterators // /////////////////////// /** * An Iterator which walks along this IntervalTree's Nodes in ascending order. */ private class TreeNodeIterator implements Iterator { private Node next; private TreeNodeIterator(Node root) { next = root.minimumNode(); } @Override public boolean hasNext() { return !next.isNil(); } @Override public Node next() { if (!hasNext()) { throw new NoSuchElementException("Interval tree has no more elements."); } Node rtrn = next; next = rtrn.successor(); return rtrn; } } /** * An Iterator which walks along this IntervalTree's Intervals in ascending order.

This * class just wraps a TreeNodeIterator and extracts each Node's Interval. */ private class TreeIterator implements Iterator { private final TreeNodeIterator nodeIter; private TreeIterator(Node root) { nodeIter = new TreeNodeIterator(root); } @Override public boolean hasNext() { return nodeIter.hasNext(); } @Override public T next() { return nodeIter.next().interval; } } /** * An Iterator which walks along this IntervalTree's Nodes that overlap a given Interval in * ascending order. */ private class OverlappingNodeIteratorBound implements Iterator { private Node next; private final int start; private final int end; private OverlappingNodeIteratorBound(Node root, int start, int end) { this.start = start; this.end = end; next = root.minimumOverlappingNode(start, end); } @Override public boolean hasNext() { return !next.isNil(); } @Override public Node next() { if (!hasNext()) { throw new NoSuchElementException("Interval tree has no more overlapping elements."); } Node rtrn = next; next = rtrn.nextOverlappingNode(start, end); return rtrn; } } /** * An Iterator which walks along this IntervalTree's Nodes that overlap a given Interval in * ascending order. */ private class OverlappingNodeIterator implements Iterator { private Node next; private final T interval; private OverlappingNodeIterator(Node root, T t) { interval = t; next = root.minimumOverlappingNode(interval); } @Override public boolean hasNext() { return !next.isNil(); } @Override public Node next() { if (!hasNext()) { throw new NoSuchElementException("Interval tree has no more overlapping elements."); } Node rtrn = next; next = rtrn.nextOverlappingNode(interval); return rtrn; } } /** * An Iterator which walks along this IntervalTree's Intervals that overlap a given Interval in * ascending order.

This class just wraps an OverlappingNodeIterator and extracts each * Node's Interval. */ private class OverlapperIterator implements Iterator { private final Iterator nodeIter; private OverlapperIterator(Node root, T t) { nodeIter = new OverlappingNodeIterator(root, t); } private OverlapperIterator(Node root, int start, int end) { nodeIter = new OverlappingNodeIteratorBound(root, start, end); } @Override public boolean hasNext() { return nodeIter.hasNext(); } @Override public T next() { return nodeIter.next().interval; } } /////////////////////////////// // Tree -- Debugging methods // /////////////////////////////// /** * Whether or not this IntervalTree is a valid binary-search tree.

This method will return * false if any Node is less than its left child or greater than its right child.

This * method is used for debugging only, and its access is changed in testing. */ @SuppressWarnings("unused") private boolean isBST() { return root.isBST(null, null); } /** * Whether or not this IntervalTree is balanced.

This method will return false if all of the * branches (from root to leaf) do not contain the same number of black nodes. (Specifically, * the black-number of each branch is compared against the black-number of the left-most * branch.)

This method is used for debugging only, and its access is changed in testing. */ @SuppressWarnings("unused") private boolean isBalanced() { int black = 0; Node x = root; while (!x.isNil()) { if (x.isBlack) { black++; } x = x.left; } return root.isBalanced(black); } /** * Whether or not this IntervalTree has a valid red coloring.

This method will return false * if all of the branches (from root to leaf) do not contain the same number of black nodes. * (Specifically, the black-number of each branch is compared against the black-number of the * left-most branch.)

This method is used for debugging only, and its access is changed in * testing. */ @SuppressWarnings("unused") private boolean hasValidRedColoring() { return root.hasValidRedColoring(); } /** * Whether or not this IntervalTree has consistent maxEnd values.

This method will only * return true if each Node has a maxEnd value equal to the highest interval end value of all * the intervals in its subtree.

This method is used for debugging only, and its access is * changed in testing. */ @SuppressWarnings("unused") private boolean hasConsistentMaxEnds() { return root.hasConsistentMaxEnds(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy