Merge remote-tracking branch 'rbn/trimmed' into builder-2
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
package com.github.btrekkie.arbitrary_order_collection;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* Provides objects ordered in an arbitrary, but consistent fashion, through the createValue() method. To determine the
|
||||
* relative order of two values, use ArbitraryOrderValue.compareTo. We may only compare values on which we have not
|
||||
* called "remove" that were created in the same ArbitraryOrderCollection instance. Note that despite the name,
|
||||
* ArbitraryOrderCollection does not implement Collection.
|
||||
*/
|
||||
/* We implement an ArbitraryOrderCollection using a red-black tree. We order the nodes arbitrarily.
|
||||
*/
|
||||
public class ArbitraryOrderCollection {
|
||||
/** The Comparator for ordering ArbitraryOrderNodes. */
|
||||
private static final Comparator<ArbitraryOrderNode> NODE_COMPARATOR = new Comparator<ArbitraryOrderNode>() {
|
||||
@Override
|
||||
public int compare(ArbitraryOrderNode node1, ArbitraryOrderNode node2) {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
/** The root node of the tree. */
|
||||
private ArbitraryOrderNode root = new ArbitraryOrderNode();
|
||||
|
||||
/** Adds and returns a new value for ordering. */
|
||||
public ArbitraryOrderValue createValue() {
|
||||
ArbitraryOrderNode node = new ArbitraryOrderNode();
|
||||
root = root.insert(node, true, NODE_COMPARATOR);
|
||||
return new ArbitraryOrderValue(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified value from this collection. Assumes we obtained the value by calling createValue() on this
|
||||
* instance of ArbitraryOrderCollection. After calling "remove" on a value, we may no longer compare it to other
|
||||
* values.
|
||||
*/
|
||||
public void remove(ArbitraryOrderValue value) {
|
||||
root = value.node.remove();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.github.btrekkie.arbitrary_order_collection;
|
||||
|
||||
import com.github.btrekkie.red_black_node.RedBlackNode;
|
||||
|
||||
/** A node in an ArbitraryOrderCollection tree. See ArbitraryOrderCollection. */
|
||||
class ArbitraryOrderNode extends RedBlackNode<ArbitraryOrderNode> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.github.btrekkie.arbitrary_order_collection;
|
||||
|
||||
/**
|
||||
* A value in an ArbitraryOrderCollection. To determine the relative order of two values in the same collection, call
|
||||
* compareTo.
|
||||
*/
|
||||
public class ArbitraryOrderValue implements Comparable<ArbitraryOrderValue> {
|
||||
/** The node that establishes this value's relative position. */
|
||||
final ArbitraryOrderNode node;
|
||||
|
||||
ArbitraryOrderValue(ArbitraryOrderNode node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ArbitraryOrderValue other) {
|
||||
return node.compareTo(other.node);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.github.btrekkie.arbitrary_order_collection.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.github.btrekkie.arbitrary_order_collection.ArbitraryOrderCollection;
|
||||
import com.github.btrekkie.arbitrary_order_collection.ArbitraryOrderValue;
|
||||
|
||||
public class ArbitraryOrderCollectionTest {
|
||||
/** Tests ArbitraryOrderCollection. */
|
||||
@Test
|
||||
public void test() {
|
||||
ArbitraryOrderCollection collection = new ArbitraryOrderCollection();
|
||||
List<ArbitraryOrderValue> values1 = new ArrayList<ArbitraryOrderValue>(5);
|
||||
ArbitraryOrderValue value = collection.createValue();
|
||||
assertEquals(0, value.compareTo(value));
|
||||
values1.add(value);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
values1.add(collection.createValue());
|
||||
}
|
||||
Collections.sort(values1);
|
||||
List<ArbitraryOrderValue> values2 = new ArrayList<ArbitraryOrderValue>(10);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
value = collection.createValue();
|
||||
values2.add(value);
|
||||
}
|
||||
for (int i = 0; i < 5; i++) {
|
||||
collection.remove(values2.get(2 * i));
|
||||
}
|
||||
assertEquals(0, values1.get(0).compareTo(values1.get(0)));
|
||||
assertTrue(values1.get(0).compareTo(values1.get(1)) < 0);
|
||||
assertTrue(values1.get(1).compareTo(values1.get(0)) > 0);
|
||||
assertTrue(values1.get(4).compareTo(values1.get(2)) > 0);
|
||||
assertTrue(values1.get(0).compareTo(values1.get(4)) < 0);
|
||||
|
||||
collection = new ArbitraryOrderCollection();
|
||||
values1 = new ArrayList<ArbitraryOrderValue>(1000);
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
value = collection.createValue();
|
||||
values1.add(value);
|
||||
}
|
||||
for (int i = 0; i < 500; i++) {
|
||||
collection.remove(values1.get(2 * i));
|
||||
}
|
||||
values2 = new ArrayList<ArbitraryOrderValue>(500);
|
||||
for (int i = 0; i < 500; i++) {
|
||||
values2.add(values1.get(2 * i + 1));
|
||||
}
|
||||
for (int i = 0; i < 500; i++) {
|
||||
values2.get(0).compareTo(values2.get(i));
|
||||
}
|
||||
Collections.sort(values2);
|
||||
for (int i = 0; i < 500; i++) {
|
||||
collection.createValue();
|
||||
}
|
||||
for (int i = 0; i < 499; i++) {
|
||||
assertTrue(values2.get(i).compareTo(values2.get(i + 1)) < 0);
|
||||
assertTrue(values2.get(i + 1).compareTo(values2.get(i)) > 0);
|
||||
}
|
||||
for (int i = 1; i < 500; i++) {
|
||||
assertEquals(0, values2.get(i).compareTo(values2.get(i)));
|
||||
assertTrue(values2.get(0).compareTo(values2.get(i)) < 0);
|
||||
assertTrue(values2.get(i).compareTo(values2.get(0)) > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.github.btrekkie.interval_tree;
|
||||
|
||||
/**
|
||||
* An interval tree data structure, which supports adding or removing an interval and finding an arbitrary interval that
|
||||
* contains a specified value.
|
||||
*/
|
||||
/* The interval tree is ordered in ascending order of the start an interval, with ties broken by the end of the
|
||||
* interval. Each node is augmented with the maximum ending value of an interval in the subtree rooted at the node.
|
||||
*/
|
||||
public class IntervalTree {
|
||||
/** The root node of the tree. */
|
||||
private IntervalTreeNode root = IntervalTreeNode.LEAF;
|
||||
|
||||
/** Adds the specified interval to this. */
|
||||
public void addInterval(IntervalTreeInterval interval) {
|
||||
root = root.insert(new IntervalTreeNode(interval), true, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified interval from this, if it is present.
|
||||
* @param interval The interval.
|
||||
* @return Whether the interval was present.
|
||||
*/
|
||||
public boolean removeInterval(IntervalTreeInterval interval) {
|
||||
IntervalTreeNode node = root;
|
||||
while (!node.isLeaf()) {
|
||||
if (interval.start < node.interval.start) {
|
||||
node = node.left;
|
||||
} else if (interval.start > node.interval.start) {
|
||||
node = node.right;
|
||||
} else if (interval.end < node.interval.end) {
|
||||
node = node.left;
|
||||
} else if (interval.end > node.interval.end) {
|
||||
node = node.right;
|
||||
} else {
|
||||
root = node.remove();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an aribtrary IntervalTreeInterval in this that contains the specified value. Returns null if there is no
|
||||
* such interval.
|
||||
*/
|
||||
public IntervalTreeInterval findInterval(double value) {
|
||||
IntervalTreeNode node = root;
|
||||
while (!node.isLeaf()) {
|
||||
if (value >= node.interval.start && value <= node.interval.end) {
|
||||
return node.interval;
|
||||
} else if (value <= node.left.maxEnd) {
|
||||
node = node.left;
|
||||
} else {
|
||||
node = node.right;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.github.btrekkie.interval_tree;
|
||||
|
||||
/**
|
||||
* An inclusive range of values [start, end]. Two intervals are equal if they have the same starting and ending values.
|
||||
*/
|
||||
public class IntervalTreeInterval {
|
||||
/** The smallest value in the range. */
|
||||
public final double start;
|
||||
|
||||
/** The largest value in the range. */
|
||||
public final double end;
|
||||
|
||||
public IntervalTreeInterval(double start, double end) {
|
||||
if (start > end) {
|
||||
throw new IllegalArgumentException("The end of the range must be at most the start");
|
||||
}
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof IntervalTreeInterval)) {
|
||||
return false;
|
||||
}
|
||||
IntervalTreeInterval interval = (IntervalTreeInterval)obj;
|
||||
return start == interval.start && end == interval.end;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.github.btrekkie.interval_tree;
|
||||
|
||||
import com.github.btrekkie.red_black_node.RedBlackNode;
|
||||
|
||||
/**
|
||||
* A node in an IntervalTree. See the comments for the implementation of IntervalTree. Its compareTo method orders
|
||||
* nodes as suggested in the comments for the implementation of IntervalTree.
|
||||
*/
|
||||
class IntervalTreeNode extends RedBlackNode<IntervalTreeNode> {
|
||||
/** The dummy leaf node. */
|
||||
public static final IntervalTreeNode LEAF = new IntervalTreeNode();
|
||||
|
||||
/** The interval stored in this node. */
|
||||
public IntervalTreeInterval interval;
|
||||
|
||||
/** The maximum ending value of an interval in the subtree rooted at this node. */
|
||||
public double maxEnd;
|
||||
|
||||
public IntervalTreeNode(IntervalTreeInterval interval) {
|
||||
this.interval = interval;
|
||||
maxEnd = interval.end;
|
||||
}
|
||||
|
||||
/** Constructs a new dummy leaf node. */
|
||||
private IntervalTreeNode() {
|
||||
interval = null;
|
||||
maxEnd = Double.NEGATIVE_INFINITY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean augment() {
|
||||
double newMaxEnd = Math.max(interval.end, Math.max(left.maxEnd, right.maxEnd));
|
||||
if (newMaxEnd == maxEnd) {
|
||||
return false;
|
||||
} else {
|
||||
maxEnd = newMaxEnd;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assertNodeIsValid() {
|
||||
double expectedMaxEnd;
|
||||
if (isLeaf()) {
|
||||
expectedMaxEnd = Double.NEGATIVE_INFINITY;
|
||||
} else {
|
||||
expectedMaxEnd = Math.max(interval.end, Math.max(left.maxEnd, right.maxEnd));
|
||||
}
|
||||
if (maxEnd != expectedMaxEnd) {
|
||||
throw new RuntimeException("The node's maxEnd does not match that of the children");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(IntervalTreeNode other) {
|
||||
if (interval.start != interval.end) {
|
||||
return Double.compare(interval.start, other.interval.start);
|
||||
} else {
|
||||
return Double.compare(interval.end, other.interval.end);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assertSubtreeIsValid() {
|
||||
super.assertSubtreeIsValid();
|
||||
assertOrderIsValid(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.github.btrekkie.interval_tree.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.github.btrekkie.interval_tree.IntervalTree;
|
||||
import com.github.btrekkie.interval_tree.IntervalTreeInterval;
|
||||
|
||||
public class IntervalTreeTest {
|
||||
/** Tests IntervalTree. */
|
||||
@Test
|
||||
public void test() {
|
||||
IntervalTree tree = new IntervalTree();
|
||||
assertNull(tree.findInterval(0.5));
|
||||
assertNull(tree.findInterval(-1));
|
||||
tree.addInterval(new IntervalTreeInterval(5, 7));
|
||||
tree.addInterval(new IntervalTreeInterval(42, 48));
|
||||
tree.addInterval(new IntervalTreeInterval(-1, 2));
|
||||
tree.addInterval(new IntervalTreeInterval(6, 12));
|
||||
tree.addInterval(new IntervalTreeInterval(21, 23));
|
||||
assertTrue(tree.removeInterval(new IntervalTreeInterval(-1, 2)));
|
||||
assertFalse(tree.removeInterval(new IntervalTreeInterval(-1, 2)));
|
||||
tree.addInterval(new IntervalTreeInterval(-6, -2));
|
||||
assertEquals(new IntervalTreeInterval(6, 12), tree.findInterval(8));
|
||||
assertNull(tree.findInterval(0));
|
||||
assertEquals(new IntervalTreeInterval(21, 23), tree.findInterval(21));
|
||||
assertEquals(new IntervalTreeInterval(42, 48), tree.findInterval(48));
|
||||
IntervalTreeInterval interval = tree.findInterval(6.5);
|
||||
assertTrue(new IntervalTreeInterval(5, 7).equals(interval) || new IntervalTreeInterval(6, 12).equals(interval));
|
||||
|
||||
tree = new IntervalTree();
|
||||
for (int i = 0; i < 500; i++) {
|
||||
tree.addInterval(new IntervalTreeInterval(2 * i, 2 * i + 1));
|
||||
}
|
||||
for (int i = 0; i < 250; i++) {
|
||||
tree.removeInterval(new IntervalTreeInterval(4 * i + 2, 4 * i + 3));
|
||||
}
|
||||
assertNull(tree.findInterval(123.5));
|
||||
assertEquals(new IntervalTreeInterval(124, 125), tree.findInterval(124.5));
|
||||
assertEquals(new IntervalTreeInterval(776, 777), tree.findInterval(776));
|
||||
assertEquals(new IntervalTreeInterval(0, 1), tree.findInterval(0.5));
|
||||
assertEquals(new IntervalTreeInterval(996, 997), tree.findInterval(997));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package com.github.btrekkie.red_black_node.test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
/**
|
||||
* Returns whether the subtree rooted at the specified node is valid, as in TestRedBlackNode.assertSubtreeIsValid().
|
||||
*/
|
||||
private boolean isSubtreeValid(TestRedBlackNode node) {
|
||||
try {
|
||||
node.assertSubtreeIsValid();
|
||||
return true;
|
||||
} catch (RuntimeException exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the nodes in the subtree rooted at the specified node are ordered correctly, as in
|
||||
* TestRedBlackNode.assertOrderIsValid.
|
||||
* @param comparator A comparator indicating how the nodes should be ordered. If this is null, we use the nodes'
|
||||
* natural ordering, as in TestRedBlackNode.compare.
|
||||
*/
|
||||
private boolean isOrderValid(TestRedBlackNode node, Comparator<TestRedBlackNode> comparator) {
|
||||
try {
|
||||
node.assertOrderIsValid(null);
|
||||
return true;
|
||||
} catch (RuntimeException exception) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Tests RedBlackNode.assertSubtreeIsValid() and RedBlackNode.assertOrderIsValid. */
|
||||
@Test
|
||||
public void testAssertIsValid() {
|
||||
// Create a perfectly balanced tree of height 3
|
||||
TestRedBlackNode node0 = new TestRedBlackNode(0);
|
||||
TestRedBlackNode node1 = new TestRedBlackNode(1);
|
||||
TestRedBlackNode node2 = new TestRedBlackNode(2);
|
||||
TestRedBlackNode node3 = new TestRedBlackNode(3);
|
||||
TestRedBlackNode node4 = new TestRedBlackNode(4);
|
||||
TestRedBlackNode node5 = new TestRedBlackNode(5);
|
||||
TestRedBlackNode node6 = new TestRedBlackNode(6);
|
||||
node0.parent = node1;
|
||||
node0.left = TestRedBlackNode.LEAF;
|
||||
node0.right = TestRedBlackNode.LEAF;
|
||||
node1.parent = node3;
|
||||
node1.left = node0;
|
||||
node1.right = node2;
|
||||
node1.isRed = true;
|
||||
node2.parent = node1;
|
||||
node2.left = TestRedBlackNode.LEAF;
|
||||
node2.right = TestRedBlackNode.LEAF;
|
||||
node3.left = node1;
|
||||
node3.right = node5;
|
||||
node4.parent = node5;
|
||||
node4.left = TestRedBlackNode.LEAF;
|
||||
node4.right = TestRedBlackNode.LEAF;
|
||||
node5.parent = node3;
|
||||
node5.left = node4;
|
||||
node5.right = node6;
|
||||
node5.isRed = true;
|
||||
node6.parent = node5;
|
||||
node6.left = TestRedBlackNode.LEAF;
|
||||
node6.right = TestRedBlackNode.LEAF;
|
||||
|
||||
node3.left = node3;
|
||||
node3.right = node3;
|
||||
node3.parent = node3;
|
||||
assertFalse(isSubtreeValid(node3));
|
||||
node3.left = node1;
|
||||
node3.right = node5;
|
||||
node3.parent = null;
|
||||
|
||||
node0.parent = node3;
|
||||
assertFalse(isSubtreeValid(node3));
|
||||
node0.parent = node1;
|
||||
|
||||
node1.right = node0;
|
||||
assertFalse(isSubtreeValid(node3));
|
||||
node1.right = node2;
|
||||
|
||||
node5.isRed = false;
|
||||
assertFalse(isSubtreeValid(node3));
|
||||
assertTrue(isSubtreeValid(node5));
|
||||
node5.isRed = true;
|
||||
|
||||
node3.isRed = true;
|
||||
assertFalse(isSubtreeValid(node3));
|
||||
assertTrue(isSubtreeValid(node5));
|
||||
node3.isRed = false;
|
||||
|
||||
node0.isRed = true;
|
||||
node2.isRed = true;
|
||||
node4.isRed = true;
|
||||
node6.isRed = true;
|
||||
assertFalse(isSubtreeValid(node3));
|
||||
node0.isRed = false;
|
||||
node2.isRed = false;
|
||||
node4.isRed = false;
|
||||
node6.isRed = false;
|
||||
|
||||
TestRedBlackNode.LEAF.isRed = true;
|
||||
assertFalse(isSubtreeValid(node3));
|
||||
TestRedBlackNode.LEAF.isRed = false;
|
||||
|
||||
TestRedBlackNode.LEAF.isValid = false;
|
||||
assertFalse(isSubtreeValid(node3));
|
||||
assertFalse(isSubtreeValid(TestRedBlackNode.LEAF));
|
||||
TestRedBlackNode.LEAF.isValid = true;
|
||||
|
||||
node1.isValid = false;
|
||||
assertFalse(isSubtreeValid(node3));
|
||||
node1.isValid = true;
|
||||
|
||||
node3.value = 2;
|
||||
node2.value = 3;
|
||||
assertFalse(isOrderValid(node3, null));
|
||||
assertFalse(
|
||||
isOrderValid(node3, new Comparator<TestRedBlackNode>() {
|
||||
@Override
|
||||
public int compare(TestRedBlackNode node1, TestRedBlackNode node2) {
|
||||
return node1.value - node2.value;
|
||||
}
|
||||
}));
|
||||
node3.value = 3;
|
||||
node2.value = 2;
|
||||
|
||||
node2.value = 4;
|
||||
node4.value = 2;
|
||||
assertFalse(isOrderValid(node3, null));
|
||||
node2.value = 2;
|
||||
node4.value = 4;
|
||||
|
||||
node0.value = 1;
|
||||
node1.value = 0;
|
||||
assertFalse(isOrderValid(node3, null));
|
||||
node0.value = 0;
|
||||
node1.value = 1;
|
||||
|
||||
// Do all of the assertions for which the tree is supposed to be valid at the end, to make sure we didn't make a
|
||||
// mistake undoing any of the modifications
|
||||
assertTrue(isSubtreeValid(node3));
|
||||
assertTrue(isSubtreeValid(node1));
|
||||
assertTrue(isSubtreeValid(node0));
|
||||
assertTrue(isSubtreeValid(TestRedBlackNode.LEAF));
|
||||
assertTrue(isOrderValid(node3, null));
|
||||
assertTrue(isOrderValid(node1, null));
|
||||
assertTrue(isOrderValid(node0, null));
|
||||
assertTrue(isOrderValid(TestRedBlackNode.LEAF, null));
|
||||
assertTrue(
|
||||
isOrderValid(node3, new Comparator<TestRedBlackNode>() {
|
||||
@Override
|
||||
public int compare(TestRedBlackNode node1, TestRedBlackNode node2) {
|
||||
return node1.value - node2.value;
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.github.btrekkie.red_black_node.test;
|
||||
|
||||
import com.github.btrekkie.red_black_node.RedBlackNode;
|
||||
|
||||
/** A RedBlackNode for RedBlackNodeTest. */
|
||||
class TestRedBlackNode extends RedBlackNode<TestRedBlackNode> {
|
||||
/** The dummy leaf node. */
|
||||
public static final TestRedBlackNode LEAF = new TestRedBlackNode();
|
||||
|
||||
/** The value stored in this node. "value" is unspecified if this is a leaf node. */
|
||||
public int value;
|
||||
|
||||
/** Whether this node is considered valid, as in assertNodeIsValid(). */
|
||||
public boolean isValid = true;
|
||||
|
||||
public TestRedBlackNode(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/** Constructs a new dummy leaf node. */
|
||||
private TestRedBlackNode() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assertNodeIsValid() {
|
||||
if (!isValid) {
|
||||
throw new RuntimeException("isValid is false");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(TestRedBlackNode other) {
|
||||
return value - other.value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
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) {
|
||||
if (start.right.min < min) {
|
||||
min = start.right.min;
|
||||
}
|
||||
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) {
|
||||
if (end.left.min < min) {
|
||||
min = end.left.min;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
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(-3);
|
||||
sam.add(16);
|
||||
sam.add(5);
|
||||
sam.add(8);
|
||||
sam.add(4);
|
||||
assertEquals(-3, sam.min(0, 7));
|
||||
assertEquals(12, sam.min(0, 2));
|
||||
assertEquals(-3, sam.min(2, 4));
|
||||
assertEquals(12, sam.min(0, 1));
|
||||
assertEquals(5, sam.min(3, 6));
|
||||
assertEquals(4, sam.min(4, 7));
|
||||
|
||||
sam = new SubArrayMin();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
sam.add(-Integer.bitCount(i));
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user