diff --git a/src/main/java/dataStructures/queue/monotonicQueue/MonotonicQueue.java b/src/main/java/dataStructures/queue/monotonicQueue/MonotonicQueue.java index 790c3896..dad72d57 100644 --- a/src/main/java/dataStructures/queue/monotonicQueue/MonotonicQueue.java +++ b/src/main/java/dataStructures/queue/monotonicQueue/MonotonicQueue.java @@ -1,4 +1,4 @@ -package dataStructures.queue; +package dataStructures.queue.monotonicQueue; import java.util.ArrayDeque; diff --git a/src/main/java/dataStructures/rbTree/RBNode.java b/src/main/java/dataStructures/rbTree/RBNode.java new file mode 100644 index 00000000..4af99192 --- /dev/null +++ b/src/main/java/dataStructures/rbTree/RBNode.java @@ -0,0 +1,149 @@ +package dataStructures.rbTree; + +/** + * This class represents a node for the Red-Black Tree. + * + * @param The type of element being stored in the node. + */ +public class RBNode> { + + enum VAL { + RED, + BLACK + } + + /** + * The element held by the node. + */ + private T element; + + /** + * The color the node is marked with. + */ + private VAL color; + + + /** + * The left child node. + */ + private RBNode left; + + /** + * The right child node. + */ + private RBNode right; + + /** + * The parent node. + */ + private RBNode parent; + + /** + * Constructor for our RB-Tree node. + * Defaults to red. + * + * @param element The element to add. + * @param left The left child node. + * @param right The right child node. + */ + public RBNode(T element, RBNode left, RBNode right) { + this.element = element; + this.left = left; + this.right = right; + this.color = VAL.RED; + this.parent = null; + } + + /** + * Constructor for a NIL node. + */ + public RBNode() { + this.element = null; + this.parent = null; + this.left = null; + this.right = null; + this.color = VAL.BLACK; + } + + /** + * Sets right node. + * + * @param other The new right node. + */ + public void setRight(RBNode other) { + this.right = other; + } + + /** + * Sets left node. + * + * @param other The new left node. + */ + public void setLeft(RBNode other) { + this.left = other; + } + + /** + * Sets parent node. + * + * @param other The new parent node. + */ + public void setParent(RBNode other) { + this.parent = other; + } + + /** + * Getter for element + * + * @return The element in the node. + */ + public T getElement() { + return this.element; + } + + /** + * Getter for parent node. + * + * @return The parent node. + */ + public RBNode getParent() { + return this.parent; + } + + /** + * Getter for right child node. + * + * @return The right child node. + */ + public RBNode getRight() { + return this.right; + } + + /** + * Getter for left child node. + * + * @return The left child node. + */ + public RBNode getLeft() { + return this.left; + } + + /** + * Getter for node color. + * + * @return The color the node is marked in. + */ + public VAL getColor() { + return this.color; + } + + /** + * Changes the color of the node. + * + * @param color The color to change to. + */ + public void setColor(VAL color) { + this.color = color; + } + +} diff --git a/src/main/java/dataStructures/rbTree/RBTree.java b/src/main/java/dataStructures/rbTree/RBTree.java new file mode 100644 index 00000000..764928be --- /dev/null +++ b/src/main/java/dataStructures/rbTree/RBTree.java @@ -0,0 +1,368 @@ +package dataStructures.rbTree; + +import java.util.LinkedList; +import java.util.Queue; + +/** + * This class represents a Red-Black (RB) tree. + * @param The class of the elements being added to the RB-Tree. + */ +public class RBTree> { + + /** + * Root of the tree. + */ + private RBNode root; + + /** + * NIL Node + */ + private RBNode nil = new RBNode<>(); + + /** + * Constructor for RB Tree. + */ + public RBTree() { + this.root = nil; + } + + /** + * Getter for root. + * @return The root of the RB tree. + */ + public RBNode getRoot() { + return this.root; + } + + /** + * Gets depth of the RB tree. + * @param node The node the tree is rooted from. + * @return Depth. + */ + public int getDepth(RBNode node) { + if (node == nil || node == null) { + return 0; + } + int hLeft = getDepth(node.getLeft()); + int hRight = getDepth(node.getRight()); + return 1 + Math.max(hRight, hLeft); + } + + /** + * Gets level order of the tree. + * @param node The node the tree is rooted from. + * @return The string representation of the tree. + */ + public String getLevelOrder(RBNode node) { + if (node == nil) { + return ""; + } + Queue> q = new LinkedList<>(); + q.add(node); + StringBuilder sb = new StringBuilder(); + while (!q.isEmpty()) { + RBNode curr = q.poll(); + sb.append(curr.getElement() + " "); + if (curr.getLeft() != nil) { + q.add(curr.getLeft()); + } + if (curr.getRight() != nil) { + q.add(curr.getRight()); + } + } + return sb.toString(); + } + + /** + * Inserts element into tree. + * @param element The element to insert. + * @return The newly added node. + */ + public RBNode insert(T element) { + RBNode toAdd = new RBNode<>(element, nil, nil); + RBNode prev = nil; + RBNode curr = root; + while (curr != nil) { + prev = curr; + if (element.compareTo(curr.getElement()) < 0) { + curr = curr.getLeft(); + } else { + curr = curr.getRight(); + } + } + toAdd.setParent(prev); + if (prev == nil) { + this.root = toAdd; + } else if (element.compareTo(prev.getElement()) < 0) { + prev.setLeft(toAdd); + } else { + prev.setRight(toAdd); + } + this.fixInsert(toAdd); + return toAdd; + } + + /** + * Fixes red-black properties upwards upon insert operation. + * @param node The node to fix. + */ + public void fixInsert(RBNode node) { + while (node.getParent().getColor().equals(RBNode.VAL.RED)) { + // Parent is a left child. + if (node.getParent() == node.getParent().getParent().getLeft()) { + RBNode uncle = node.getParent().getParent().getRight(); + if (uncle.getColor().equals(RBNode.VAL.RED)) { + node.getParent().setColor(RBNode.VAL.BLACK); + uncle.setColor(RBNode.VAL.BLACK); + node.getParent().getParent().setColor(RBNode.VAL.RED); + node = node.getParent().getParent(); + } else { + if (node == node.getParent().getRight()) { // Node is right child. + node = node.getParent(); + this.leftRotate(node); + } + node.getParent().setColor(RBNode.VAL.BLACK); + node.getParent().getParent().setColor(RBNode.VAL.RED); + this.rightRotate(node.getParent().getParent()); + } + } else { // Mirror image of above. + RBNode uncle = node.getParent().getParent().getLeft(); + if (uncle.getColor().equals(RBNode.VAL.RED)) { + node.getParent().setColor(RBNode.VAL.BLACK); + uncle.setColor(RBNode.VAL.BLACK); + node.getParent().getParent().setColor(RBNode.VAL.RED); + node = node.getParent().getParent(); + } else { + if (node == node.getParent().getLeft()) { + node = node.getParent(); + this.rightRotate(node); + } + node.getParent().setColor(RBNode.VAL.BLACK); + node.getParent().getParent().setColor(RBNode.VAL.RED); + this.leftRotate(node.getParent().getParent()); + } + } + } + root.setColor(RBNode.VAL.BLACK); + } + + /** + * Find element. In this case, this is "it-exists" checker. + * However, in a map-based RB Tree like Java's TreeMap, this would conduct a search based on + * a key and return the value in the key : value pair. + * @param element + * @return The element if it's in the tree, else null. + */ + public T get(T element) { + RBNode curr = root; + while (curr != nil) { + if (curr.getElement().equals(element)) { + return element; + } + if (element.compareTo(curr.getElement()) < 0) { + curr = curr.getLeft(); + } else { + curr = curr.getRight(); + } + } + return null; + } + + /** + * Helper function to conduct a left-rotate. + * @param node The node to rotate on. + */ + private void leftRotate(RBNode node) { + RBNode temp = node.getRight(); + node.setRight(temp.getLeft()); + if (temp.getLeft() != nil) { + temp.getLeft().setParent(node); + } + temp.setParent(node.getParent()); + if (node.getParent() == nil) { + root = temp; + } else if (node.getParent().getLeft() == node) { + node.getParent().setLeft(temp); + } else { + node.getParent().setRight(temp); + } + temp.setLeft(node); + node.setParent(temp); + } + + /** + * Helper function to conduct a right-rotate. + * @param node The node to rotate on. + */ + private void rightRotate(RBNode node) { + RBNode temp = node.getLeft(); + node.setLeft(temp.getRight()); + if (temp.getRight() != nil) { + temp.getRight().setParent(node); + } + temp.setParent(node.getParent()); + if (node.getParent() == nil) { + root = temp; + } else if (node == node.getParent().getLeft()) { + node.getParent().setLeft(temp); + } else { + node.getParent().setRight(temp); + } + temp.setRight(node); + node.setParent(temp); + } + + /** + * Helper function that transposes node A into B. + * @param a The node to transpose. + * @param b The node to transpose to. + */ + public void transplant(RBNode a, RBNode b) { + if (b.getParent() == nil) { + root = a; + } else if (b == b.getParent().getLeft()) { + b.getParent().setLeft(a); + } else { + b.getParent().setRight(a); + } + a.setParent(b.getParent()); + } + + /** + * Gets the node with the minimum value. + * @param node The node tree is rooted at. + * @return The node with the minimum value. + */ + public RBNode getMin(RBNode node) { + while (node.getLeft() != nil) { + node = node.getLeft(); + } + return node; + } + + /** + * Deletes a node from the tree. + * @param node The node to delete. + */ + public void delete(RBNode node) { + RBNode x; + RBNode y; + RBNode.VAL deletedColor = node.getColor(); + if (node.getLeft() == nil) { + x = node.getRight(); + this.transplant(node.getRight(), node); + } else if (node.getRight() == nil) { + x = node.getLeft(); + this.transplant(node.getLeft(), node); + } else { + y = this.getMin(node.getRight()); + deletedColor = y.getColor(); + x = y.getRight(); + if (y.getParent() == node) { + x.setParent(y); + } else { + this.transplant(y.getRight(), y); + y.setRight(node.getRight()); + y.getRight().setParent(y); + } + this.transplant(y, node); + y.setLeft(node.getLeft()); + y.getLeft().setParent(y); + y.setColor(node.getColor()); + } + if (deletedColor.equals(RBNode.VAL.BLACK)) { + this.fixDelete(x); + } + } + + /** + * Fixes red-black properties after delete operation. + * @param node The node to fix. + */ + private void fixDelete(RBNode node) { + while (node != root && node.getColor().equals(RBNode.VAL.BLACK)) { + if (node == node.getParent().getLeft()) { + RBNode brother = node.getParent().getRight(); + if (brother.getColor().equals(RBNode.VAL.RED)) { + brother.setColor(RBNode.VAL.BLACK); + node.getParent().setColor(RBNode.VAL.RED); + this.leftRotate(node.getParent()); + brother = node.getParent().getRight(); + } + if (brother.getLeft().getColor().equals(RBNode.VAL.BLACK) + && brother.getRight().getColor().equals(RBNode.VAL.BLACK)) { + brother.setColor(RBNode.VAL.RED); + node = node.getParent(); + } else { + if (brother.getRight().getColor().equals(RBNode.VAL.BLACK)) { + brother.getLeft().setColor(RBNode.VAL.BLACK); + brother.setColor(RBNode.VAL.RED); + this.rightRotate(brother); + brother = node.getParent().getRight(); + } + brother.setColor(node.getParent().getColor()); + node.getParent().setColor(RBNode.VAL.BLACK); + brother.getRight().setColor(RBNode.VAL.BLACK); + this.leftRotate(node.getParent()); + node = this.root; + } + } else { + RBNode brother = node.getParent().getLeft(); + if (brother.getColor().equals(RBNode.VAL.RED)) { + brother.setColor(RBNode.VAL.BLACK); + node.getParent().setColor(RBNode.VAL.RED); + this.rightRotate(node.getParent()); + brother = node.getParent().getLeft(); + } + if (brother.getLeft().getColor().equals(RBNode.VAL.BLACK) + && brother.getRight().getColor().equals(RBNode.VAL.BLACK)) { + brother.setColor(RBNode.VAL.RED); + node = node.getParent(); + } else { + if (brother.getLeft().getColor().equals(RBNode.VAL.BLACK)) { + brother.getRight().setColor(RBNode.VAL.BLACK); + brother.setColor(RBNode.VAL.RED); + this.leftRotate(brother); + brother = node.getParent().getLeft(); + } + brother.setColor(node.getParent().getColor()); + node.getParent().setColor(RBNode.VAL.BLACK); + brother.getLeft().setColor(RBNode.VAL.BLACK); + this.rightRotate(node.getParent()); + node = this.root; + } + } + } + node.setColor(RBNode.VAL.BLACK); + } + + /** + * Counts the number of black nodes in a tree. + * @param node The node the tree is rooted from. + * @return The number of black nodes. If the property has been broken, -1. + */ + public int countBlack(RBNode node) { + if (node == this.nil) { + return 0; + } + int leftCount = countBlack(node.getLeft()); + int rightCount = countBlack(node.getRight()); + // Check equality in black nodes. + if (leftCount == -1 || rightCount == -1 || leftCount != rightCount) { + return -1; + } else { + if (node.getColor().equals(RBNode.VAL.BLACK)) { + return leftCount + 1; + } + return leftCount; + } + } + + /** + * Checks if red black property is met. + * @return True if property is met. + */ + public boolean isRedBlackTree() { + return countBlack(this.root) != -1; + } +} diff --git a/src/main/java/dataStructures/rbTree/README.md b/src/main/java/dataStructures/rbTree/README.md new file mode 100644 index 00000000..7aed629e --- /dev/null +++ b/src/main/java/dataStructures/rbTree/README.md @@ -0,0 +1,36 @@ +# Red-Black Tree + +The red-black tree is another form of self-balancing binary tree with a looser constraint +as compared to an AVL Tree. + +It achieves balance with the following properties: + +1) Every node is colored either red or black. (In our implementation we denote this property with the tag field) +2) The root is black. +3) The leaf is black. +4) A red node can only have black children. +5) For any node, a path from itself to any of its descendant leaf nodes must contain the same amount of nodes that are colored black. + +![rb-image](https://miro.medium.com/v2/resize:fit:1400/format:webp/1*xGjCx645d9RPwOm5mIpthA.jpeg) + +Much like an AVL tree, red black properties are maintained using a series of +left and right rotations after an insert or delete operation is conducted. There are 5 different +cases to consider during an insert operation and 6 different cases to consider during a delete operation. +[This article](https://www.happycoders.eu/algorithms/red-black-tree-java/) explains the cases. + +## Operation Orders + +Much like other balanced BSTs, RB Trees have *O(logN)* operations. + +## Notes + +The AVL balance property is stronger than the red-black property, this means it requires more +rotations for the tree to balance after operations. This makes RB Trees +a more ideal data structure for use cases that require a lot of insert and delete operations. + +However, RB Trees take up more space as each node now needs to track what color it is. + +Interestingly, Java's [TreeMap](https://docs.oracle.com/javase/8/docs/api/java/util/TreeMap.html) +and [TreeSet](https://docs.oracle.com/javase/8/docs/api/java/util/TreeSet.html) +is a RB Tree instead of an AVL Tree. It is recommended to use these instead of creating your own +RB Tree as unlike BSTs, an RB Tree is a lot more complex to implement. \ No newline at end of file diff --git a/src/test/java/dataStructures/queue/MonotonicQueueTest.java b/src/test/java/dataStructures/queue/MonotonicQueueTest.java index 8bca1530..a30d0dbd 100644 --- a/src/test/java/dataStructures/queue/MonotonicQueueTest.java +++ b/src/test/java/dataStructures/queue/MonotonicQueueTest.java @@ -3,6 +3,8 @@ import org.junit.Assert; import org.junit.Test; +import dataStructures.queue.monotonicQueue.MonotonicQueue; + /** * This class implements tests for the monotonic queue. */ diff --git a/src/test/java/dataStructures/rbTree/RBTreeTest.java b/src/test/java/dataStructures/rbTree/RBTreeTest.java new file mode 100644 index 00000000..5de96693 --- /dev/null +++ b/src/test/java/dataStructures/rbTree/RBTreeTest.java @@ -0,0 +1,82 @@ +package dataStructures.rbTree; + +import org.junit.Assert; +import org.junit.Test; +public class RBTreeTest { + @Test + public void testInsertAndSearch() { + RBTree tree = new RBTree<>(); + Assert.assertEquals(null, tree.get(10)); + tree.insert(1); + tree.insert(2); + tree.insert(3); + Assert.assertEquals((Integer) 1, tree.get(1)); + Assert.assertEquals((Integer) 2, tree.get(2)); + Assert.assertEquals((Integer) 3, tree.get(3)); + Assert.assertEquals(true, tree.isRedBlackTree()); + } + + @Test + public void testDeleteAndSearch() { + RBTree tree = new RBTree<>(); + Assert.assertEquals(null, tree.get(10)); + Assert.assertEquals(true, tree.isRedBlackTree()); + RBNode del1 = tree.insert(1); + tree.insert(5); + tree.insert(8); + tree.insert(2); + RBNode del2 = tree.insert(3); + Assert.assertEquals((Integer) 1, tree.get(1)); + Assert.assertEquals((Integer) 2, tree.get(2)); + Assert.assertEquals((Integer) 3, tree.get(3)); + Assert.assertEquals(true, tree.isRedBlackTree()); + tree.delete(del2); + Assert.assertEquals(null, tree.get(3)); + Assert.assertEquals(true, tree.isRedBlackTree()); + tree.delete(del1); + Assert.assertEquals(null, tree.get(1)); + Assert.assertEquals(true, tree.isRedBlackTree()); + } + + @Test + public void testRedBlackRotations() { + RBTree tree = new RBTree<>(); + + // Testing insert rotations + Assert.assertEquals("", tree.getLevelOrder(tree.getRoot())); + tree.insert(1); + RBNode del2 = tree.insert(2); + tree.insert(3); + Assert.assertEquals("2 1 3 ", tree.getLevelOrder(tree.getRoot())); + Assert.assertEquals(true, tree.isRedBlackTree()); + + RBNode del4 = tree.insert(4); + RBNode del5 = tree.insert(5); + Assert.assertEquals("2 1 4 3 5 ", tree.getLevelOrder(tree.getRoot())); + Assert.assertEquals(true, tree.isRedBlackTree()); + + tree.insert(9); + RBNode del6 = tree.insert(6); + tree.insert(7); + RBNode del8 = tree.insert(8); + Assert.assertEquals("4 2 6 1 3 5 8 7 9 ", tree.getLevelOrder(tree.getRoot())); + + // Testing delete rotations + tree.delete(del6); + Assert.assertEquals("4 2 7 1 3 5 8 9 ", tree.getLevelOrder(tree.getRoot())); + Assert.assertEquals(4, tree.getDepth(tree.getRoot())); + tree.delete(del5); + Assert.assertEquals("4 2 8 1 3 7 9 ", tree.getLevelOrder(tree.getRoot())); + Assert.assertEquals(true, tree.isRedBlackTree()); + tree.delete(del2); + tree.delete(del8); + Assert.assertEquals(null, tree.get(8)); + Assert.assertEquals(3, tree.getDepth(tree.getRoot())); + Assert.assertEquals(true, tree.isRedBlackTree()); + Assert.assertEquals("4 3 9 1 7 ", tree.getLevelOrder(tree.getRoot())); + tree.delete(del4); + Assert.assertEquals("7 3 9 1 ", tree.getLevelOrder(tree.getRoot())); + Assert.assertEquals(true, tree.isRedBlackTree()); + Assert.assertEquals(3, tree.getDepth(tree.getRoot())); + } +}