/* Copyright 2008-2010 Gephi Authors : Cezary Bartosiak Website : http://www.gephi.org This file is part of Gephi. Gephi is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Gephi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Gephi. If not, see <http://www.gnu.org/licenses/>. */ package org.gephi.data.attributes.type; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * It is essentially a map from intervals to object which can be queried for * {@code Interval} instances associated with a particular interval of time. * * <p>Insertion can be performed in <i>O</i>(lg <i>n</i>) time, where <i>n</i> * is the number of nodes. All intervals in a tree that overlap some interval * <i>i</i> can be listed in <i>O</i>(min(<i>n</i>, <i>k</i> lg <i>n</i>) time, * where <i>k</i> is the number of intervals in the output list. Thus search and * deletion can be performed in this time. * * <p>The space consumption is <i>O</i>(<i>n</i>). * * <p>Note that this implementation doesn't allow intervals to be duplicated. * * <p>References: * <ul> * <li>Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein. * Introduction to Algorithms, Second Edition. MIT, 2001. ISBN 83-204-2879-3 * </ul> * * @author Cezary Bartosiak * * @param <T> type of data */ public final class IntervalTree<T> { private Node nil; // the sentinel node private Node root; // the root of this interval tree /** * Constructs an empty {@code IntervalTree}. */ public IntervalTree() { nil = new Node(); nil.left = nil.right = nil.p = nil; root = nil; } /** * Constructs a copy of the given {@code IntervalTree}. * * @param intervalTree a copied {@code IntervalTree} */ public IntervalTree(IntervalTree intervalTree) { this(); copy(intervalTree.root.left, intervalTree.nil); } private void copy(Node x, Node nil) { if (x != nil) { copy(x.left, nil); insert(x.i); copy(x.right, nil); } } private boolean compareLow(Interval a, Interval b) { if (a.getLow() < b.getLow() || a.getLow() == b.getLow() && (!a.isLowExcluded() || b.isHighExcluded())) return true; return false; } /** * Inserts the {@code interval} into this {@code IntervalTree}. * * @param interval an interval to be inserted * * @throws NullPointerException if {@code interval} is null. */ public void insert(Interval<T> interval) { if (interval == null) throw new NullPointerException("Interval cannot be null."); insert(new Node(interval)); } private void insert(Node z) { z.left = z.right = nil; Node y = root; Node x = root.left; while (x != nil) { y = x; if (compareLow(z.i, y.i)) x = x.left; else x = x.right; y.max = Math.max(z.max, y.max); } z.p = y; if (y == root || compareLow(z.i, y.i)) y.left = z; else y.right = z; insertFixup(z); } private void insertFixup(Node z) { Node y = nil; z.color = RED; while (z.p.color == RED) if (z.p == z.p.p.left) { y = z.p.p.right; if (y.color == RED) { z.p.color = BLACK; y.color = BLACK; z.p.p.color = RED; z = z.p.p; } else { if (z == z.p.right) { z = z.p; leftRotate(z); } z.p.color = BLACK; z.p.p.color = RED; rightRotate(z.p.p); } } else { y = z.p.p.left; if (y.color == RED) { z.p.color = BLACK; y.color = BLACK; z.p.p.color = RED; z = z.p.p; } else { if (z == z.p.left) { z = z.p; rightRotate(z); } z.p.color = BLACK; z.p.p.color = RED; leftRotate(z.p.p); } } root.left.color = BLACK; } /** * Removes all intervals from this {@code IntervalTree} that overlap with * the given {@code interval}. * * @param interval determines which intervals should be removed * * @throws NullPointerException if {@code interval} is null. */ public void delete(Interval interval) { if (interval == null) throw new NullPointerException("Interval cannot be null."); for (Node n : searchNodes(interval)) delete(n); } private void delete(Node z) { z.max = Double.NEGATIVE_INFINITY; for (Node i = z.p; i != root; i = i.p) i.max = Math.max(i.left.max, i.right.max); Node y; Node x; if (z.left == nil || z.right == nil) y = z; else y = succesor(z); if (y.left == nil) x = y.right; else x = y.left; x.p = y.p; if (root == x.p) root.left = x; else if (y == y.p.left) y.p.left = x; else y.p.right = x; if (y != z) { if (y.color == BLACK) deleteFixup(x); y.left = z.left; y.right = z.right; y.p = z.p; y.color = z.color; z.left.p = z.right.p = y; if (z == z.p.left) z.p.left = y; else z.p.right = y; } else if (y.color == BLACK) deleteFixup(x); } private void deleteFixup(Node x) { while (x != root.left && x.color == BLACK) if (x == x.p.left) { Node w = x.p.right; if (w.color == RED) { w.color = BLACK; x.p.color = RED; leftRotate(x.p); w = x.p.right; } if (w.left.color == BLACK && w.right.color == BLACK) { w.color = RED; x = x.p; } else { if (w.right.color == BLACK) { w.left.color = BLACK; w.color = RED; rightRotate(w); w = x.p.right; } w.color = x.p.color; x.p.color = BLACK; w.right.color = BLACK; leftRotate(x.p); x = root.left; } } else { Node w = x.p.left; if (w.color == RED) { w.color = BLACK; x.p.color = RED; rightRotate(x.p); w = x.p.left; } if (w.right.color == BLACK && w.left.color == BLACK) { w.color = RED; x = x.p; } else { if (w.left.color == BLACK) { w.right.color = BLACK; w.color = RED; leftRotate(w); w = x.p.left; } w.color = x.p.color; x.p.color = BLACK; w.left.color = BLACK; rightRotate(x.p); x = root.left; } } x.color = BLACK; } private void leftRotate(Node x) { Node y = x.right; x.right = y.left; if (y.left != nil) y.left.p = x; y.p = x.p; if (x == x.p.left) x.p.left = y; else x.p.right = y; y.left = x; x.p = y; y.max = x.max; x.max = Math.max(x.i.getHigh(), Math.max(x.left.max, x.right.max)); } private void rightRotate(Node x) { Node y = x.left; x.left = y.right; if (y.right != nil) y.right.p = x; y.p = x.p; if (x == x.p.left) x.p.left = y; else x.p.right = y; y.right = x; x.p = y; y.max = x.max; x.max = Math.max(x.i.getHigh(), Math.max(x.left.max, x.right.max)); } private Node succesor(Node x) { Node y = x.right; if (y != nil) { while (y.left != nil) y = y.left; return y; } y = x.p; while (x == y.right) { x = y; y = y.p; } if (y == root) return nil; return y; } /** * Returns the interval with the lowest left endpoint. * * @return the interval with the lowest left endpoint * or null if the tree is empty. */ public Interval<T> minimum() { if (root.left == nil) return null; return treeMinimum(root.left).i; } private Node treeMinimum(Node x) { while (x.left != nil) x = x.left; return x; } /** * Returns the interval with the highest left endpoint. * * @return the interval with the highest left endpoint * or null if the tree is empty. */ public Interval<T> maximum() { if (root.left == nil) return null; return treeMaximum(root.left).i; } private Node treeMaximum(Node x) { while (x.right != nil) x = x.right; return x; } /** * Returns the leftmost point or {@code Double.NEGATIVE_INFINITY} in case * of no intervals. * * @return the leftmost point. */ public double getLow() { if (isEmpty()) return Double.NEGATIVE_INFINITY; return minimum().getLow(); } /** * Returns the rightmost point or {@code Double.POSITIVE_INFINITY} in case * of no intervals. * * @return the rightmost point. */ public double getHigh() { if (isEmpty()) return Double.POSITIVE_INFINITY; return root.left.max; } /** * Indicates if the leftmost point is excluded. * * @return {@code true} if the leftmost point is excluded, * {@code false} otherwise. */ public boolean isLowExcluded() { if (isEmpty()) return true; return minimum().isLowExcluded(); } /** * Indicates if the rightmost point is excluded. * * @return {@code true} if the rightmost point is excluded, * {@code false} otherwise. */ public boolean isHighExcluded() { if (isEmpty()) return true; return maximum().isHighExcluded(); } /** * Indicates if this {@code IntervalTree} contains 0 intervals. * * @return {@code true} if this {@code IntervalTree} is empty, * {@code false} otherwise. */ public boolean isEmpty() { return root.left == nil; } /** * Returns all intervals overlapping with a given {@code Interval}. * * @param interval an {#code Interval} to be searched for overlaps * * @return all intervals overlapping with a given {@code Interval}. * * @throws NullPointerException if {@code interval} is null. */ public List<Interval<T>> search(Interval interval) { if (interval == null) throw new NullPointerException("Interval cannot be null."); List<Interval<T>> overlaps = new ArrayList<Interval<T>>(); for (Node n : searchNodes(interval)) overlaps.add(n.i); return overlaps; } /** * Returns all intervals overlapping with an interval given by {@code low} * and {@code high}. They are considered as included by default. * * @param low the left endpoint of an interval to be searched for overlaps * @param high the right endpoint an interval to be searched for overlaps * * @return all intervals overlapping with an interval given by {@code low} * and {@code high}. * * @throws IllegalArgumentException if {@code low} > {@code high}. */ public List<Interval<T>> search(double low, double high) { if (low > high) throw new IllegalArgumentException( "The left endpoint of the interval must be less than " + "the right endpoint."); List<Interval<T>> overlaps = new ArrayList<Interval<T>>(); for (Node n : searchNodes(new Interval(low, high))) overlaps.add(n.i); return overlaps; } private List<Node> searchNodes(Interval interval) { List<Node> result = new ArrayList<Node>(); searchNodes(root.left, interval, result); return result; } private void searchNodes(Node n, Interval interval, List<Node> result) { // Don't search nodes that don't exist. if (n == nil) return; // Skip all nodes that have got their max value below the start of // the given interval. if (interval.getLow() > n.max) return; // Search left children. if (n.left != nil) searchNodes(n.left, interval, result); // Check this node. if (n.i.compareTo(interval) == 0) result.add(n); // Skip all nodes to the right of nodes whose low value is past the end // of the given interval. if (interval.compareTo(n.i) < 0) return; // Otherwise, search right children. if (n.right != nil) searchNodes(n.right, interval, result); } /** * Indicates if this {@code IntervalTree} overlaps with the given time interval. * * @param interval a given time interval * * @return {@code true} if this {@code IntervalTree} overlaps with {@code interval}, * {@code false} otherwise. */ public boolean overlapsWith(Interval interval) { return overlapsWith(root.left, interval); } private boolean overlapsWith(Node n, Interval interval) { // Don't search nodes that don't exist. if (n == nil) return false; // Skip all nodes that have got their max value below the start of // the given interval. if (interval.getLow() > n.max) return false; // Search left children. if (n.left != nil) if (overlapsWith(n.left, interval)) return true; // Check this node. if (n.i.compareTo(interval) == 0) return true; // Skip all nodes to the right of nodes whose low value is past the end // of the given interval. if (interval.compareTo(n.i) < 0) return false; // Otherwise, search right children. if (n.right != nil) if (overlapsWith(n.right, interval)) return true; // No overlaps, return false. return false; } private void inorderTreeWalk(Node x, List<Interval<T>> list) { if (x != nil) { inorderTreeWalk(x.left, list); list.add(x.i); inorderTreeWalk(x.right, list); } } /** * Compares this interval tree with the specified object for equality. * * <p>Note that two interval trees are equal if they contain the same * intervals. * * @param obj object to which this interval tree is to be compared * * @return {@code true} if and only if the specified {@code Object} is a * {@code IntervalTree} which contain the same intervals as this * {@code IntervalTree's}. * * @see #hashCode */ @Override public boolean equals(Object obj) { if (obj != null && obj.getClass().equals(this.getClass())) { List<Interval<T>> thisIntervals = new ArrayList<Interval<T>>(); List<Interval<T>> objIntervals = new ArrayList<Interval<T>>(); inorderTreeWalk(root.left, thisIntervals); ((IntervalTree<T>)obj).inorderTreeWalk( ((IntervalTree<T>)obj).root.left, objIntervals); if (thisIntervals.size() == objIntervals.size()) { for (int i = 0; i < thisIntervals.size(); ++i) if (!thisIntervals.get(i).equals(objIntervals.get(i))) return false; return true; } } return false; } /** * Returns a hashcode of this interval tree. * * @return a hashcode of this interval tree. */ @Override public int hashCode() { List<Interval<T>> list = new ArrayList<Interval<T>>(); inorderTreeWalk(root.left, list); return Arrays.deepHashCode(list.toArray()); } /** * Creates a string representation of all the intervals with their values. * * @param timesAsDoubles indicates if times should be shown as doubles or dates * * @return a string representation with times as doubles or dates. */ public String toString(boolean timesAsDoubles) { List<Interval<T>> list = new ArrayList<Interval<T>>(); inorderTreeWalk(root.left, list); if (!list.isEmpty()) { StringBuilder sb = new StringBuilder("<"); sb.append(list.get(0).toString(timesAsDoubles)); for (int i = 1; i < list.size(); ++i) sb.append("; ").append(list.get(i).toString(timesAsDoubles)); sb.append(">"); return sb.toString(); } return "<empty>"; } /** * Returns a string representation of this interval tree in a format * {@code <[low, high, value], ..., [low, high, value]>}. Nodes are visited * in {@code inorder}. * * <p>Times are always shown as doubles.</p> * * @return a string representation of this interval tree. */ @Override public String toString() { return toString(true); } private class Node { public Interval<T> i; // i.low is the key of this node public double max; // the maximum value of any interval endpoint // stored in the subtree rooted at this node public Color color; // the color of this node public Node left; // the left subtree of this node public Node right; // the right subtree of this node public Node p; // the parent node /* * Constructs a sentinel node by default. */ public Node() { color = BLACK; } /* * Constructs a new {@code Node} instance. */ public Node(Interval<T> i) { this(); this.i = i; this.max = i.getHigh(); } } private enum Color { RED, BLACK } private static final Color RED = Color.RED; private static final Color BLACK = Color.BLACK; }