diff --git a/README.md b/README.md
index 2e27cdde9..ecb3225dc 100644
--- a/README.md
+++ b/README.md
@@ -1,61 +1,140 @@
-# RedBlackNode
-`RedBlackNode` is a Java implementation of red-black trees. By subclassing
-`RedBlackNode`, clients can add arbitrary data and augmentation information to
-each node. (self-balancing binary search tree, self-balancing BST, augment,
-augmented)
+# Description
+`RedBlackNode` is a Java implementation of red-black trees. Compared to a class
+like Java's `TreeMap`, `RedBlackNode` is a low-level data structure. The
+internals of each node are exposed as public fields, allowing clients to
+directly observe and manipulate the structure of the tree. This gives clients
+flexibility, although it also enables them to violate the red-black or BST
+properties. The `RedBlackNode` class provides methods for performing various
+standard operations, such as insertion and removal.
+
+Unlike most implementations of binary search trees, `RedBlackNode` supports
+arbitrary augmentation. By subclassing `RedBlackNode`, clients can add arbitrary
+data and augmentation information to each node.
# Features
* Supports min, max, root, predecessor, successor, insert, remove, rotate,
- split, concatenate, create balanced tree, LCA, and compare operations. The
+ split, concatenate, create balanced tree, LCA, and compare operations. The
running time of each operation has optimal big O bounds.
-* Supports arbitrary augmentation by overriding `augment()`. Examples of
+* Supports arbitrary augmentation by overriding `augment()`. Examples of
augmentation are the number of non-leaf nodes in a subtree and the sum of the
- values in a subtree.
-* The parent and child links and the color are public fields. This gives
- clients flexibility. However, it is possible for a client to violate the
- red-black or BST properties.
+ values in a subtree. All `RedBlackNode` methods (such as `insert` and
+ `remove()`) call `augment()` as necessary to correctly maintain the
+ augmentation information, unless otherwise indicated in their comments.
+* The parent and child links and the color are public fields. This gives clients
+ flexibility, although it also enables them to violate the red-black or BST
+ properties.
* "Assert is valid" methods allow clients to check for errors in the structure
- or contents of a red-black tree. This is useful for debugging.
+ or contents of a red-black tree. This is useful for debugging.
* As a bonus (a proof of concept and a test case), this includes the `TreeList`
class, a `List` implementation backed by a red-black tree augmented by subtree
size.
-* Tested in Java 6.0 and 7.0. It might also work in Java 5.0.
+* Tested in Java 6.0, 7.0, and 8.0.
# Limitations
-* The values of the tree must be stored in the non-leaf nodes. `RedBlackNode`
+* The values of the tree must be stored in the non-leaf nodes. `RedBlackNode`
does not support use cases where the values must be stored in the leaf nodes.
* Augmentations that depend on information stored in a node's ancestors are not
- (easily) supported. For example, augmenting each node with the number of
- nodes in the left subtree is not (easily and efficiently) supported, because
- in order to perform a right rotation, we would need to use the parent's
- augmentation information. However, `RedBlackNode` supports augmenting each
+ (easily) supported. For example, augmenting each node with the number of nodes
+ in the left subtree is not (easily and efficiently) supported, because in
+ order to perform a right rotation, we would need to use the parent's
+ augmentation information. However, `RedBlackNode` supports augmenting each
node with the number of nodes in the subtree, which is basically equivalent.
-* The running time of each operation has optimal big O bounds. However, beyond
+* The running time of each operation has optimal big O bounds. However, beyond
this, no special effort has been made to optimize performance.
-# Example
-
-/** Red-black tree augmented by the sum of the values in the subtree. */
-public class SumNode extends RedBlackNode<SumNode> {
- public int value;
- public int sum;
+# Example usage
+```java
+class Node extends RedBlackNode> {
+ /** The value we are storing in the node. */
+ public final T value;
- public SumNode(int value) {
+ /** The number of nodes in this subtree. */
+ public int size;
+
+ public Node(T value) {
this.value = value;
}
@Override
public boolean augment() {
- int newSum = value + left.sum + right.sum;
- if (newSum == sum) {
+ int newSize = left.size + right.size + 1;
+ if (newSize == size) {
return false;
} else {
- sum = newSum;
+ size = newSize;
return true;
}
}
}
-
+```
+
+```java
+/** Stores a set of distinct values. */
+public class Tree> {
+ /** The dummy leaf node. */
+ private final Node leaf = new Node(null);
+
+ private Node root = leaf;
+
+ public void add(T value) {
+ // A comparator telling "insert" where to put the new node
+ Comparator> comparator = new Comparator>() {
+ public int compare(Node node1, Node node2) {
+ return node1.value.compareTo(node2.value);
+ }
+ };
+
+ Node newNode = new Node(value);
+ root = root.insert(newNode, false, comparator);
+ }
+
+ /** Returns the node containing the specified value, if any. */
+ private Node find(T value) {
+ Node node = root;
+ while (!node.isLeaf()) {
+ int c = value.compareTo(node.value);
+ if (c == 0) {
+ return node;
+ } else if (c < 0) {
+ node = node.left;
+ } else {
+ node = node.right;
+ }
+ }
+ return null;
+ }
+
+ public boolean contains(T value) {
+ return find(value) != null;
+ }
+
+ public void remove(T value) {
+ Node node = find(value);
+ if (node != null) {
+ root = node.remove();
+ }
+ }
+
+ /** Returns the (rank + 1)th node in the subtree rooted at "node". */
+ private Node getNodeWithRank(Node node, int rank) {
+ if (rank < 0 || rank >= node.size) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (rank == node.left.size) {
+ return node;
+ } else if (rank < node.left.size) {
+ return getNodeWithRank(node.left, rank);
+ } else {
+ return getNodeWithRank(node.right, rank - node.left.size - 1);
+ }
+ }
+
+ /** Returns the (rank + 1)th-smallest value in the tree. */
+ public T getItemWithRank(int rank) {
+ return getNodeWithRank(root, rank).value;
+ }
+}
+```
# Documentation
For more detailed instructions, check the source code to see the full API and
diff --git a/RedBlackNode.jar b/RedBlackNode.jar
index 234edde7a..3a126d57a 100644
Binary files a/RedBlackNode.jar and b/RedBlackNode.jar differ
diff --git a/src/com/github/btrekkie/red_black_node/RedBlackNode.java b/src/com/github/btrekkie/red_black_node/RedBlackNode.java
index 671c35b6b..e045b380d 100644
--- a/src/com/github/btrekkie/red_black_node/RedBlackNode.java
+++ b/src/com/github/btrekkie/red_black_node/RedBlackNode.java
@@ -8,22 +8,33 @@ import java.util.Iterator;
import java.util.Set;
/**
- * A node in a red-black tree ( https://en.wikipedia.org/wiki/Red%E2%80%93black_tree ). The RedBlackNode class provides
- * methods for performing various standard operations.
+ * A node in a red-black tree ( https://en.wikipedia.org/wiki/Red%E2%80%93black_tree ). Compared to a class like Java's
+ * TreeMap, RedBlackNode is a low-level data structure. The internals of a node are exposed as public fields, allowing
+ * clients to directly observe and manipulate the structure of the tree. This gives clients flexibility, although it
+ * also enables them to violate the red-black or BST properties. The RedBlackNode class provides methods for performing
+ * various standard operations, such as insertion and removal.
*
- * Subclasses may add arbitrary information to the node. For example, if we were to use a RedBlackNode subclass to
- * implement a sorted set, the subclass should have a field storing an element in the set. Subclasses can augment the
- * tree with arbitrary information by overriding augment().
+ * Unlike most implementations of binary search trees, RedBlackNode supports arbitrary augmentation. By subclassing
+ * RedBlackNode, clients can add arbitrary data and augmentation information to each node. For example, if we were to
+ * use a RedBlackNode subclass to implement a sorted set, the subclass would have a field storing an element in the set.
+ * If we wanted to keep track of the number of non-leaf nodes in each subtree, we would store this as a "size" field and
+ * override augment() to update this field. All RedBlackNode methods (such as "insert" and remove()) call augment() as
+ * necessary to correctly maintain the augmentation information, unless otherwise indicated.
*
- * The values of the tree are stored in the non-leaf nodes. RedBlackNode does not support use cases where values must
- * be stored in the leaf nodes. It is recommended that all of the leaf nodes in a given tree be the same (black)
- * RedBlackNode instance, to save space. The root of an empty tree is a leaf node, as opposed to null.
+ * The values of the tree are stored in the non-leaf nodes. RedBlackNode does not support use cases where values must be
+ * stored in the leaf nodes. It is recommended that all of the leaf nodes in a given tree be the same (black)
+ * RedBlackNode instance, to save space. The root of an empty tree is a leaf node, as opposed to null.
*
- * The internals of the node are exposed publicly, so clients can access or alter a node arbitrarily. This gives
- * clients flexibility. It is possible for a client to violate the red-black or BST properties.
+ * For reference, a red-black tree is a binary search tree satisfying the following properties:
*
- * @param The type of node in the tree. For example, we might have "class FooNode extends
- * RedBlackNode>".
+ * - Every node is colored red or black.
+ * - The leaf nodes, which are dummy nodes that do not store any values, are colored black.
+ * - The root is black.
+ * - Both children of each red node are black.
+ * - Every path from the root to a leaf contains the same number of black nodes.
+ *
+ * @param The type of node in the tree. For example, we might have
+ * "class FooNode extends RedBlackNode>".
* @author Bill Jacobs
*/
public abstract class RedBlackNode> implements Comparable {
@@ -64,7 +75,7 @@ public abstract class RedBlackNode> implements Compara
* children, this is equivalent to whether the augmentation information changed as a result of this call to
* augment(). For example, in the case of subtree size, this returns whether the value of the size field prior to
* calling augment() differed from the size field of the left child plus the size field of the right child plus one.
- * False positives are permitted. The return value is unspecified if we have not called augment() on this subtree
+ * False positives are permitted. The return value is unspecified if we have not called augment() on this node
* before.
*
* This method may assume that this is not a leaf node. It may not assume that the augmentation information stored
@@ -172,8 +183,9 @@ public abstract class RedBlackNode> implements Compara
}
/**
- * Performs a left rotation about this node. This method assumes that !isLeaf() && !right.isLeaf(). It calls
- * augment() on this node and on its resulting parent.
+ * Performs a left rotation about this node. This method assumes that !isLeaf() && !right.isLeaf(). It calls
+ * augment() on this node and on its resulting parent. However, it does not call augment() on any of the resulting
+ * parent's ancestors, because that is normally the responsibility of the caller.
* @return The return value from calling augment() on the resulting parent.
*/
public boolean rotateLeft() {
@@ -202,8 +214,9 @@ public abstract class RedBlackNode> implements Compara
}
/**
- * Performs a right rotation about this node. This method assumes that !isLeaf() && !left.isLeaf(). It calls
- * augment() on this node and on its resulting parent.
+ * Performs a right rotation about this node. This method assumes that !isLeaf() && !left.isLeaf(). It calls
+ * augment() on this node and on its resulting parent. However, it does not call augment() on any of the resulting
+ * parent's ancestors, because that is normally the responsibility of the caller.
* @return The return value from calling augment() on the resulting parent.
*/
public boolean rotateRight() {
@@ -349,7 +362,7 @@ public abstract class RedBlackNode> implements Compara
}
/**
- * Inserts the specified node into the tree rooted at this node. Assumes this is the root. We treat newNode as a
+ * Inserts the specified node into the tree rooted at this node. Assumes this is the root. We treat newNode as a
* solitary node that does not belong to any tree, and we ignore its initial "parent", "left", "right", and isRed
* fields.
*
@@ -357,10 +370,11 @@ public abstract class RedBlackNode> implements Compara
* should manually add the node to the appropriate location, color it red, and call fixInsertion().
*
* @param newNode The node to insert.
- * @param allowDuplicates Whether to insert newNode if there is an equal node in the tree. To check whether we
+ * @param allowDuplicates Whether to insert newNode if there is an equal node in the tree. To check whether we
* inserted newNode, check whether newNode.parent is null and the return value differs from newNode.
- * @param comparator A comparator indicating where to put the node. If this is null, we use the nodes' natural
- * order, as in N.compareTo.
+ * @param comparator A comparator indicating where to put the node. If this is null, we use the nodes' natural
+ * order, as in N.compareTo. If you are passing null, then you must override the compareTo method, because the
+ * default implementation requires the nodes to already be in the same tree.
* @return The root of the resulting tree.
*/
public N insert(N newNode, boolean allowDuplicates, Comparator super N> comparator) {
@@ -705,9 +719,10 @@ public abstract class RedBlackNode> implements Compara
}
/**
- * Returns the root of a perfectly height-balanced tree containing the specified nodes, in iteration order. This
- * method is responsible for setting the "left", "right", "parent", and isRed fields of the nodes, and calling
- * augment() as appropriate. It ignores the initial values of the "left", "right", "parent", and isRed fields.
+ * Returns the root of a perfectly height-balanced tree containing the specified nodes, in iteration order. This
+ * method is responsible for setting the "left", "right", "parent", and isRed fields of the nodes (excluding
+ * "leaf"), and calling augment() as appropriate. It ignores the initial values of the "left", "right", "parent",
+ * and isRed fields.
* @param nodes The nodes.
* @param leaf The leaf node.
* @return The root of the tree.
@@ -875,7 +890,7 @@ public abstract class RedBlackNode> implements Compara
// firstPivot: The node where we last went right, i.e. the next node to use as a pivot when concatenating with
// the pre-split tree.
// advanceFirst: Whether to set "first" to be its next black descendant at the end of the loop.
- // last, lastParent, lastPivot, advanceFirst: Analogous to "first", firstParent, firstPivot, and advanceFirst,
+ // last, lastParent, lastPivot, advanceLast: Analogous to "first", firstParent, firstPivot, and advanceFirst,
// but for the post-split tree.
if (parent != null) {
throw new IllegalArgumentException("This is not the root of a tree");
@@ -1063,8 +1078,12 @@ public abstract class RedBlackNode> implements Compara
/**
* Returns the lowest common ancestor of this node and "other" - the node that is an ancestor of both and is not the
- * parent of a node that is an ancestor of both. Assumes that this is in the same tree as "other". Assumes that
- * neither "this" nor "other" is a leaf node. This method may return "this" or "other".
+ * parent of a node that is an ancestor of both. Assumes that this is in the same tree as "other". Assumes that
+ * neither "this" nor "other" is a leaf node. This method may return "this" or "other".
+ *
+ * Note that while it is possible to compute the lowest common ancestor in O(P) time, where P is the length of the
+ * path from this node to "other", the "lca" method is not guaranteed to take O(P) time. If your application
+ * requires this, then you should write your own lowest common ancestor method.
*/
public N lca(N other) {
if (isLeaf() || other.isLeaf()) {
@@ -1108,13 +1127,16 @@ public abstract class RedBlackNode> implements Compara
}
/**
- * Returns an integer comparing the position of this node in the tree that contains it with that of "other".
- * Returns a negative number if this is earlier, a positive number if this is later, and 0 if this is at the same
- * position. Assumes that this is in the same tree as "other". Assumes that neither "this" nor "other" is a leaf
- * node.
+ * Returns an integer comparing the position of this node in the tree that contains it with that of "other". Returns
+ * a negative number if this is earlier, a positive number if this is later, and 0 if this is at the same position.
+ * Assumes that this is in the same tree as "other". Assumes that neither "this" nor "other" is a leaf node.
*
- * The base class's implementation takes O(log N) time. If a RedBlackNode subclass stores a value used to order the
+ * The base class's implementation takes O(log N) time. If a RedBlackNode subclass stores a value used to order the
* nodes, then it could override compareTo to compare the nodes' values, which would take O(1) time.
+ *
+ * Note that while it is possible to compare the positions of two nodes in O(P) time, where P is the length of the
+ * path from this node to "other", the default implementation of compareTo is not guaranteed to take O(P) time. If
+ * your application requires this, then you should write your own comparison method.
*/
@Override
public int compareTo(N other) {
@@ -1198,11 +1220,11 @@ public abstract class RedBlackNode> implements Compara
}
/**
- * Throws a RuntimeException if this is a repeated node other than a leaf node or the subtree rooted at this node
- * does not satisfy the red-black properties, excluding the requirement that the root be black.
+ * Throws a RuntimeException if the subtree rooted at this node does not satisfy the red-black properties, excluding
+ * the requirement that the root be black, or it contains a repeated node other than a leaf node.
* @param blackHeight The required number of black nodes in each path from this to a leaf node, including this and
* the leaf node.
- * @param visited The nodes we have reached thus far, other than leaf nodes. This method adds the non-leaf nodes in
+ * @param visited The nodes we have reached thus far, other than leaf nodes. This method adds the non-leaf nodes in
* the subtree rooted at this node to "visited".
*/
private void assertSubtreeIsValidRedBlack(int blackHeight, Set> visited) {
@@ -1255,9 +1277,8 @@ public abstract class RedBlackNode> implements Compara
/**
* Throws a RuntimeException if the subtree rooted at this node is not a valid red-black tree, e.g. if a red node
- * has a red child or it contains a non-leaf node "node" for which node.left.parent != node. (If parent != null,
- * it's okay if isRed is true.) This method is useful for debugging. See also
- * assertSubtreeIsValid().
+ * has a red child or it contains a non-leaf node "node" for which node.left.parent != node. (If parent != null,
+ * it's okay if isRed is true.) This method is useful for debugging. See also assertSubtreeIsValid().
*/
public void assertSubtreeIsValidRedBlack() {
if (isLeaf()) {