diff --git a/README.md b/README.md index 5afa37bdd..2e27cdde9 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ augmented) * Tested in Java 6.0 and 7.0. It might also work in Java 5.0. # Limitations +* 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 diff --git a/RedBlackNode.jar b/RedBlackNode.jar index e6a079f04..fe3488c51 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 ebc8d6172..cc84a6ce7 100644 --- a/src/com/github/btrekkie/red_black_node/RedBlackNode.java +++ b/src/com/github/btrekkie/red_black_node/RedBlackNode.java @@ -9,14 +9,16 @@ 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. The leaf nodes in a tree are dummy nodes colored black that do - * not contain any values. It is recommended that all of the leaf nodes in a given tree be the same RedBlackNode - * instance, to save space. The root of an empty tree is a leaf node, as opposed to null. + * methods for performing various standard operations. * * 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(). * + * 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. * @@ -539,6 +541,9 @@ public abstract class RedBlackNode> implements Compara * "left", "right", "parent", and "isBlack" fields. */ public void removeWithoutGettingRoot() { + if (isLeaf()) { + throw new IllegalArgumentException("Attempted to remove a leaf node"); + } N replacement; if (left.isLeaf() || right.isLeaf()) { replacement = null; @@ -619,6 +624,10 @@ public abstract class RedBlackNode> implements Compara * @return The root of the resulting tree. */ public N remove() { + if (isLeaf()) { + throw new IllegalArgumentException("Attempted to remove a leaf node"); + } + // Find an arbitrary non-leaf node in the tree other than this node N node; if (parent != null) { diff --git a/src/com/github/btrekkie/red_black_node/test/RedBlackNodeTest.java b/src/com/github/btrekkie/red_black_node/test/RedBlackNodeTest.java index 6059745aa..4d571aeb1 100644 --- a/src/com/github/btrekkie/red_black_node/test/RedBlackNodeTest.java +++ b/src/com/github/btrekkie/red_black_node/test/RedBlackNodeTest.java @@ -8,10 +8,10 @@ import java.util.Comparator; import org.junit.Test; /** - * Tests RedBlackNode. Most of the testing for RedBlackNode takes place in TreeListTest, IntervalTreeTest, and - * ArbitraryOrderCollectionTest, which test realistic use cases of RedBlackNode. TreeListTest tests most of the - * RedBlackNode methods, while IntervalTreeTest tests non-structural augmentation and the "insert" method, - * ArbitraryOrderCollectionTest tests compareTo, and RedBlackNodeTest tests assertSubtreeIsValid() and + * Tests RedBlackNode. Most of the testing for RedBlackNode takes place in TreeListTest, IntervalTreeTest, + * SubArrayMinTest, and ArbitraryOrderCollectionTest, which test realistic use cases of RedBlackNode. TreeListTest + * tests most of the RedBlackNode methods, while IntervalTreeTest tests the "insert" method, SubArrayMinTest tests + * "lca", ArbitraryOrderCollectionTest tests compareTo, and RedBlackNodeTest tests assertSubtreeIsValid() and * assertOrderIsValid. */ public class RedBlackNodeTest { diff --git a/src/com/github/btrekkie/sub_array_min/SubArrayMin.java b/src/com/github/btrekkie/sub_array_min/SubArrayMin.java new file mode 100644 index 000000000..874db6575 --- /dev/null +++ b/src/com/github/btrekkie/sub_array_min/SubArrayMin.java @@ -0,0 +1,85 @@ +package com.github.btrekkie.sub_array_min; + +/** A list of integers. SubArrayMin provides the ability to quickly determine the minimum value in a given sublist. */ +/* We implement SubArrayMin using a red-black tree augmented by subtree size and minimum value. Using the subtree size + * augmentation, we can find the node at a given index. + */ +public class SubArrayMin { + /** The root node. */ + private SubArrayMinNode root = SubArrayMinNode.LEAF; + + /** Appends the specified value to the end of the list. */ + public void add(int value) { + SubArrayMinNode newNode = new SubArrayMinNode(value); + newNode.left = SubArrayMinNode.LEAF; + newNode.right = SubArrayMinNode.LEAF; + if (root.isLeaf()) { + root = newNode; + newNode.augment(); + } else { + SubArrayMinNode node = root.max(); + node.right = newNode; + newNode.parent = node; + newNode.isRed = true; + root = newNode.fixInsertion(); + } + } + + /** Returns the node for the element with the specified index. Assumes "index" is in the range [0, root.size). */ + private SubArrayMinNode getNode(int index) { + if (index < 0 || index >= root.size) { + throw new IndexOutOfBoundsException("Index " + index + " is not in the range [0, " + root.size + ")"); + } + int rank = index; + SubArrayMinNode node = root; + while (rank != node.left.size) { + if (rank < node.left.size) { + node = node.left; + } else { + rank -= node.left.size + 1; + node = node.right; + } + } + return node; + } + + /** + * Returns the minimum value in the subarray starting at index startIndex and ending at index endIndex - 1, + * inclusive. Assumes startIndex < endIndex, and assumes this contains indices startIndex and endIndex - 1. + */ + public int min(int startIndex, int endIndex) { + if (startIndex >= endIndex) { + throw new IllegalArgumentException("The start index must be less than the end index"); + } + SubArrayMinNode start = getNode(startIndex); + SubArrayMinNode end = getNode(endIndex - 1); + SubArrayMinNode lca = start.lca(end); + + int min = Math.min(lca.value, Math.min(start.value, end.value)); + if (start != lca) { + for (SubArrayMinNode node = start; node.parent != lca; node = node.parent) { + if (node.parent.left == node) { + if (node.parent.value < min) { + min = node.parent.value; + } + if (node.parent.right.min < min) { + min = node.parent.right.min; + } + } + } + } + if (end != lca) { + for (SubArrayMinNode node = end; node.parent != lca; node = node.parent) { + if (node.parent.right == node) { + if (node.parent.value < min) { + min = node.parent.value; + } + if (node.parent.left.min < min) { + min = node.parent.left.min; + } + } + } + } + return min; + } +} diff --git a/src/com/github/btrekkie/sub_array_min/SubArrayMinNode.java b/src/com/github/btrekkie/sub_array_min/SubArrayMinNode.java new file mode 100644 index 000000000..1839b7220 --- /dev/null +++ b/src/com/github/btrekkie/sub_array_min/SubArrayMinNode.java @@ -0,0 +1,56 @@ +package com.github.btrekkie.sub_array_min; + +import com.github.btrekkie.red_black_node.RedBlackNode; + +/** A node in a SubArrayMin object. See the comments for the implementation of that class. */ +class SubArrayMinNode extends RedBlackNode { + /** The dummy leaf node. */ + public static final SubArrayMinNode LEAF = new SubArrayMinNode(); + + /** The element stored in the node. The value is unspecified if this is a leaf node. */ + public final int value; + + /** The number of elements in the subtree rooted at this node. */ + public int size; + + /** The minimum element in the subtree rooted at this node. This is Integer.MAX_VALUE if this is a leaf node. */ + public int min; + + public SubArrayMinNode(int value) { + this.value = value; + } + + private SubArrayMinNode() { + value = 0; + min = Integer.MAX_VALUE; + } + + @Override + public boolean augment() { + int newSize = left.size + right.size + 1; + int newMin = Math.min(value, Math.min(left.min, right.min)); + if (newSize == size && newMin == min) { + return false; + } else { + size = newSize; + min = newMin; + return true; + } + } + + @Override + public void assertNodeIsValid() { + int expectedSize; + int expectedMin; + if (isLeaf()) { + expectedSize = 0; + expectedMin = Integer.MAX_VALUE; + } else { + expectedSize = left.size + right.size + 1; + expectedMin = Math.min(value, Math.min(left.min, right.min)); + } + if (size != expectedSize || min != expectedMin) { + throw new RuntimeException("The node's size or minimum value does not match that of the children"); + } + } +} diff --git a/src/com/github/btrekkie/sub_array_min/test/SubArrayMinTest.java b/src/com/github/btrekkie/sub_array_min/test/SubArrayMinTest.java new file mode 100644 index 000000000..d69657f7a --- /dev/null +++ b/src/com/github/btrekkie/sub_array_min/test/SubArrayMinTest.java @@ -0,0 +1,45 @@ +package com.github.btrekkie.sub_array_min.test; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.github.btrekkie.sub_array_min.SubArrayMin; + +public class SubArrayMinTest { + /** Tests SubArrayMin. */ + @Test + public void test() { + SubArrayMin sam = new SubArrayMin(); + sam.add(12); + sam.add(42); + sam.add(16); + sam.add(-3); + sam.add(8); + sam.add(5); + sam.add(4); + assertEquals(-3, sam.min(0, 7)); + assertEquals(12, sam.min(0, 3)); + assertEquals(-3, sam.min(2, 4)); + assertEquals(12, sam.min(0, 1)); + assertEquals(5, sam.min(4, 6)); + assertEquals(4, sam.min(4, 7)); + + sam = new SubArrayMin(); + for (int i = 0; i < 1000; i++) { + // Taken from http://stackoverflow.com/a/109025 + int value1 = i - ((i >>> 1) & 0x55555555); + int value2 = (value1 & 0x33333333) + ((value1 >>> 2) & 0x33333333); + int setBitCount = (((value2 + (value2 >>> 4)) & 0x0f0f0f0f) * 0x01010101) >>> 24; + + sam.add(-setBitCount); + } + assertEquals(0, sam.min(0, 1)); + assertEquals(-4, sam.min(0, 30)); + assertEquals(-9, sam.min(0, 1000)); + assertEquals(-9, sam.min(123, 777)); + assertEquals(-8, sam.min(777, 888)); + assertEquals(-6, sam.min(777, 788)); + assertEquals(-9, sam.min(900, 1000)); + } +} diff --git a/src/com/github/btrekkie/tree_list/TreeList.java b/src/com/github/btrekkie/tree_list/TreeList.java index dea9bb65c..ba96c716f 100644 --- a/src/com/github/btrekkie/tree_list/TreeList.java +++ b/src/com/github/btrekkie/tree_list/TreeList.java @@ -57,7 +57,7 @@ public class TreeList extends AbstractList { } /** - * Returns the node for get(index). Raises an IndexOutOfBoundsException if "index" is not in the range [0, size()). + * Returns the node for get(index). Throws an IndexOutOfBoundsException if "index" is not in the range [0, size()). */ private TreeListNode getNode(int index) { if (index < 0 || index >= root.size) { diff --git a/src/com/github/btrekkie/tree_list/TreeListNode.java b/src/com/github/btrekkie/tree_list/TreeListNode.java index a16637e81..de78368db 100644 --- a/src/com/github/btrekkie/tree_list/TreeListNode.java +++ b/src/com/github/btrekkie/tree_list/TreeListNode.java @@ -7,7 +7,7 @@ class TreeListNode extends RedBlackNode> { /** The element stored in the node. The value is unspecified if this is a leaf node. */ public T value; - /** The number of elements in the subtree rooted at this node, not counting leaf nodes, as in TreeList.leaf. */ + /** The number of elements in the subtree rooted at this node. */ public int size; public TreeListNode(T value) {