Added test for "lca"

This adds SubArrayMinTest, which tests RedBlackNode.lca.
This commit is contained in:
Bill Jacobs
2016-05-26 14:47:27 -07:00
parent f355e1ed2b
commit a1dba8a58c
9 changed files with 206 additions and 9 deletions

View File

@@ -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

Binary file not shown.

View File

@@ -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<N extends RedBlackNode<N>> 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<N extends RedBlackNode<N>> 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) {

View File

@@ -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 {

View File

@@ -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;
}
}

View File

@@ -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<SubArrayMinNode> {
/** 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");
}
}
}

View File

@@ -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));
}
}

View File

@@ -57,7 +57,7 @@ public class TreeList<T> extends AbstractList<T> {
}
/**
* 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<T> getNode(int index) {
if (index < 0 || index >= root.size) {

View File

@@ -7,7 +7,7 @@ class TreeListNode<T> extends RedBlackNode<TreeListNode<T>> {
/** 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) {