/* * This file is part of the HyperGraphDB source distribution. This is copyrighted * software. For permitted uses, licensing options and redistribution, please see * the LicensingInformation file at the root level of the distribution. * * Copyright (c) 2005-2010 Kobrix Software, Inc. All rights reserved. */ package org.hypergraphdb.util; import java.util.AbstractSet; import java.util.Comparator; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.SortedSet; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.hypergraphdb.HGRandomAccessResult; import org.hypergraphdb.HGSearchResult; /** * * <p> * Implements a set of elements as a left-leaning red-black tree. The node * doesn't contain a pointer to a value, nor does it contains a pointer * to the parent which should make it more memory efficient than most * implementations (e.g. the standard java.util.TreeMap). However, tree * mutations are implemented recursively, which is not optimal and could * be removed in the future. Unfortunately, Java uses 4 bytes to store a boolean * so we don't gain as much in space compactness as we could theoretically, but * it's still an improvement. * </p> * * @author Borislav Iordanov * * @param <E> The type of elements this set stores. It must implement the <code>Comparable</code> * interface or a <code>Comparator</code> has to be provided at construction time. */ public class LLRBTree<E> extends AbstractSet<E> implements HGSortedSet<E>, Cloneable, java.io.Serializable { private static final long serialVersionUID = -1; private static final boolean RED = true; private static final boolean BLACK = false; // The node stack is used to keep track of parent pointers // during tree traversals. A fixed-size array is used for // the stack because it is known that it'll never go beyond // the depth of the tree which, since this a balanced tree, // is going to always be a relatively small number approx. // equal to 2*log(treeSize). private final class NodeStack { Node<E> [] A; Node<E> [] B; int pos = -1; int bpos; @SuppressWarnings("unchecked") NodeStack() { int s = 0; if (size > 0) s = (int)(2*(Math.log(size + 1)/Math.log(2))); A = new Node[s]; B = new Node[s]; } void backup() { bpos = pos; System.arraycopy(A, 0, B, 0, pos+1); } void restore() { pos = bpos; Node<E> [] tmp = A; A = B; B = tmp; } boolean isEmpty() { return pos < 0; } Node<E> top() { return A[pos]; } Node<E> pop() { return A[pos--]; } Node<E> push(Node<E> n) { return A[++pos] = n; } void clear() { pos = -1; } } private static class Node<E> implements Cloneable { E key; Node<E> left, right; boolean color; @SuppressWarnings("unchecked") public Node<E> clone() throws CloneNotSupportedException { Node<E> n = (Node<E>)super.clone(); if (left != null) n.left = left.clone(); if (right != null) n.right = right.clone(); return n; } Node(E key, boolean color) { this.key = key; this.color = color; } Node<E> rotateLeft() { Node<E> x = right; right = x.left; x.left = this; x.color = this.color; this.color = RED; return x; } Node<E> rotateRight() { Node<E> x = left; left = x.right; x.right = this; x.color = this.color; this.color = RED; return x; } Node<E> colorFlip() { color = !color; left.color = !left.color; right.color = !right.color; return this; } private Node<E> fixUp() { Node<E> h = this; if (isRed(h.right)) { h = h.rotateLeft(); if (isRightLeaning(h.left)) h.left = h.left.rotateLeft(); } if (isRed(h.left) && isRed(h.left.left)) { h = h.rotateRight(); } if (isRed(h.left) && isRed(h.right)) { h.colorFlip(); } return h; } private Node<E> moveRedRight() { colorFlip(); if (isRed(left.left)) { Node<E> h = rotateRight(); return h.colorFlip(); } else return this; } private Node<E> moveRedLeft() { colorFlip(); if (isRed(right.left)) { right = right.rotateRight(); Node<E> h = rotateLeft(); if (isRightLeaning(h.right)) h.right = h.right.rotateLeft(); return h.colorFlip(); } else return this; } } private final Node<E> UNKNOWN = new Node<E>(null, BLACK); final class ResultSet implements HGRandomAccessResult<E> { boolean locked = false; int lookahead = 0; Node<E> next = UNKNOWN, current = UNKNOWN, prev = UNKNOWN; // Keeps track of parents of current node because Node itself doesn't have // a parent field. NodeStack stack = new NodeStack(); // min, max, advance, back all work on the current position as // stored in the 'stack' of parents. // // 'min' returns the smallest element rooted at (and including) // stack.top while 'max' analogously returns the largest element // // 'advance' returns the next smallest elements and 'back' the previous // largest element // // All 4 modify the stack so that it's positioned at the returned // node Node<E> min() { Node<E> result = stack.top(); while (result.left != null) result = stack.push(result.left); return result; } Node<E> max() { Node<E> result = stack.top(); while (result.right != null) result = stack.push(result.right); return result; } Node<E> advance() { Node<E> current = stack.top(); if (current.right != null) { stack.push(current.right); return min(); } else { stack.backup(); stack.pop(); while (!stack.isEmpty()) { Node<E> parent = stack.top(); if (parent.left == current) return parent; else current = stack.pop(); } stack.restore(); return null; } } Node<E> back() { Node<E> current = stack.top(); if (current.left != null) { stack.push(current.left); return max(); } else { stack.backup(); stack.pop(); while (!stack.isEmpty()) { Node<E> parent = stack.top(); if (parent.right == current) return parent; else current = stack.pop(); } stack.restore(); return null; } } ResultSet(boolean acquireLock) { if (acquireLock) lock.readLock().lock(); locked = acquireLock; } public void goBeforeFirst() { lookahead = 0; next = current = prev = UNKNOWN; stack.clear(); } public void goAfterLast() { lookahead = 0; stack.clear(); stack.push(root); prev = max(); next = current = UNKNOWN; } @SuppressWarnings("unchecked") public GotoResult goTo(E key, boolean exactMatch) { // Not clear here whether we should be starting from the root really? // Gotos are performed generally during result set intersection where the target // is expected to be approximately close to the current position. Anyway, // starting from the root simplifies the implementations so until profiling // reveals the need for something else we start from the root. // To make sure the current position remains unchanged if we return GotoResult.nothing // we save the stack array. stack.backup(); stack.clear(); Node<E> current = root; GotoResult result = GotoResult.nothing; Comparable<E> ckey = providedComparator == null ? (Comparable<E>)key : null; // make typecast out of loop, expensive! while (current != null) { stack.push(current); int cmp = ckey == null ? providedComparator.compare(key, current.key) : ckey.compareTo(current.key); if (cmp == 0) { result = GotoResult.found; break; } else if (cmp < 0) { if (exactMatch || current.left != null) current = current.left; else { result = GotoResult.close; break; } } else { if (exactMatch || current.right != null) current = current.right; else if (advance() != null) { result = GotoResult.close; break; } else break; } } if (GotoResult.nothing == result) { stack.restore(); return GotoResult.nothing; } else { lookahead = 0; next = UNKNOWN; prev = UNKNOWN; this.current = stack.top(); return result; } } public void close() { if (locked) lock.readLock().unlock(); } public E current() { if (current == UNKNOWN) throw new NoSuchElementException(); else return current.key; } public boolean isOrdered() { return true; } public boolean hasNext() { if (next == UNKNOWN) moveNext(); return next != null; } // Advance internal cursor to next element and assign 'next' to it. private void moveNext() { if (stack.isEmpty()) { stack.push(root); next = min(); lookahead = 1; } else while (true) { next = advance(); if (next == null) break; if (++lookahead == 1) break; } } public E next() { if (!hasNext()) throw new NoSuchElementException(); prev = current; current = next; lookahead--; moveNext(); return current.key; } public void remove() { if (current == UNKNOWN) throw new NoSuchElementException(); // Because of lack of parent pointers in Node, we can't really // take advantage of the fact that we are already positioned // at the node we want to delete. In the current iteration context, // we could make use of the parent stack to some advantage, but this // would require a completely new version of the delete algo which // is too big of a price to pay. // // So we just do a normal remove and reset the iterator to its 'prev' state. LLRBTree.this.remove(current.key); if (prev != null) if (goTo(prev.key, true) == GotoResult.nothing) throw new Error("LLRBTree.ResultSet.remove buggy."); else { current = prev = next = UNKNOWN; lookahead = 0; stack.clear(); } } public boolean hasPrev() { if (prev == UNKNOWN) movePrev(); return prev != null; } private void movePrev() { if (stack.isEmpty()) prev = null; else while (true) { prev = back(); if (prev == null) break; if (--lookahead == -1) break; } } public E prev() { if (prev == null) throw new NoSuchElementException(); next = current; current = prev; lookahead++; movePrev(); return current.key; } } // END IMPLEMENTATION OF Iterator/HGSearchResult private Node<E> root = null; private int size = 0; private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private Comparator<E> providedComparator = null; private static boolean isRed(Node<?> x) { return x == null ? false : x.color == RED; } private static boolean isBlack(Node<?> x) { return x == null || x.color == BLACK; } // A node is right-leaning if it's right child is red, but it's left is black private static boolean isRightLeaning(Node<?> x) { return x == null ? false : isRed(x.right) && isBlack(x.left); } @SuppressWarnings("unchecked") private Node<E> insert(Node<E> h, E key) { if (h == null) { size++; return new Node<E>(key, RED); } // Split 4-Nodes if (isRed(h.left) && isRed(h.right)) h.colorFlip(); int cmp = providedComparator != null ? providedComparator.compare(key, h.key) : ((Comparable<E>)key).compareTo(h.key); if (cmp < 0) h.left = insert(h.left, key); else if (cmp > 0) h.right = insert(h.right, key); // Fix right leaning tree. if (isRed(h.right) && isBlack(h.left)) h = h.rotateLeft(); // Fix two reds in a row. else if (isRed(h.left) && isRed(h.left.left)) h = h.rotateRight(); return h; } private Node<E> min(Node<E> h) { if (h == null) return null; Node<E> x = h; while (x.left != null) x = x.left; return x; } private Node<E> max(Node<E> h) { if (h == null) return null; Node<E> x = h; while (x.right != null) x = x.right; return x; } // The following two functions are for debugging only to print to coloring of a node, its // children and its grandchildren. /* private String cs(boolean b) { return b ? "red" : "black"; } private String colors(Node<E> h) { String colors = "h -> " + cs(h.color); if (h.left != null) { colors += ", h.left -> " + cs(h.left.color); if (h.left.left != null) colors += ", h.left.left -> " + cs(h.left.left.color); else colors += ", h.left.left = null"; if (h.left.right != null) colors += ", h.left.right -> " + cs(h.left.right.color); else colors += ", h.left.right = null"; } if (h.right != null) { colors += ", h.right -> " + cs(h.right.color); if (h.right.left != null) colors += ", h.right.left -> " + cs(h.right.left.color); else colors += ", h.right.left = null"; if (h.right.right != null) colors += ", h.right.right -> " + cs(h.right.right.color); else colors += ", h.right.right = null"; } return colors; } */ private Node<E> deleteMax(Node<E> h) { if (isRed(h.left) && isBlack(h.right)) h = h.rotateRight(); else if (h.right == null) return null; // h.left will be null here as well if (isBlack(h.right) && isBlack(h.right.left)) h = h.moveRedRight(); h.right = deleteMax(h.right); return h.fixUp(); } private Node<E> deleteMin(Node<E> h) { if (h.left == null) return null; // h.right will be null here as well if (isBlack(h.left) && isBlack(h.left.left)) h = h.moveRedLeft(); h.left = deleteMin(h.left); return h.fixUp(); } @SuppressWarnings("unchecked") private Node<E> delete(Node<E> h, E key) { int cmp = providedComparator != null ? providedComparator.compare(key, h.key) : ((Comparable<E>)key).compareTo(h.key); if (cmp < 0) { if (!isRed(h.left) && !isRed(h.left.left)) h = h.moveRedLeft(); h.left = delete(h.left, key); } else // cmp >= 0 { if (isRed(h.left) && isBlack(h.right)) { h = h.rotateRight(); cmp++; // if we rotate right, then current h becomes necessarily < key } else if (cmp == 0 && (h.right == null)) { size--; return null; // h.left is null here due to transformations going down } Node<E> tmp = h; // track if moveRedRight changes 'h' so we don't need to call key.compareTo again if (!isRed(h.right) && !isRed(h.right.left)) tmp = h.moveRedRight(); // if no rotation in above line and key==h.key replace with successor and we're done if (tmp == h && cmp == 0) { h.key = min(h.right).key; h.right = deleteMin(h.right); size--; } else { h = tmp; h.right = delete(h.right, key); } } return h.fixUp(); } public LLRBTree() { } public LLRBTree(Comparator<E> comparator) { this.providedComparator = comparator; } public void removeMax() { lock.writeLock().lock(); try { if (root == null) return; root = deleteMax(root); if (root != null) root.color = BLACK; size--; } finally { lock.writeLock().unlock(); } } public void removeMin() { lock.writeLock().lock(); try { if (root == null) return; root = deleteMin(root); if (root != null) root.color = BLACK; size--; } finally { lock.writeLock().unlock(); } } // Set interface implementation public int size() { return size; } public boolean isEmtpy() { return size == 0; } public Comparator<E> comparator() { return providedComparator; } public void clear() { lock.writeLock().lock(); root = null; size = 0; lock.writeLock().unlock(); } @SuppressWarnings("unchecked") public boolean contains(Object key) { lock.readLock().lock(); try { Node<E> current = root; Comparable<E> ckey = providedComparator == null ? (Comparable<E>)key : null; while (current != null) { int cmp = ckey != null ? ckey.compareTo(current.key) : providedComparator.compare((E)key, current.key); if (cmp == 0) return true; else if (cmp < 0) current = current.left; else current = current.right; } return false; } finally { lock.readLock().unlock(); } } public boolean add(E key) { lock.writeLock().lock(); try { int s = size; root = insert(root, key); root.color = BLACK; return s != size; } finally { lock.writeLock().unlock(); } } @SuppressWarnings("unchecked") public boolean remove(Object key) { lock.writeLock().lock(); try { if (root == null) return false; int s = size; root = delete(root, (E)key); if (root != null) root.color = BLACK; return s != size; } finally { lock.writeLock().unlock(); } } public E first() { lock.readLock().lock(); try { if (root == null) return null; return min(root).key; } finally { lock.readLock().unlock(); } } public E last() { lock.readLock().lock(); try { if (root == null) return null; return max(root).key; } finally { lock.readLock().unlock(); } } public SortedSet<E> headSet(E toElement) { throw new UnsupportedOperationException("...because of lazy implementor: this is a TODO."); } public SortedSet<E> subSet(E fromElement, E toElement) { throw new UnsupportedOperationException("...because of lazy implementor: this is a TODO."); } public SortedSet<E> tailSet(E fromElement) { throw new UnsupportedOperationException("...because of lazy implementor: this is a TODO."); } @Override @SuppressWarnings("unchecked") public Iterator<E> iterator() { if (isEmpty()) return (Iterator<E>)HGSearchResult.EMPTY; // avoid checking for root == null in ResultSet impl. else return new ResultSet(false); } public HGRandomAccessResult<E> getSearchResult() { return new ResultSet(true); } @Override @SuppressWarnings("unchecked") public Object clone() throws CloneNotSupportedException { lock.readLock().lock(); try { LLRBTree<E> cl = (LLRBTree<E>)super.clone(); cl.root = root == null ? root : root.clone(); cl.size = size; return cl; } finally { lock.readLock().unlock(); } } public int depth() { lock.readLock().lock(); try { return depth(root); } finally { lock.readLock().unlock(); } } private int depth(Node<E> x) { if (x == null) return 0; else return Math.max(1 + depth(x.left), 1 + depth(x.right)); } // Integrity checks... public boolean check() { return isBST() && is234() && isBalanced(); } public boolean isBST() { // Is this tree a BST? return isBST(root, first(), last()); } @SuppressWarnings("unchecked") private boolean isBST(Node<E> x, E min, E max) { // Are all the values in the BST rooted at x between min and max, // and does the same property hold for both subtrees? if (x == null) return true; int c1 = providedComparator != null ? providedComparator.compare(x.key, min) : ((Comparable<E>)x.key).compareTo(min); int c2 = providedComparator != null ? providedComparator.compare(max, x.key) : ((Comparable<E>)max).compareTo(x.key); if (c1 < 0 || c2 < 0) return false; return isBST(x.left, min, x.key) && isBST(x.right, x.key, max); } public boolean is234() { return is234(root); } boolean is234(Node<E> x) { if (x == null) return true; // Does the tree have no red right links, and at most two (left) // red links in a row on any path? if (isRightLeaning(x)) { System.err.println("Right leaning node"); return false; } if (isRed(x)) if (isRed(x.left)) { System.err.println("2 consecutive reds"); return false; } return is234(x.left) && is234(x.right); } public boolean isBalanced() { return isBalanced(root); } public boolean isBalanced(Node<E> r) { // Do all paths from root to leaf have same number of black edges? int black = 0; // number of black links on path from root to min Node<E> x = r; while (x != null) { if (!isRed(x)) black++; x = x.left; } return isBalanced(r, black); } private boolean isBalanced(Node<E> x, int black) { // Does every path from the root to a leaf have the given number // of black links? if (x == null && black == 0) return true; else if (x == null && black != 0) return false; if (!isRed(x)) black--; return isBalanced(x.left, black) && isBalanced(x.right, black); } }