/* * Copyright (c) 2000, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code 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 General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. * */ package sun.jvm.hotspot.utilities; /** <P> This class implements a Red-Black tree as described in Cormen, Leiserson, Rivest, <I>Introduction to Algorithms</I>, MIT Press: Cambridge, MA, 1990. </P> <P> A property of this implementation is that it is designed to allow straightforward augmentation of the data structure. A valid augmentation is one in which a node contains auxiliary information that can be computed by examining only this node and its left and right children (see CLR, section 15.2). </P> <P> An RBTree is a structure of RBNodes, each of which contains a user data element. When the user inserts a piece of data into the tree, a new RBNode is constructed around it. </P> <P> An RBTree takes a Comparator as argument to its constructor which is used internally to order the nodes in the tree. The comparator's arguments are obtained by calling the routine "getNodeData" on two nodes; the default implementaion returns the node data. This Comparator is also used to perform the generic "find" operation, which returns the RBNode containing user data precisely equalling the query data. Different types of user data will typically require different types of traversals as well as additional comparison operations; these are left to the RBTree subclass. </P> */ import java.io.PrintStream; import java.util.Comparator; import java.util.Random; public class RBTree { private RBNode root; private Comparator comparator; protected static final boolean DEBUGGING = true; protected static final boolean VERBOSE = true; protected static final boolean REALLY_VERBOSE = false; public RBTree(Comparator comparator) { this.comparator = comparator; } public RBNode getRoot() { return root; } public void insertNode(RBNode x) { treeInsert(x); x.setColor(RBColor.RED); boolean shouldPropagate = x.update(); if (DEBUGGING && REALLY_VERBOSE) { System.err.println("RBTree.insertNode"); } RBNode propagateStart = x; // Loop invariant: x has been updated. while ((x != root) && (x.getParent().getColor() == RBColor.RED)) { if (x.getParent() == x.getParent().getParent().getLeft()) { RBNode y = x.getParent().getParent().getRight(); if ((y != null) && (y.getColor() == RBColor.RED)) { // Case 1 if (DEBUGGING && REALLY_VERBOSE) { System.err.println(" Case 1/1"); } x.getParent().setColor(RBColor.BLACK); y.setColor(RBColor.BLACK); x.getParent().getParent().setColor(RBColor.RED); x.getParent().update(); x = x.getParent().getParent(); shouldPropagate = x.update(); propagateStart = x; } else { if (x == x.getParent().getRight()) { // Case 2 if (DEBUGGING && REALLY_VERBOSE) { System.err.println(" Case 1/2"); } x = x.getParent(); leftRotate(x); } // Case 3 if (DEBUGGING && REALLY_VERBOSE) { System.err.println(" Case 1/3"); } x.getParent().setColor(RBColor.BLACK); x.getParent().getParent().setColor(RBColor.RED); shouldPropagate = rightRotate(x.getParent().getParent()); propagateStart = x.getParent(); } } else { // Same as then clause with "right" and "left" exchanged RBNode y = x.getParent().getParent().getLeft(); if ((y != null) && (y.getColor() == RBColor.RED)) { // Case 1 if (DEBUGGING && REALLY_VERBOSE) { System.err.println(" Case 2/1"); } x.getParent().setColor(RBColor.BLACK); y.setColor(RBColor.BLACK); x.getParent().getParent().setColor(RBColor.RED); x.getParent().update(); x = x.getParent().getParent(); shouldPropagate = x.update(); propagateStart = x; } else { if (x == x.getParent().getLeft()) { // Case 2 if (DEBUGGING && REALLY_VERBOSE) { System.err.println(" Case 2/2"); } x = x.getParent(); rightRotate(x); } // Case 3 if (DEBUGGING && REALLY_VERBOSE) { System.err.println(" Case 2/3"); } x.getParent().setColor(RBColor.BLACK); x.getParent().getParent().setColor(RBColor.RED); shouldPropagate = leftRotate(x.getParent().getParent()); propagateStart = x.getParent(); } } } while (shouldPropagate && (propagateStart != root)) { if (DEBUGGING && REALLY_VERBOSE) { System.err.println(" Propagating"); } propagateStart = propagateStart.getParent(); shouldPropagate = propagateStart.update(); } root.setColor(RBColor.BLACK); if (DEBUGGING) { verify(); } } /** FIXME: this does not work properly yet for augmented red-black trees since it doesn't update nodes. Need to figure out exactly from which points we need to propagate updates upwards. */ public void deleteNode(RBNode z) { // This routine splices out a node. Note that we may not actually // delete the RBNode z from the tree, but may instead remove // another node from the tree, copying its contents into z. // y is the node to be unlinked from the tree RBNode y; if ((z.getLeft() == null) || (z.getRight() == null)) { y = z; } else { y = treeSuccessor(z); } // y is guaranteed to be non-null at this point RBNode x; if (y.getLeft() != null) { x = y.getLeft(); } else { x = y.getRight(); } // x is the potential child of y to replace it in the tree. // x may be null at this point RBNode xParent; if (x != null) { x.setParent(y.getParent()); xParent = x.getParent(); } else { xParent = y.getParent(); } if (y.getParent() == null) { root = x; } else { if (y == y.getParent().getLeft()) { y.getParent().setLeft(x); } else { y.getParent().setRight(x); } } if (y != z) { z.copyFrom(y); } if (y.getColor() == RBColor.BLACK) { deleteFixup(x, xParent); } if (DEBUGGING) { verify(); } } public void print() { printOn(System.out); } public void printOn(PrintStream tty) { printFromNode(root, tty, 0); } //---------------------------------------------------------------------- // Functionality overridable by subclass // protected Object getNodeValue(RBNode node) { return node.getData(); } /** Verify invariants are preserved */ protected void verify() { verifyFromNode(root); } //---------------------------------------------------------------------- // Internals only below this point // // // Vanilla binary search tree operations // private void treeInsert(RBNode z) { RBNode y = null; RBNode x = root; while (x != null) { y = x; if (comparator.compare(getNodeValue(z), getNodeValue(x)) < 0) { x = x.getLeft(); } else { x = x.getRight(); } } z.setParent(y); if (y == null) { root = z; } else { if (comparator.compare(getNodeValue(z), getNodeValue(y)) < 0) { y.setLeft(z); } else { y.setRight(z); } } } private RBNode treeSuccessor(RBNode x) { if (x.getRight() != null) { return treeMinimum(x.getRight()); } RBNode y = x.getParent(); while ((y != null) && (x == y.getRight())) { x = y; y = y.getParent(); } return y; } private RBNode treeMinimum(RBNode x) { while (x.getLeft() != null) { x = x.getLeft(); } return x; } // // Insertion and deletion helpers // /** Returns true if updates must continue propagating up the tree */ private boolean leftRotate(RBNode x) { // Set y. RBNode y = x.getRight(); // Turn y's left subtree into x's right subtree. x.setRight(y.getLeft()); if (y.getLeft() != null) { y.getLeft().setParent(x); } // Link x's parent to y. y.setParent(x.getParent()); if (x.getParent() == null) { root = y; } else { if (x == x.getParent().getLeft()) { x.getParent().setLeft(y); } else { x.getParent().setRight(y); } } // Put x on y's left. y.setLeft(x); x.setParent(y); // Update nodes in appropriate order (lowest to highest) boolean res = x.update(); res = y.update() || res; return res; } /** Returns true if updates must continue propagating up the tree */ private boolean rightRotate(RBNode y) { // Set x. RBNode x = y.getLeft(); // Turn x's right subtree into y's left subtree. y.setLeft(x.getRight()); if (x.getRight() != null) { x.getRight().setParent(y); } // Link y's parent into x. x.setParent(y.getParent()); if (y.getParent() == null) { root = x; } else { if (y == y.getParent().getLeft()) { y.getParent().setLeft(x); } else { y.getParent().setRight(x); } } // Put y on x's right. x.setRight(y); y.setParent(x); // Update nodes in appropriate order (lowest to highest) boolean res = y.update(); res = x.update() || res; return res; } /** Restores red-black property to tree after splicing out node during deletion. Note that x may be null, which is why xParent must be specified. */ private void deleteFixup(RBNode x, RBNode xParent) { while ((x != root) && ((x == null) || (x.getColor() == RBColor.BLACK))) { if (x == xParent.getLeft()) { // NOTE: the text points out that w can not be null. The // reason is not obvious from simply examining the code; it // comes about because of properties of the red-black tree. RBNode w = xParent.getRight(); if (DEBUGGING) { if (w == null) { throw new RuntimeException("x's sibling should not be null"); } } if (w.getColor() == RBColor.RED) { // Case 1 w.setColor(RBColor.BLACK); xParent.setColor(RBColor.RED); leftRotate(xParent); w = xParent.getRight(); } if (((w.getLeft() == null) || (w.getLeft().getColor() == RBColor.BLACK)) && ((w.getRight() == null) || (w.getRight().getColor() == RBColor.BLACK))) { // Case 2 w.setColor(RBColor.RED); x = xParent; xParent = x.getParent(); } else { if ((w.getRight() == null) || (w.getRight().getColor() == RBColor.BLACK)) { // Case 3 w.getLeft().setColor(RBColor.BLACK); w.setColor(RBColor.RED); rightRotate(w); w = xParent.getRight(); } // Case 4 w.setColor(xParent.getColor()); xParent.setColor(RBColor.BLACK); if (w.getRight() != null) { w.getRight().setColor(RBColor.BLACK); } leftRotate(xParent); x = root; xParent = x.getParent(); } } else { // Same as clause above with "right" and "left" // exchanged // NOTE: the text points out that w can not be null. The // reason is not obvious from simply examining the code; it // comes about because of properties of the red-black tree. RBNode w = xParent.getLeft(); if (DEBUGGING) { if (w == null) { throw new RuntimeException("x's sibling should not be null"); } } if (w.getColor() == RBColor.RED) { // Case 1 w.setColor(RBColor.BLACK); xParent.setColor(RBColor.RED); rightRotate(xParent); w = xParent.getLeft(); } if (((w.getRight() == null) || (w.getRight().getColor() == RBColor.BLACK)) && ((w.getLeft() == null) || (w.getLeft().getColor() == RBColor.BLACK))) { // Case 2 w.setColor(RBColor.RED); x = xParent; xParent = x.getParent(); } else { if ((w.getLeft() == null) || (w.getLeft().getColor() == RBColor.BLACK)) { // Case 3 w.getRight().setColor(RBColor.BLACK); w.setColor(RBColor.RED); leftRotate(w); w = xParent.getLeft(); } // Case 4 w.setColor(xParent.getColor()); xParent.setColor(RBColor.BLACK); if (w.getLeft() != null) { w.getLeft().setColor(RBColor.BLACK); } rightRotate(xParent); x = root; xParent = x.getParent(); } } } if (x != null) { x.setColor(RBColor.BLACK); } } // Returns the number of black children along all paths to all // leaves of the given node private int verifyFromNode(RBNode node) { // Bottoms out at leaf nodes if (node == null) { return 1; } // Each node is either red or black if (!((node.getColor() == RBColor.RED) || (node.getColor() == RBColor.BLACK))) { if (DEBUGGING) { System.err.println("Verify failed:"); printOn(System.err); } throw new RuntimeException("Verify failed (1)"); } // Every leaf (null) is black if (node.getColor() == RBColor.RED) { // Both its children are black if (node.getLeft() != null) { if (node.getLeft().getColor() != RBColor.BLACK) { if (DEBUGGING) { System.err.println("Verify failed:"); printOn(System.err); } throw new RuntimeException("Verify failed (2)"); } } if (node.getRight() != null) { if (node.getRight().getColor() != RBColor.BLACK) { if (DEBUGGING) { System.err.println("Verify failed:"); printOn(System.err); } throw new RuntimeException("Verify failed (3)"); } } } // Every simple path to a leaf contains the same number of black // nodes int i = verifyFromNode(node.getLeft()); int j = verifyFromNode(node.getRight()); if (i != j) { if (DEBUGGING) { System.err.println("Verify failed:"); printOn(System.err); } throw new RuntimeException("Verify failed (4) (left black count = " + i + ", right black count = " + j + ")"); } return i + ((node.getColor() == RBColor.RED) ? 0 : 1); } /** Debugging */ private void printFromNode(RBNode node, PrintStream tty, int indentDepth) { for (int i = 0; i < indentDepth; i++) { tty.print(" "); } tty.print("-"); if (node == null) { tty.println(); return; } tty.println(" " + getNodeValue(node) + ((node.getColor() == RBColor.RED) ? " (red)" : " (black)")); printFromNode(node.getLeft(), tty, indentDepth + 2); printFromNode(node.getRight(), tty, indentDepth + 2); } //---------------------------------------------------------------------- // Test harness // public static void main(String[] args) { int treeSize = 10000; int maxVal = treeSize; System.err.println("Building tree..."); RBTree tree = new RBTree(new Comparator() { public int compare(Object o1, Object o2) { Integer i1 = (Integer) o1; Integer i2 = (Integer) o2; if (i1.intValue() < i2.intValue()) { return -1; } else if (i1.intValue() == i2.intValue()) { return 0; } return 1; } }); Random rand = new Random(System.currentTimeMillis()); for (int i = 0; i < treeSize; i++) { Integer val = new Integer(rand.nextInt(maxVal) + 1); try { tree.insertNode(new RBNode(val)); if ((i > 0) && (i % 100 == 0)) { System.err.print(i + "..."); System.err.flush(); } } catch (Exception e) { e.printStackTrace(); System.err.println("While inserting value " + val); tree.printOn(System.err); System.exit(1); } } // Now churn data in tree by deleting and inserting lots of nodes System.err.println(); System.err.println("Churning tree..."); for (int i = 0; i < treeSize; i++) { if (DEBUGGING && VERBOSE) { System.err.println("Iteration " + i + ":"); tree.printOn(System.err); } // Pick a random value to remove (NOTE that this is not // uniformly distributed) RBNode xParent = null; RBNode x = tree.getRoot(); int depth = 0; while (x != null) { // Traverse path down to leaf xParent = x; if (rand.nextBoolean()) { x = x.getLeft(); } else { x = x.getRight(); } ++depth; } // Pick a random height at which to remove value int height = rand.nextInt(depth); if (DEBUGGING) { if (height >= depth) { throw new RuntimeException("bug in java.util.Random"); } } // Walk that far back up (FIXME: off by one?) while (height > 0) { xParent = xParent.getParent(); --height; } // Tell the tree to remove this node if (DEBUGGING && VERBOSE) { System.err.println("(Removing value " + tree.getNodeValue(xParent) + ")"); } tree.deleteNode(xParent); // Now create and insert a new value Integer newVal = new Integer(rand.nextInt(maxVal) + 1); if (DEBUGGING && VERBOSE) { System.err.println("(Inserting value " + newVal + ")"); } tree.insertNode(new RBNode(newVal)); } System.err.println("All tests passed."); } }