/* Copyright 2008-2010 Gephi Authors : Cezary Bartosiak Website : http://www.gephi.org This file is part of Gephi. DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. Copyright 2011 Gephi Consortium. All rights reserved. The contents of this file are subject to the terms of either the GNU General Public License Version 3 only ("GPL") or the Common Development and Distribution License("CDDL") (collectively, the "License"). You may not use this file except in compliance with the License. You can obtain a copy of the License at http://gephi.org/about/legal/license-notice/ or /cddl-1.0.txt and /gpl-3.0.txt. See the License for the specific language governing permissions and limitations under the License. When distributing the software, include this License Header Notice in each file and include the License files at /cddl-1.0.txt and /gpl-3.0.txt. If applicable, add the following below the License Header, with the fields enclosed by brackets [] replaced by your own identifying information: "Portions Copyrighted [year] [name of copyright owner]" If you wish your version of this file to be governed by only the CDDL or only the GPL Version 3, indicate your decision by adding "[Contributor] elects to include this software in this distribution under the [CDDL or GPL Version 3] license." If you do not indicate a single choice of license, a recipient has the option to distribute your version of this file under either the CDDL, the GPL Version 3 or to extend the choice of license to its licensees as provided above. However, if you add GPL Version 3 code and therefore, elected the GPL Version 3 license, then the option applies only if the new code is made subject to such option by the copyright holder. Contributor(s): Portions Copyrighted 2011 Gephi Consortium. */ 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); if (y.p == root) root.max = y.max; } z.p = y; if (y == root) root.max = z.max; 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); if (i.p == root) root.max = i.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; if (y.p == root) root.max = x.max; 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; if (y.p == root) root.max = x.max; 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. * * @return all intervals */ public List<Interval<T>> getIntervals() { List<Interval<T>> list = new ArrayList<Interval<T>>(); inorderTreeWalk(root.left, list); return list; } /** * 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; }