diff --git a/.gitignore b/.gitignore index 67fae9804..0c2ab5325 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,5 @@ hs_err_pid* .DS_Store -target +target/** !target/RedBlackNode*.jar diff --git a/README.md b/README.md index 39bf14c85..4a5080399 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ data and augmentation information to each node. # 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. + (Note that many data structures can be implemented with either approach.) * 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 diff --git a/pom.xml b/pom.xml index c6a125803..9dd63689b 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.github.btrekkie.red_black_node RedBlackNode - 1.0.0 + 1.0.1 RedBlackNode Java implementation of augmented red-black trees. Easily maintain custom augmentation information by subclassing RedBlackNode: the base class does the work for you. diff --git a/src/main/java/com/github/btrekkie/red_black_node/RedBlackNode.java b/src/main/java/com/github/btrekkie/red_black_node/RedBlackNode.java index b604d5267..7777b7361 100644 --- a/src/main/java/com/github/btrekkie/red_black_node/RedBlackNode.java +++ b/src/main/java/com/github/btrekkie/red_black_node/RedBlackNode.java @@ -269,6 +269,7 @@ public abstract class RedBlackNode> implements Compara grandparent.left.isRed = false; grandparent.right.isRed = false; grandparent.isRed = true; + if (changed) { changed = parent.augment(); if (changed) { @@ -288,6 +289,7 @@ public abstract class RedBlackNode> implements Compara node = parent; parent = node.parent; } + if (parent.left == node) { boolean grandparentChanged = grandparent.rotateRight(); if (augment) { @@ -299,6 +301,7 @@ public abstract class RedBlackNode> implements Compara changed = grandparentChanged; } } + parent.isRed = false; grandparent.isRed = true; node = parent; @@ -366,8 +369,8 @@ public abstract class RedBlackNode> implements Compara * solitary node that does not belong to any tree, and we ignore its initial "parent", "left", "right", and isRed * fields. * - * If it is not efficient or convenient for a subclass to find the location for a node using a Comparator, then it - * should manually add the node to the appropriate location, color it red, and call fixInsertion(). + * If it is not efficient or convenient to find the location for a node using a Comparator, then you 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 @@ -544,6 +547,7 @@ public abstract class RedBlackNode> implements Compara } } + // Update augmentation info N parent = sibling.parent; if (changed && parent != null) { if (!haveAugmentedParent) { @@ -594,6 +598,7 @@ public abstract class RedBlackNode> implements Compara } if (child != null) { + // Replace this node with its child child.parent = parent; if (parent != null) { if (parent.left == this) { @@ -603,6 +608,7 @@ public abstract class RedBlackNode> implements Compara } } child.isRed = false; + if (child.parent != null) { N parent; for (parent = child.parent; parent != null; parent = parent.parent) { @@ -612,6 +618,7 @@ public abstract class RedBlackNode> implements Compara } } } else if (parent != null) { + // Replace this node with a leaf node N leaf = left; N parent = this.parent; N sibling; @@ -622,6 +629,7 @@ public abstract class RedBlackNode> implements Compara parent.right = leaf; sibling = parent.left; } + if (!isRed) { RedBlackNode siblingNode = sibling; siblingNode.fixSiblingDeletion(); @@ -704,6 +712,7 @@ public abstract class RedBlackNode> implements Compara N left = createTree(iterator, (size - 1) / 2, height - 1, leaf); N node = iterator.next(); N right = createTree(iterator, size / 2, height - 1, leaf); + node.isRed = height % 4 == 1; node.left = left; node.right = right; @@ -713,6 +722,7 @@ public abstract class RedBlackNode> implements Compara if (!right.isLeaf()) { right.parent = node; } + node.augment(); return node; } @@ -732,10 +742,12 @@ public abstract class RedBlackNode> implements Compara if (size == 0) { return leaf; } + int height = 0; for (int subtreeSize = size; subtreeSize > 0; subtreeSize /= 2) { height++; } + N node = createTree(nodes.iterator(), size, height, leaf); node.parent = null; node.isRed = false; @@ -866,10 +878,10 @@ public abstract class RedBlackNode> implements Compara /** * Splits the tree rooted at this node into two trees, so that the first element of the return value is the root of * a tree consisting of the nodes that were before the specified node, and the second element of the return value is - * the root of a tree consisting of the nodes that were equal to or after the specified node. This method assumes - * that this node is the root. It assumes that this is in the same tree as splitNode. It takes O(log N) time. It - * is considerably more efficient than removing all of the elements after splitNode and then creating a new tree - * from those nodes. + * the root of a tree consisting of the nodes that were equal to or after the specified node. This method is + * destructive, meaning it does not preserve the original tree. It assumes that this node is the root and is in the + * same tree as splitNode. It takes O(log N) time. It is considerably more efficient than removing all of the + * nodes at or after splitNode and then creating a new tree from those nodes. * @param The node at which to split the tree. * @return An array consisting of the resulting trees. */ @@ -895,6 +907,9 @@ public abstract class RedBlackNode> implements Compara if (parent != null) { throw new IllegalArgumentException("This is not the root of a tree"); } + if (isLeaf() || splitNode.isLeaf()) { + throw new IllegalArgumentException("The root or the split node is a leaf"); + } // Create an array containing the path from the root to splitNode int depth = 1; @@ -905,8 +920,7 @@ public abstract class RedBlackNode> implements Compara if (parent != this) { throw new IllegalArgumentException("The split node does not belong to this tree"); } - @SuppressWarnings("unchecked") - N[] path = (N[])Array.newInstance(getClass(), depth); + RedBlackNode[] path = new RedBlackNode[depth]; for (parent = splitNode; parent != null; parent = parent.parent) { depth--; path[depth] = parent; @@ -1052,22 +1066,18 @@ public abstract class RedBlackNode> implements Compara } // Add lastPivot to the post-split tree - if (last == null) { - last = leaf; - } else { - lastPivot.isRed = true; - lastPivot.parent = lastParent; - if (lastParent != null) { - lastParent.left = lastPivot; - } - lastPivot.left = leaf; - lastPivot.right = leaf; - lastPivot.fixInsertionWithoutGettingRoot(false); - for (last = lastPivot; last.parent != null; last = last.parent) { - last.augment(); - } + lastPivot.isRed = true; + lastPivot.parent = lastParent; + if (lastParent != null) { + lastParent.left = lastPivot; + } + lastPivot.left = leaf; + lastPivot.right = leaf; + lastPivot.fixInsertionWithoutGettingRoot(false); + for (last = lastPivot; last.parent != null; last = last.parent) { last.augment(); } + last.augment(); @SuppressWarnings("unchecked") N[] result = (N[])Array.newInstance(getClass(), 2); diff --git a/src/main/java/com/github/btrekkie/tree_list/TreeList.java b/src/main/java/com/github/btrekkie/tree_list/TreeList.java index 41e411d91..3201bbd23 100644 --- a/src/main/java/com/github/btrekkie/tree_list/TreeList.java +++ b/src/main/java/com/github/btrekkie/tree_list/TreeList.java @@ -140,9 +140,8 @@ public class TreeList extends AbstractList { public T remove(int index) { TreeListNode node = getNode(index); modCount++; - T value = node.value; root = node.remove(); - return value; + return node.value; } @Override @@ -151,6 +150,7 @@ public class TreeList extends AbstractList { throw new IndexOutOfBoundsException("Index " + index + " is not in the range [0, " + root.size + "]"); } modCount++; + if (values.isEmpty()) { return false; } else { @@ -325,6 +325,7 @@ public class TreeList extends AbstractList { } else if (modCount != TreeList.this.modCount) { throw new ConcurrentModificationException(); } + haveCalledNextOrPrevious = true; justCalledNext = true; haveModified = false; @@ -357,6 +358,7 @@ public class TreeList extends AbstractList { } else if (modCount != TreeList.this.modCount) { throw new ConcurrentModificationException(); } + haveCalledNextOrPrevious = true; justCalledNext = false; haveModified = false; @@ -380,6 +382,7 @@ public class TreeList extends AbstractList { } else if (modCount != TreeList.this.modCount) { throw new ConcurrentModificationException(); } + if (justCalledNext) { prevNode.value = value; } else { diff --git a/target/RedBlackNode-1.0.0.jar b/target/RedBlackNode-1.0.0.jar deleted file mode 100644 index 0b2b3dbf9..000000000 Binary files a/target/RedBlackNode-1.0.0.jar and /dev/null differ diff --git a/target/RedBlackNode-1.0.1.jar b/target/RedBlackNode-1.0.1.jar new file mode 100644 index 000000000..e73187bc8 Binary files /dev/null and b/target/RedBlackNode-1.0.1.jar differ