From 9f09c3fb751e3e86bf83f3484ae82361d0d14f76 Mon Sep 17 00:00:00 2001 From: yeoshuheng Date: Wed, 27 Dec 2023 19:59:46 +0800 Subject: [PATCH 01/15] feat: first commit on RB tree --- .../java/dataStructures/rbTree/RBNode.java | 19 + .../java/dataStructures/rbTree/RBTree.java | 410 ++++++++++++++++++ 2 files changed, 429 insertions(+) create mode 100644 src/main/java/dataStructures/rbTree/RBNode.java create mode 100644 src/main/java/dataStructures/rbTree/RBTree.java diff --git a/src/main/java/dataStructures/rbTree/RBNode.java b/src/main/java/dataStructures/rbTree/RBNode.java new file mode 100644 index 00000000..09581f23 --- /dev/null +++ b/src/main/java/dataStructures/rbTree/RBNode.java @@ -0,0 +1,19 @@ +package dataStructures.rbTree; + +public class RBNode > { + T value; + RBNode left = null; + RBNode right = null; + + RBNode parent = null; + + /** + * Marks the color of the RB node, true if red, false if black. + */ + boolean tag; + + public RBNode(T value, boolean tag) { + this.value = value; + this.tag = tag; + } +} diff --git a/src/main/java/dataStructures/rbTree/RBTree.java b/src/main/java/dataStructures/rbTree/RBTree.java new file mode 100644 index 00000000..4ba5a9ce --- /dev/null +++ b/src/main/java/dataStructures/rbTree/RBTree.java @@ -0,0 +1,410 @@ +package dataStructures.rbTree; +import dataStructures.avlTree.Node; +import dataStructures.rbTree.RBNode; +import java.lang.StringBuilder; + +import java.util.*; +public class RBTree> { + RBNode root = null; + + /** + * Inserts a node into RB Tree. + * + * @param val The value to be inserted. + */ + public void insertNode(T val) { + RBNode current = root; + RBNode parent = null; + while (!current.equals(null)) { // Iteratively search for insertion point. + parent = current; + if (val.compareTo(current.value) == -1) { + current = current.left; + } else { + current = current.right; + } + } + // Inserted node will always be kept as red first. + RBNode toInsert = new RBNode<>(val, true); + if (parent == null) { // First node. + this.root = toInsert; + } else if (val.compareTo(parent.value) == -1) { + parent.left = toInsert; + toInsert.parent = parent; + } else { + parent.right = toInsert; + toInsert.parent = parent; + } + this.fixAfterInsert(toInsert); + } + + /** + * Searches the RB Tree iteratively for a given value. + * + * @param val The value to search for, null if it does not exist. + */ + public RBNode findNode(T val) { + RBNode current = this.root; + while (!current.equals(null)) { + if (current.value.equals(val)) { + return current; + } else if (current.value.compareTo(val) == -1) { + current = current.left; + } else { + current = current.right; + } + } + return null; + } + + /** + * Helper function that conducts update on the relationship + * between a parent node with a new child node. + * + * @param par The parent node. + * @param curr The current node that is a child of the parent. + * @param updated The new child to replace the current node. + */ + private void swap(RBNode par, RBNode curr, RBNode updated) { + if (par.equals(null)) { + root = updated; + } else if (par.left == curr) { // If the old node is originally the left child. + par.left = updated; + } else { + par.right = updated; + } + if (!updated.equals(null)) { + updated.parent = par; + } + } + + /** + * This conducts right-rotation on a given node. + * + * @param node The node to rotate from. + */ + private void rightRotate(RBNode node) { + RBNode par = node.parent; + RBNode left = node.left; + + // Swap current node's left with the right node of the left child. + node.left = left.right; + if (!node.left.equals(null)) { + node.left.parent = node; + } + + // Swap current node with left's right node. + left.right = node; + node.parent = left; + + // Swap parent of node to left. (Which is the new "higher" node in the tree). + swap(par, node, left); + } + + /** + * This conducts left-rotation on a given node. + * + * @param node The node to rotate from. + */ + private void leftRotate(RBNode node) { + RBNode par = node.parent; + RBNode right = node.right; + + // Swap current node's right with the left node of the right child. + node.right = right.left; + if (!node.right.equals(null)) { + node.right.parent = node; + } + + // Swap current node with right's left node. + right.left = node; + node.parent = right; + + // Swap parent of node to right. + swap(par, node, right); + } + + /** + * Helper function to find the brother of the node. + * + * @param node The node we are interested in. + * @return Brother of the node. + */ + private RBNode findBrother(RBNode node) { + RBNode par = node.parent; + if (par.left == node) { + return par.right; + } else { + return par.left; + } + } + + /** + * Fixes properties of the RB tree after insertion of a node. + * + * @param node The node to fix. + */ + private void fixAfterInsert(RBNode node) { + RBNode par = node.parent; + if (par.equals(null)) { + node.tag = false; // Roots must be black. + return; + } + if (!par.tag) {return;} // Parent is of the correct color. + + RBNode grandpa = par.parent; + + // Case where grandpa == null, parent is the root of the tree. + if (grandpa.equals(null)) { + par.tag = false; // Roots must be black. + return; + } + + RBNode uncle = this.findBrother(par); + + // If uncle is red, we have to recolor the parent, grandpa and uncle. + if (!uncle.equals(null) && uncle.tag) { + par.tag = false; + grandpa.tag = true; + uncle.tag = false; + // Fix upwards. + fixAfterInsert(grandpa); + } else if (par.equals(grandpa.left)) { // If parent is the left node of grandpa... + if (node.equals(par.right)) { // And we are the right child. + leftRotate(par); + rightRotate(grandpa); + + // We become the new "parent" of our parent, recolor accordingly. + node.tag = false; + } else { // And we are the left child. + rightRotate(grandpa); + + // Parent becomes the new parent of grandpa, hence we recolor accordingly. + par.tag = false; + } + grandpa.tag = true; + } else { // If parent is the right node of grandpa... + if (node.equals(par.right)) { // And we are the right child. + leftRotate(grandpa); + par.tag = false; + } else { // And we are the left child. + rightRotate(par); + leftRotate(grandpa); + node.tag = false; + } + grandpa.tag = true; + } + } + + /** + * Deletes a node from the RB tree. + * + * @param val The value to remove from the tree. + */ + public void deleteNode(T val) { + + // Finds node with the value within the tree. + RBNode current = findNode(val); + if (current.equals(null)) {return;} + + RBNode nextNode; // Node to fix next. + boolean deletedColor; + + // Current node does not have 2 children. + if (current.left.equals(null) || current.right.equals(null)) { + nextNode = deleteZeroOrOne(current); + deletedColor = current.tag; + } else { + // Find the successor of this node. + // Our successor would be the smallest node in the subtree + // with the current node's right child as the root. + RBNode successor = current.right; + while (!successor.left.equals(null)) { + successor = successor.left; + } + // We replace the value with the successor. + current.value = successor.value; + + // Since our successor must be a leaf, it has no children. + // We remove the successor node from this "path". + nextNode = deleteZeroOrOne(successor); + deletedColor = successor.tag; + } + // If we deleted a black node, we need to fix the tree. + if (!deletedColor) { + fixAfterDelete(nextNode); // We fix the next node that causes problems with the property. + if (nextNode.value.equals(-1)) { + // In our implementation, -1 represents the node being a temporary node, but this could also + // be replaced by a sentinel RBTree node. + swap(nextNode.parent, nextNode, null); + } + } + } + + /** + * Helper function to delete a node with zero or one child. + * + * @param node The node in focus. + * + * @return The next node in concern. + */ + private RBNode deleteZeroOrOne(RBNode node) { + // If something has no child, we can ignore. + if (node.left.equals(null) && node.right.equals(null)) { + if (node.tag) { // If our node is red, we can just remove it. + swap(node.parent, node, null); + return null; + } else { + // Else, we need a temp. black node to hold the spot first, because the number of + // black nodes down this path has reduced. + RBNode temp = new RBNode<>(null, false); + swap(node.parent, node, temp); + return temp; + } + } + // We replace ourselves with the respective child we have. + if (!node.left.equals(null)) { + swap(node.parent, node, node.left); + return node.left; + } else { + swap(node.parent, node, node.right); + return node.right; + } + } + + /** + * Fixes the tree after a delete operation. + * + * @param node Node in question. + */ + private void fixAfterDelete(RBNode node) { + if (node.equals(root)) { // No need to fix the root. + return; + } + RBNode brother = findBrother(node); + // If our brother is red... + if (brother.tag) { + fixRedSibling(node, brother); + brother = findBrother(node); // Get our new brother (who will now be black.) + } + if (!brother.left.tag && !brother.right.tag) { // Brother's children are all black. + brother.tag = true; // Color our brother node red. + if (node.parent.tag) { // Parent is red, brother's children are both black. + node.parent.tag = false; // Recolor parent black. + } else { + // Else, we fix upwards. + fixAfterDelete(node.parent); + } + } else { // Brother has at least one red child. + fixBlackSibling(node, brother); + } + } + + /** + * Helper function that fixes node with a brother that is red. + * + * @param node The black node. + * @param brother The red node (our sibling node), that we need to fix to black. + */ + private void fixRedSibling(RBNode node, RBNode brother) { + // Recolor nodes (remember that we are black!) + brother.tag = false; + node.parent.tag = true; + + // Rotate node. + if (node.equals(node.parent.left)) { + leftRotate(node.parent); + } else { + rightRotate(node.parent); + } + } + + /** + * Helper function that fixes node with a brother is black and has at least one red child. + * + * @param node The node. + * @param brother The node's sibling. + */ + private void fixBlackSibling(RBNode node, RBNode brother) { + // If we are the left child and our brother's right child is black. + if (node.parent.left.equals(node) && !brother.right.tag) { + // Recolor our brother and his child. + brother.left.tag = false; + brother.tag = true; + + // Fix position and update to our new brother. + rightRotate(brother); + brother = node.parent.right; + } else if (!node.parent.left.equals(node) && !brother.left.tag){ + // If we are the right child and our brother's left child is black. + brother.right.tag = false; + brother.tag = true; + leftRotate(brother); + brother = node.parent.left; + } + + // Brother has at least one red child, and the outer child with respect to our position + // is red. + brother.tag = node.parent.tag; + node.parent.tag = false; + if (node.parent.left.equals(node)) { + // Recolor nodes. + brother.right.tag = false; + // Readjust by bring parent about us. + leftRotate(node.parent); + } else { + brother.left.tag = false; + rightRotate(node.parent); + } + } + + /** + * Prints out in-order traversal of tree rooted at node. + * + * @param node Node which the tree is rooted at. + */ + public void printInOrder(RBNode node) { + if (node == null) { + return; + } + if (node.left != null) { + printInOrder(node.left); + } + System.out.print(node.toString() + " "); + if (node.right != null) { + printInOrder(node.right); + } + } + + /** + * Prints out pre-order traversal of tree rooted at node. + * + * @param node node which the tree is rooted at. + */ + private void printPreOrder(Node node) { + if (node == null) { + return; + } + System.out.print(node.toString() + " "); + if (node.left != null) { + printPreOrder(node.left); + } + if (node.right != null) { + printPreOrder(node.right); + } + } + + /** + * Prints out post-order traversal of tree rooted at node. + * + * @param node node which the tree is rooted at. + */ + private void printPostOrder(Node node) { + if (node.left != null) { + printPostOrder(node.left); + } + if (node.right != null) { + printPostOrder(node.right); + } + System.out.print(node.toString() + " "); + } +} From 715f117b0063caf1f1499c4be3674315f2d0bcc9 Mon Sep 17 00:00:00 2001 From: yeoshuheng Date: Fri, 29 Dec 2023 22:35:13 +0800 Subject: [PATCH 02/15] test: test cases for rb tree --- .../dataStructures/rbTree/RBTreeTest.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/test/java/dataStructures/rbTree/RBTreeTest.java diff --git a/src/test/java/dataStructures/rbTree/RBTreeTest.java b/src/test/java/dataStructures/rbTree/RBTreeTest.java new file mode 100644 index 00000000..fcfc365f --- /dev/null +++ b/src/test/java/dataStructures/rbTree/RBTreeTest.java @@ -0,0 +1,68 @@ +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.findNode(10)); + tree.insertNode(1); + tree.insertNode(2); + tree.insertNode(3); + Assert.assertEquals((Integer) 1, tree.findNode(1).getValue()); + Assert.assertEquals((Integer) 2, tree.findNode(2).getValue()); + Assert.assertEquals((Integer) 3, tree.findNode(3).getValue()); + } + + @Test + public void testDeleteAndSearch() { + RBTree tree = new RBTree<>(); + Assert.assertEquals(null, tree.findNode(10)); + tree.insertNode(1); + tree.insertNode(5); + tree.insertNode(8); + tree.insertNode(2); + tree.insertNode(3); + Assert.assertEquals((Integer) 1, tree.findNode(1).getValue()); + Assert.assertEquals((Integer) 2, tree.findNode(2).getValue()); + Assert.assertEquals((Integer) 3, tree.findNode(3).getValue()); + tree.deleteNode(3); + Assert.assertEquals(null, tree.findNode(3)); + tree.deleteNode(1); + Assert.assertEquals(null, tree.findNode(1)); + } + + @Test + public void testRBRotations() { + RBTree tree = new RBTree<>(); + + // Testing insert rotations + Assert.assertEquals("", tree.getLevelOrder(tree.root)); + tree.insertNode(1); + tree.insertNode(2); + tree.insertNode(3); + Assert.assertEquals("2 1 3 ", tree.getLevelOrder(tree.root)); + + tree.insertNode(4); + tree.insertNode(5); + Assert.assertEquals("2 1 4 3 5 ", tree.getLevelOrder(tree.root)); + + tree.insertNode(9); + tree.insertNode(6); + tree.insertNode(7); + tree.insertNode(8); + Assert.assertEquals("4 2 6 1 3 5 8 7 9 ", tree.getLevelOrder(tree.root)); + + // Testing delete rotations + tree.deleteNode(6); + Assert.assertEquals("4 2 8 1 3 5 9 7 ", tree.getLevelOrder(tree.root)); + tree.deleteNode(5); + Assert.assertEquals("4 2 8 1 3 7 9 ", tree.getLevelOrder(tree.root)); + tree.deleteNode(2); + tree.deleteNode(8); + Assert.assertEquals("4 1 7 3 9 ", tree.getLevelOrder(tree.root)); + tree.deleteNode(4); + Assert.assertEquals("3 1 7 9 ", tree.getLevelOrder(tree.root)); + } +} From b87a025a89c5cbaec96f73935327ba8f02a74f6d Mon Sep 17 00:00:00 2001 From: yeoshuheng Date: Fri, 29 Dec 2023 22:35:24 +0800 Subject: [PATCH 03/15] feat: rbtree --- .../java/dataStructures/rbTree/RBNode.java | 4 +++ .../java/dataStructures/rbTree/RBTree.java | 31 +++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/main/java/dataStructures/rbTree/RBNode.java b/src/main/java/dataStructures/rbTree/RBNode.java index 09581f23..f660a5de 100644 --- a/src/main/java/dataStructures/rbTree/RBNode.java +++ b/src/main/java/dataStructures/rbTree/RBNode.java @@ -16,4 +16,8 @@ public RBNode(T value, boolean tag) { this.value = value; this.tag = tag; } + + public T getValue() { + return this.value; + } } diff --git a/src/main/java/dataStructures/rbTree/RBTree.java b/src/main/java/dataStructures/rbTree/RBTree.java index 4ba5a9ce..636dd947 100644 --- a/src/main/java/dataStructures/rbTree/RBTree.java +++ b/src/main/java/dataStructures/rbTree/RBTree.java @@ -380,7 +380,7 @@ public void printInOrder(RBNode node) { * * @param node node which the tree is rooted at. */ - private void printPreOrder(Node node) { + public void printPreOrder(RBNode node) { if (node == null) { return; } @@ -393,12 +393,39 @@ private void printPreOrder(Node node) { } } + /** + * Prints out level-order traversal of tree rooted at node. + * + * @param node node which the tree is rooted at. + * + * @return String representing the level order. + */ + public String getLevelOrder(RBNode node) { + if (node == null) { + return ""; + } + Queue> q = new LinkedList<>(); + StringBuilder sb = new StringBuilder(); + q.add(node); + while (!q.isEmpty()) { + RBNode curr = q.poll(); + sb.append(curr.toString() + " "); + if (curr.left != null) { + q.add(curr.left); + } + if (curr.right != null) { + q.add(curr.right); + } + } + return sb.toString(); + } + /** * Prints out post-order traversal of tree rooted at node. * * @param node node which the tree is rooted at. */ - private void printPostOrder(Node node) { + private void printPostOrder(RBNode node) { if (node.left != null) { printPostOrder(node.left); } From b0cce183e005b26455c6928ab870318f1828ce2d Mon Sep 17 00:00:00 2001 From: yeoshuheng Date: Tue, 23 Jan 2024 21:17:56 +0800 Subject: [PATCH 04/15] fix: resolved monotonicq import issues --- .../queue/monotonicQueue/MonotonicQueue.java | 2 +- .../java/dataStructures/rbTree/RBTree.java | 1 - .../java/dataStructures/queue/DequeTest.java | 92 +++++++++---------- .../queue/MonotonicQueueTest.java | 2 + .../java/dataStructures/queue/QueueTest.java | 77 ++++++++-------- 5 files changed, 87 insertions(+), 87 deletions(-) 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/RBTree.java b/src/main/java/dataStructures/rbTree/RBTree.java index 636dd947..d49c090d 100644 --- a/src/main/java/dataStructures/rbTree/RBTree.java +++ b/src/main/java/dataStructures/rbTree/RBTree.java @@ -1,5 +1,4 @@ package dataStructures.rbTree; -import dataStructures.avlTree.Node; import dataStructures.rbTree.RBNode; import java.lang.StringBuilder; diff --git a/src/test/java/dataStructures/queue/DequeTest.java b/src/test/java/dataStructures/queue/DequeTest.java index 0ef262c1..d2612712 100644 --- a/src/test/java/dataStructures/queue/DequeTest.java +++ b/src/test/java/dataStructures/queue/DequeTest.java @@ -11,53 +11,53 @@ * This class implements tests for the deque. */ public class DequeTest { - @Test - public void testEmpty() { - Deque d = new Deque<>(); - Assert.assertTrue(d.isEmpty()); - Assert.assertEquals(0, d.getSize()); - Assert.assertNull(d.peekFirst()); - Assert.assertNull(d.peekLast()); - } + @Test + public void testEmpty() { + Deque d = new Deque<>(); + Assert.assertEquals(true, d.isEmpty()); + Assert.assertEquals(0, d.getSize()); + Assert.assertEquals(d.peekFirst(), null); + Assert.assertEquals(d.peekLast(), null); + } - @Test - public void testInsertion() { - Deque d = new Deque<>(); - Assert.assertEquals("[]", d.toString()); - d.addElement(2); - d.addElement(3); - d.addFirst(1); - Assert.assertEquals("[ 1 2 3 ]", d.toString()); - Assert.assertEquals(3, d.getSize()); - } + @Test + public void testInsertion() { + Deque d = new Deque<>(); + Assert.assertEquals("[]", d.toString()); + d.addElement(2); + d.addElement(3); + d.addFirst(1); + Assert.assertEquals("[ 1 2 3 ]", d.toString()); + Assert.assertEquals(3, d.getSize()); + } - @Test - public void testPeek() { - Deque d = new Deque<>(); - Assert.assertNull(d.peekFirst()); - Assert.assertNull(d.peekLast()); - d.addElement(1); - d.addElement(2); - d.addElement(3); - d.peekLast(); - Assert.assertEquals(3, d.getSize()); - Assert.assertEquals(Optional.of(1).get(), d.peekFirst()); - Assert.assertEquals(Optional.of(3).get(), d.peekLast()); - } + @Test + public void testPeek() { + Deque d = new Deque<>(); + Assert.assertEquals(null, d.peekFirst()); + Assert.assertEquals(null, d.peekLast()); + d.addElement(1); + d.addElement(2); + d.addElement(3); + d.peekLast(); + Assert.assertEquals(3, d.getSize()); + Assert.assertEquals(Optional.of(1).get(), d.peekFirst()); + Assert.assertEquals(Optional.of(3).get(), d.peekLast()); + } - @Test - public void testPoll() { - Deque d = new Deque<>(); - Assert.assertNull(d.pollFirst()); - Assert.assertNull(d.pollLast()); - d.addElement(1); - d.addElement(2); - d.addElement(3); - Assert.assertEquals(Optional.of(1).get(), d.pollFirst()); - Assert.assertEquals(Optional.of(3).get(), d.pollLast()); - Assert.assertEquals(1, d.getSize()); - Assert.assertEquals("[ 2 ]", d.toString()); - Assert.assertEquals(Optional.of(2).get(), d.pollLast()); - Assert.assertEquals(0, d.getSize()); - } + @Test + public void testPoll() { + Deque d = new Deque<>(); + Assert.assertEquals(null, d.pollFirst()); + Assert.assertEquals(null, d.pollLast()); + d.addElement(1); + d.addElement(2); + d.addElement(3); + Assert.assertEquals(Optional.of(1).get(), d.pollFirst()); + Assert.assertEquals(Optional.of(3).get(), d.pollLast()); + Assert.assertEquals(1, d.getSize()); + Assert.assertEquals("[ 2 ]", d.toString()); + Assert.assertEquals(Optional.of(2).get(), d.pollLast()); + Assert.assertEquals(0, d.getSize()); + } } 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/queue/QueueTest.java b/src/test/java/dataStructures/queue/QueueTest.java index 132d2410..0982223e 100644 --- a/src/test/java/dataStructures/queue/QueueTest.java +++ b/src/test/java/dataStructures/queue/QueueTest.java @@ -7,46 +7,45 @@ * This class implements the test cases for the queue. */ public class QueueTest { - @Test - public void testEmptyQueue() { - Queue q = new Queue<>(); - Assert.assertEquals(0, q.size()); - Assert.assertTrue(q.isEmpty()); - Assert.assertNull(q.dequeue()); - } + @Test + public void testEmptyQueue() { + Queue q = new Queue<>(); + Assert.assertEquals(0, q.size()); + Assert.assertEquals(true, q.isEmpty()); + Assert.assertEquals(null, q.dequeue()); + } - @Test - public void testEnqueue() { - Queue q = new Queue<>(); - q.enqueue(1); - q.enqueue(2); - q.enqueue(3); - Assert.assertEquals(3, q.size()); - } + @Test + public void testEnqueue() { + Queue q = new Queue<>(); + q.enqueue(1); + q.enqueue(2); + q.enqueue(3); + Assert.assertEquals(3, q.size()); + } - @Test - public void testPeek() { - Queue q = new Queue<>(); - q.enqueue(1); - Assert.assertEquals("1", q.peek().toString()); - q.enqueue(2); - q.enqueue(3); - q.peek(); - Assert.assertEquals("1", q.peek().toString()); - } - - @Test - public void testDequeue() { - Queue q = new Queue<>(); - q.enqueue(1); - q.enqueue(2); - q.enqueue(3); - Assert.assertEquals("1", q.dequeue().toString()); - Assert.assertEquals(2, q.size()); - q.dequeue(); - Assert.assertEquals(1, q.size()); - Assert.assertEquals("3", q.dequeue().toString()); - Assert.assertEquals(0, q.size()); - } + @Test + public void testPeek() { + Queue q = new Queue<>(); + q.enqueue(1); + Assert.assertEquals("1", q.peek().toString()); + q.enqueue(2); + q.enqueue(3); + q.peek(); + Assert.assertEquals("1", q.peek().toString()); + } + @Test + public void testDequeue() { + Queue q = new Queue<>(); + q.enqueue(1); + q.enqueue(2); + q.enqueue(3); + Assert.assertEquals("1", q.dequeue().toString()); + Assert.assertEquals(2, q.size()); + q.dequeue(); + Assert.assertEquals(1, q.size()); + Assert.assertEquals("3", q.dequeue().toString()); + Assert.assertEquals(0, q.size()); + } } From bc311346a76566cca1e77f0fad494ba8e71dd2f8 Mon Sep 17 00:00:00 2001 From: yeoshuheng Date: Tue, 23 Jan 2024 22:20:22 +0800 Subject: [PATCH 05/15] fix: nullptr exceptions considered --- .../java/dataStructures/rbTree/RBNode.java | 24 ++++++++++++ .../java/dataStructures/rbTree/RBTree.java | 38 +++++++++---------- 2 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/main/java/dataStructures/rbTree/RBNode.java b/src/main/java/dataStructures/rbTree/RBNode.java index f660a5de..6ece4d33 100644 --- a/src/main/java/dataStructures/rbTree/RBNode.java +++ b/src/main/java/dataStructures/rbTree/RBNode.java @@ -20,4 +20,28 @@ public RBNode(T value, boolean tag) { public T getValue() { return this.value; } + + public boolean getTag() {return this.tag;} + + public RBNode getLeft() {return this.left;} + + public RBNode getRight() {return this.right;} + + @Override + public String toString() { + return this.value.toString(); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (!(other instanceof RBNode)) { + return false; + } + RBNode otherNode = (RBNode) other; + return this.getValue() == otherNode.getValue(); + } + } diff --git a/src/main/java/dataStructures/rbTree/RBTree.java b/src/main/java/dataStructures/rbTree/RBTree.java index d49c090d..f3223802 100644 --- a/src/main/java/dataStructures/rbTree/RBTree.java +++ b/src/main/java/dataStructures/rbTree/RBTree.java @@ -14,7 +14,7 @@ public class RBTree> { public void insertNode(T val) { RBNode current = root; RBNode parent = null; - while (!current.equals(null)) { // Iteratively search for insertion point. + while (current != null) { // Iteratively search for insertion point. parent = current; if (val.compareTo(current.value) == -1) { current = current.left; @@ -24,7 +24,7 @@ public void insertNode(T val) { } // Inserted node will always be kept as red first. RBNode toInsert = new RBNode<>(val, true); - if (parent == null) { // First node. + if (this.root == null) { // First node. this.root = toInsert; } else if (val.compareTo(parent.value) == -1) { parent.left = toInsert; @@ -43,10 +43,10 @@ public void insertNode(T val) { */ public RBNode findNode(T val) { RBNode current = this.root; - while (!current.equals(null)) { + while (current != null) { if (current.value.equals(val)) { return current; - } else if (current.value.compareTo(val) == -1) { + } else if (val.compareTo(current.value) == -1) { current = current.left; } else { current = current.right; @@ -64,14 +64,14 @@ public RBNode findNode(T val) { * @param updated The new child to replace the current node. */ private void swap(RBNode par, RBNode curr, RBNode updated) { - if (par.equals(null)) { + if (par == null) { root = updated; } else if (par.left == curr) { // If the old node is originally the left child. par.left = updated; } else { par.right = updated; } - if (!updated.equals(null)) { + if (updated != null) { updated.parent = par; } } @@ -87,8 +87,8 @@ private void rightRotate(RBNode node) { // Swap current node's left with the right node of the left child. node.left = left.right; - if (!node.left.equals(null)) { - node.left.parent = node; + if (left.right != null) { + left.right.parent = node; } // Swap current node with left's right node. @@ -110,8 +110,8 @@ private void leftRotate(RBNode node) { // Swap current node's right with the left node of the right child. node.right = right.left; - if (!node.right.equals(null)) { - node.right.parent = node; + if (right.left != null) { + right.left.parent = node; } // Swap current node with right's left node. @@ -144,7 +144,7 @@ private RBNode findBrother(RBNode node) { */ private void fixAfterInsert(RBNode node) { RBNode par = node.parent; - if (par.equals(null)) { + if (par == null) { node.tag = false; // Roots must be black. return; } @@ -153,7 +153,7 @@ private void fixAfterInsert(RBNode node) { RBNode grandpa = par.parent; // Case where grandpa == null, parent is the root of the tree. - if (grandpa.equals(null)) { + if (grandpa == null) { par.tag = false; // Roots must be black. return; } @@ -161,7 +161,7 @@ private void fixAfterInsert(RBNode node) { RBNode uncle = this.findBrother(par); // If uncle is red, we have to recolor the parent, grandpa and uncle. - if (!uncle.equals(null) && uncle.tag) { + if (uncle != null && uncle.tag) { par.tag = false; grandpa.tag = true; uncle.tag = false; @@ -182,7 +182,7 @@ private void fixAfterInsert(RBNode node) { } grandpa.tag = true; } else { // If parent is the right node of grandpa... - if (node.equals(par.right)) { // And we are the right child. + if (node.equals(par.right)) { // And we are the left child. leftRotate(grandpa); par.tag = false; } else { // And we are the left child. @@ -203,13 +203,13 @@ public void deleteNode(T val) { // Finds node with the value within the tree. RBNode current = findNode(val); - if (current.equals(null)) {return;} + if (current == null) {return;} RBNode nextNode; // Node to fix next. boolean deletedColor; // Current node does not have 2 children. - if (current.left.equals(null) || current.right.equals(null)) { + if (current.left == null || current.right == null) { nextNode = deleteZeroOrOne(current); deletedColor = current.tag; } else { @@ -217,7 +217,7 @@ public void deleteNode(T val) { // Our successor would be the smallest node in the subtree // with the current node's right child as the root. RBNode successor = current.right; - while (!successor.left.equals(null)) { + while (successor.left != null) { successor = successor.left; } // We replace the value with the successor. @@ -248,7 +248,7 @@ public void deleteNode(T val) { */ private RBNode deleteZeroOrOne(RBNode node) { // If something has no child, we can ignore. - if (node.left.equals(null) && node.right.equals(null)) { + if (node.left == null && node.right == null) { if (node.tag) { // If our node is red, we can just remove it. swap(node.parent, node, null); return null; @@ -261,7 +261,7 @@ private RBNode deleteZeroOrOne(RBNode node) { } } // We replace ourselves with the respective child we have. - if (!node.left.equals(null)) { + if (node.left != null) { swap(node.parent, node, node.left); return node.left; } else { From e5c4830aa91fc7852aba869fb0af60cd6be7786e Mon Sep 17 00:00:00 2001 From: yeoshuheng Date: Tue, 23 Jan 2024 23:39:13 +0800 Subject: [PATCH 06/15] feat: added tombstone class + fix: changed nullptr exceptions --- .../java/dataStructures/rbTree/RBTree.java | 57 +++++++++++-------- .../dataStructures/rbTree/TombstoneNode.java | 12 ++++ 2 files changed, 46 insertions(+), 23 deletions(-) create mode 100644 src/main/java/dataStructures/rbTree/TombstoneNode.java diff --git a/src/main/java/dataStructures/rbTree/RBTree.java b/src/main/java/dataStructures/rbTree/RBTree.java index f3223802..83154ba5 100644 --- a/src/main/java/dataStructures/rbTree/RBTree.java +++ b/src/main/java/dataStructures/rbTree/RBTree.java @@ -211,7 +211,7 @@ public void deleteNode(T val) { // Current node does not have 2 children. if (current.left == null || current.right == null) { nextNode = deleteZeroOrOne(current); - deletedColor = current.tag; + deletedColor = current.getTag(); } else { // Find the successor of this node. // Our successor would be the smallest node in the subtree @@ -226,14 +226,12 @@ public void deleteNode(T val) { // Since our successor must be a leaf, it has no children. // We remove the successor node from this "path". nextNode = deleteZeroOrOne(successor); - deletedColor = successor.tag; + deletedColor = successor.getTag(); } // If we deleted a black node, we need to fix the tree. if (!deletedColor) { fixAfterDelete(nextNode); // We fix the next node that causes problems with the property. - if (nextNode.value.equals(-1)) { - // In our implementation, -1 represents the node being a temporary node, but this could also - // be replaced by a sentinel RBTree node. + if (nextNode instanceof TombstoneNode) { swap(nextNode.parent, nextNode, null); } } @@ -247,27 +245,23 @@ public void deleteNode(T val) { * @return The next node in concern. */ private RBNode deleteZeroOrOne(RBNode node) { - // If something has no child, we can ignore. - if (node.left == null && node.right == null) { - if (node.tag) { // If our node is red, we can just remove it. + if (node.left != null && node.right == null) { + swap(node.parent, node, node.left); + return node.left; + } else if (node.right != null && node.left == null) { + swap(node.parent, node, node.right); + } else { + if (node.getTag()) { swap(node.parent, node, null); return null; } else { - // Else, we need a temp. black node to hold the spot first, because the number of - // black nodes down this path has reduced. - RBNode temp = new RBNode<>(null, false); + // We create a temporary tombstone node. + RBNode temp = new TombstoneNode<>(); swap(node.parent, node, temp); return temp; } } - // We replace ourselves with the respective child we have. - if (node.left != null) { - swap(node.parent, node, node.left); - return node.left; - } else { - swap(node.parent, node, node.right); - return node.right; - } + return null; } /** @@ -276,7 +270,7 @@ private RBNode deleteZeroOrOne(RBNode node) { * @param node Node in question. */ private void fixAfterDelete(RBNode node) { - if (node.equals(root)) { // No need to fix the root. + if (node == root || node == null) { // No need to fix the root or null nodes. return; } RBNode brother = findBrother(node); @@ -285,7 +279,8 @@ private void fixAfterDelete(RBNode node) { fixRedSibling(node, brother); brother = findBrother(node); // Get our new brother (who will now be black.) } - if (!brother.left.tag && !brother.right.tag) { // Brother's children are all black. + if ((brother.left == null || !brother.left.getTag()) + && (brother.right == null || !brother.right.getTag())) { // Brother's children are all black. brother.tag = true; // Color our brother node red. if (node.parent.tag) { // Parent is red, brother's children are both black. node.parent.tag = false; // Recolor parent black. @@ -325,7 +320,8 @@ private void fixRedSibling(RBNode node, RBNode brother) { */ private void fixBlackSibling(RBNode node, RBNode brother) { // If we are the left child and our brother's right child is black. - if (node.parent.left.equals(node) && !brother.right.tag) { + if (node.parent.left.equals(node) && + (brother.right == null || !brother.right.getTag())) { // Recolor our brother and his child. brother.left.tag = false; brother.tag = true; @@ -333,7 +329,9 @@ private void fixBlackSibling(RBNode node, RBNode brother) { // Fix position and update to our new brother. rightRotate(brother); brother = node.parent.right; - } else if (!node.parent.left.equals(node) && !brother.left.tag){ + + } else if (!node.parent.left.equals(node) && + (brother.left == null || !brother.left.getTag())){ // If we are the right child and our brother's left child is black. brother.right.tag = false; brother.tag = true; @@ -351,6 +349,7 @@ private void fixBlackSibling(RBNode node, RBNode brother) { // Readjust by bring parent about us. leftRotate(node.parent); } else { + if (brother.left == null) {return;} brother.left.tag = false; rightRotate(node.parent); } @@ -433,4 +432,16 @@ private void printPostOrder(RBNode node) { } System.out.print(node.toString() + " "); } + + /** + * Gets the depth of the tree rooted at the node. + * + * @param node node which the tree is rooted at. + */ + public int getDepth(RBNode node) { + if (node == null) {return 0;} + int l_depth = getDepth(node.left); + int r_depth = getDepth(node.right); + return Math.max(l_depth, r_depth) + 1; + } } diff --git a/src/main/java/dataStructures/rbTree/TombstoneNode.java b/src/main/java/dataStructures/rbTree/TombstoneNode.java new file mode 100644 index 00000000..e070b72f --- /dev/null +++ b/src/main/java/dataStructures/rbTree/TombstoneNode.java @@ -0,0 +1,12 @@ +package dataStructures.rbTree; + +public class TombstoneNode> extends RBNode { + public TombstoneNode() { + super(null, false); + } + + @Override + public boolean equals(Object other) { + return false; + } +} From d4b2afca1ec74af50163d7f79cdcae76c1f599fc Mon Sep 17 00:00:00 2001 From: yeoshuheng Date: Wed, 24 Jan 2024 00:04:01 +0800 Subject: [PATCH 07/15] fix: debugged test cases, checked for tree depth during adjustment --- .../java/dataStructures/rbTree/RBTree.java | 6 ++++ src/main/java/dataStructures/rbTree/README.md | 36 +++++++++++++++++++ .../dataStructures/rbTree/RBTreeTest.java | 11 +++--- 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 src/main/java/dataStructures/rbTree/README.md diff --git a/src/main/java/dataStructures/rbTree/RBTree.java b/src/main/java/dataStructures/rbTree/RBTree.java index 83154ba5..20ec51fc 100644 --- a/src/main/java/dataStructures/rbTree/RBTree.java +++ b/src/main/java/dataStructures/rbTree/RBTree.java @@ -3,6 +3,12 @@ import java.lang.StringBuilder; import java.util.*; + +/* + This class implements a RBTree. + The implementation of this class heavily relies upon the following site: + https://www.happycoders.eu/algorithms/red-black-tree-java/ + */ public class RBTree> { RBNode root = null; diff --git a/src/main/java/dataStructures/rbTree/README.md b/src/main/java/dataStructures/rbTree/README.md new file mode 100644 index 00000000..55624cca --- /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 + +This allows for *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/rbTree/RBTreeTest.java b/src/test/java/dataStructures/rbTree/RBTreeTest.java index fcfc365f..d963b612 100644 --- a/src/test/java/dataStructures/rbTree/RBTreeTest.java +++ b/src/test/java/dataStructures/rbTree/RBTreeTest.java @@ -56,13 +56,16 @@ public void testRBRotations() { // Testing delete rotations tree.deleteNode(6); - Assert.assertEquals("4 2 8 1 3 5 9 7 ", tree.getLevelOrder(tree.root)); + Assert.assertEquals("4 2 7 1 3 5 8 9 ", tree.getLevelOrder(tree.root)); + Assert.assertEquals(4, tree.getDepth(tree.root)); tree.deleteNode(5); - Assert.assertEquals("4 2 8 1 3 7 9 ", tree.getLevelOrder(tree.root)); + Assert.assertEquals("4 2 7 1 3 9 8 ", tree.getLevelOrder(tree.root)); tree.deleteNode(2); tree.deleteNode(8); - Assert.assertEquals("4 1 7 3 9 ", tree.getLevelOrder(tree.root)); + Assert.assertEquals(3, tree.getDepth(tree.root)); + Assert.assertEquals("4 3 7 1 9 ", tree.getLevelOrder(tree.root)); tree.deleteNode(4); - Assert.assertEquals("3 1 7 9 ", tree.getLevelOrder(tree.root)); + Assert.assertEquals("7 3 9 1 ", tree.getLevelOrder(tree.root)); + Assert.assertEquals(3, tree.getDepth(tree.root)); } } From 2045eade3172e6010b617c3502001875b13d812f Mon Sep 17 00:00:00 2001 From: yeoshuheng Date: Wed, 24 Jan 2024 00:07:10 +0800 Subject: [PATCH 08/15] docs: updated readme for rb --- src/main/java/dataStructures/rbTree/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/dataStructures/rbTree/README.md b/src/main/java/dataStructures/rbTree/README.md index 55624cca..7aed629e 100644 --- a/src/main/java/dataStructures/rbTree/README.md +++ b/src/main/java/dataStructures/rbTree/README.md @@ -20,7 +20,7 @@ cases to consider during an insert operation and 6 different cases to consider d ## Operation Orders -This allows for *O(logN)* operations. +Much like other balanced BSTs, RB Trees have *O(logN)* operations. ## Notes From e05312553fddc9e371205e6058467c21cd883577 Mon Sep 17 00:00:00 2001 From: yeoshuheng Date: Wed, 7 Feb 2024 14:08:17 +0800 Subject: [PATCH 09/15] feat: Add rb trees --- .../java/dataStructures/rbTree/RBNode.java | 164 +++- .../java/dataStructures/rbTree/RBTree.java | 784 ++++++++---------- .../dataStructures/rbTree/TombstoneNode.java | 12 - .../dataStructures/rbTree/RBTreeTest.java | 90 +- 4 files changed, 541 insertions(+), 509 deletions(-) delete mode 100644 src/main/java/dataStructures/rbTree/TombstoneNode.java diff --git a/src/main/java/dataStructures/rbTree/RBNode.java b/src/main/java/dataStructures/rbTree/RBNode.java index 6ece4d33..5fa219e0 100644 --- a/src/main/java/dataStructures/rbTree/RBNode.java +++ b/src/main/java/dataStructures/rbTree/RBNode.java @@ -1,47 +1,147 @@ package dataStructures.rbTree; -public class RBNode > { - T value; - RBNode left = null; - RBNode right = null; +/** + * This class represents a node for the Red-Black Tree. + * + * @param The type of element being stored in the node. + */ +public class RBNode> { - RBNode parent = null; + 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; - /** - * Marks the color of the RB node, true if red, false if black. - */ - boolean tag; + /** + * The right child node. + */ + private RBNode right; - public RBNode(T value, boolean tag) { - this.value = value; - this.tag = tag; - } + /** + * The parent node. + */ + private RBNode parent; - public T getValue() { - return this.value; - } + /** + * Constructor for our RB-Tree node. + * Defaults to red. + * + * @param element The element to add. + */ + public RBNode(T element) { + this.element = element; + this.left = null; + this.right = null; + this.color = VAL.RED; + this.parent = null; + } - public boolean getTag() {return this.tag;} + /** + * Constructor for a NIL node. + */ + public RBNode() { + this.element = null; + this.parent = null; + this.left = null; + this.right = null; + this.color = VAL.BLACK; + } - public RBNode getLeft() {return this.left;} + /** + * Sets right node. + * + * @param other The new right node. + */ + public void setRight(RBNode other) { + this.right = other; + } - public RBNode getRight() {return this.right;} + /** + * Sets left node. + * + * @param other The new left node. + */ + public void setLeft(RBNode other) { + this.left = other; + } - @Override - public String toString() { - return this.value.toString(); - } + /** + * Sets parent node. + * + * @param other The new parent node. + */ + public void setParent(RBNode other) { + this.parent = other; + } - @Override - public boolean equals(Object other) { - if (other == this) { - return true; + /** + * Getter for element + * + * @return The element in the node. + */ + public T getElement() { + return this.element; } - if (!(other instanceof RBNode)) { - return false; + + /** + * 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.left; + } + + /** + * 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 changeColor(VAL color) { + this.color = color; } - RBNode otherNode = (RBNode) other; - return this.getValue() == otherNode.getValue(); - } } diff --git a/src/main/java/dataStructures/rbTree/RBTree.java b/src/main/java/dataStructures/rbTree/RBTree.java index 20ec51fc..79c7cdc5 100644 --- a/src/main/java/dataStructures/rbTree/RBTree.java +++ b/src/main/java/dataStructures/rbTree/RBTree.java @@ -1,453 +1,397 @@ package dataStructures.rbTree; -import dataStructures.rbTree.RBNode; -import java.lang.StringBuilder; -import java.util.*; +import java.util.LinkedList; +import java.util.Queue; -/* - This class implements a RBTree. - The implementation of this class heavily relies upon the following site: - https://www.happycoders.eu/algorithms/red-black-tree-java/ +/** + * This class represents a Red-Black (RB) tree. + * @param The class of the elements being added to the RB-Tree. */ public class RBTree> { - RBNode root = null; - /** - * Inserts a node into RB Tree. - * - * @param val The value to be inserted. - */ - public void insertNode(T val) { - RBNode current = root; - RBNode parent = null; - while (current != null) { // Iteratively search for insertion point. - parent = current; - if (val.compareTo(current.value) == -1) { - current = current.left; - } else { - current = current.right; - } + /** + * The root node. + */ + private RBNode root; + + /** + * A representation of the NIL node used in RB-Trees. + */ + private RBNode nilNode; + + /** + * Constructor for the Red-Black Tree. + */ + public RBTree() { + nilNode = new RBNode<>(); + root = nilNode; } - // Inserted node will always be kept as red first. - RBNode toInsert = new RBNode<>(val, true); - if (this.root == null) { // First node. - this.root = toInsert; - } else if (val.compareTo(parent.value) == -1) { - parent.left = toInsert; - toInsert.parent = parent; - } else { - parent.right = toInsert; - toInsert.parent = parent; - } - this.fixAfterInsert(toInsert); - } - /** - * Searches the RB Tree iteratively for a given value. - * - * @param val The value to search for, null if it does not exist. - */ - public RBNode findNode(T val) { - RBNode current = this.root; - while (current != null) { - if (current.value.equals(val)) { - return current; - } else if (val.compareTo(current.value) == -1) { - current = current.left; - } else { - current = current.right; - } + /** + * Gets root of the tree. + * + * @return Root. + */ + public RBNode getRoot() { + return this.root; } - return null; - } - /** - * Helper function that conducts update on the relationship - * between a parent node with a new child node. - * - * @param par The parent node. - * @param curr The current node that is a child of the parent. - * @param updated The new child to replace the current node. - */ - private void swap(RBNode par, RBNode curr, RBNode updated) { - if (par == null) { - root = updated; - } else if (par.left == curr) { // If the old node is originally the left child. - par.left = updated; - } else { - par.right = updated; - } - if (updated != null) { - updated.parent = par; + /** + * Helper function to conduct left-rotate. + * + * @param node The node to rotate from. + */ + private void leftRotate(RBNode node) { + RBNode right = node.getRight(); + node.setRight(right.getLeft()); + if (right.getLeft() != this.nilNode) { + right.getLeft().setParent(node); + } + right.setParent(node.getParent()); + if (node.getParent() == null) { + root = right; + } else if (node == node.getParent().getLeft()) { // We swap ourselves with our right node. + node.getParent().setLeft(right); + } else { + node.getParent().setRight(right); + } + // We become the child of the right node. + right.setLeft(node); + node.setParent(right); } - } - - /** - * This conducts right-rotation on a given node. - * - * @param node The node to rotate from. - */ - private void rightRotate(RBNode node) { - RBNode par = node.parent; - RBNode left = node.left; - // Swap current node's left with the right node of the left child. - node.left = left.right; - if (left.right != null) { - left.right.parent = node; + /** + * Helper function to conduct right-rotate. + * + * @param node The node to rotate from. + */ + private void rightRotate(RBNode node) { + RBNode left = node.getLeft(); + node.setLeft(left.getRight()); + if (left.getRight() != this.nilNode) { + left.getRight().setParent(node); + } + left.setParent(node.getParent()); + if (node.getParent() == null) { + root = left; + } else if (node.getParent().getLeft() == node) { + node.getParent().setLeft(left); + } else { + node.getParent().setRight(left); + } + left.setRight(node); + node.setParent(left); } - // Swap current node with left's right node. - left.right = node; - node.parent = left; - - // Swap parent of node to left. (Which is the new "higher" node in the tree). - swap(par, node, left); - } - - /** - * This conducts left-rotation on a given node. - * - * @param node The node to rotate from. - */ - private void leftRotate(RBNode node) { - RBNode par = node.parent; - RBNode right = node.right; - - // Swap current node's right with the left node of the right child. - node.right = right.left; - if (right.left != null) { - right.left.parent = node; - } - - // Swap current node with right's left node. - right.left = node; - node.parent = right; - - // Swap parent of node to right. - swap(par, node, right); - } - - /** - * Helper function to find the brother of the node. - * - * @param node The node we are interested in. - * @return Brother of the node. - */ - private RBNode findBrother(RBNode node) { - RBNode par = node.parent; - if (par.left == node) { - return par.right; - } else { - return par.left; - } - } - - /** - * Fixes properties of the RB tree after insertion of a node. - * - * @param node The node to fix. - */ - private void fixAfterInsert(RBNode node) { - RBNode par = node.parent; - if (par == null) { - node.tag = false; // Roots must be black. - return; - } - if (!par.tag) {return;} // Parent is of the correct color. - - RBNode grandpa = par.parent; - - // Case where grandpa == null, parent is the root of the tree. - if (grandpa == null) { - par.tag = false; // Roots must be black. - return; - } - - RBNode uncle = this.findBrother(par); - - // If uncle is red, we have to recolor the parent, grandpa and uncle. - if (uncle != null && uncle.tag) { - par.tag = false; - grandpa.tag = true; - uncle.tag = false; - // Fix upwards. - fixAfterInsert(grandpa); - } else if (par.equals(grandpa.left)) { // If parent is the left node of grandpa... - if (node.equals(par.right)) { // And we are the right child. - leftRotate(par); - rightRotate(grandpa); - - // We become the new "parent" of our parent, recolor accordingly. - node.tag = false; - } else { // And we are the left child. - rightRotate(grandpa); - - // Parent becomes the new parent of grandpa, hence we recolor accordingly. - par.tag = false; - } - grandpa.tag = true; - } else { // If parent is the right node of grandpa... - if (node.equals(par.right)) { // And we are the left child. - leftRotate(grandpa); - par.tag = false; - } else { // And we are the left child. - rightRotate(par); - leftRotate(grandpa); - node.tag = false; - } - grandpa.tag = true; - } - } - - /** - * Deletes a node from the RB tree. - * - * @param val The value to remove from the tree. - */ - public void deleteNode(T val) { - - // Finds node with the value within the tree. - RBNode current = findNode(val); - if (current == null) {return;} - - RBNode nextNode; // Node to fix next. - boolean deletedColor; - - // Current node does not have 2 children. - if (current.left == null || current.right == null) { - nextNode = deleteZeroOrOne(current); - deletedColor = current.getTag(); - } else { - // Find the successor of this node. - // Our successor would be the smallest node in the subtree - // with the current node's right child as the root. - RBNode successor = current.right; - while (successor.left != null) { - successor = successor.left; - } - // We replace the value with the successor. - current.value = successor.value; - - // Since our successor must be a leaf, it has no children. - // We remove the successor node from this "path". - nextNode = deleteZeroOrOne(successor); - deletedColor = successor.getTag(); - } - // If we deleted a black node, we need to fix the tree. - if (!deletedColor) { - fixAfterDelete(nextNode); // We fix the next node that causes problems with the property. - if (nextNode instanceof TombstoneNode) { - swap(nextNode.parent, nextNode, null); - } - } - } - - /** - * Helper function to delete a node with zero or one child. - * - * @param node The node in focus. - * - * @return The next node in concern. - */ - private RBNode deleteZeroOrOne(RBNode node) { - if (node.left != null && node.right == null) { - swap(node.parent, node, node.left); - return node.left; - } else if (node.right != null && node.left == null) { - swap(node.parent, node, node.right); - } else { - if (node.getTag()) { - swap(node.parent, node, null); + /** + * Gets an element from the tree. + * In our implementation, this represents more of a "check-if-exists" operation. + * But in key-value RB-Trees, such as Java's TreeMap, the get will return the value + * tagged to the key, whilst the search will be conducted based on the key value. + * + * @param element The element in the tree. + * @return The element we are looking for. + */ + public T get(T element) { + RBNode current = root; + while (current != null) { + int compareResults = current.getElement().compareTo(element); + switch (compareResults) { + case 1: + return current.getElement(); + case -1: + current = current.getLeft(); + break; + default: + current = current.getRight(); + break; + } + } return null; - } else { - // We create a temporary tombstone node. - RBNode temp = new TombstoneNode<>(); - swap(node.parent, node, temp); - return temp; - } } - return null; - } - /** - * Fixes the tree after a delete operation. - * - * @param node Node in question. - */ - private void fixAfterDelete(RBNode node) { - if (node == root || node == null) { // No need to fix the root or null nodes. - return; - } - RBNode brother = findBrother(node); - // If our brother is red... - if (brother.tag) { - fixRedSibling(node, brother); - brother = findBrother(node); // Get our new brother (who will now be black.) - } - if ((brother.left == null || !brother.left.getTag()) - && (brother.right == null || !brother.right.getTag())) { // Brother's children are all black. - brother.tag = true; // Color our brother node red. - if (node.parent.tag) { // Parent is red, brother's children are both black. - node.parent.tag = false; // Recolor parent black. - } else { - // Else, we fix upwards. - fixAfterDelete(node.parent); - } - } else { // Brother has at least one red child. - fixBlackSibling(node, brother); + /** + * The element to insert into the tree. + * + * @param element The element to insert. + */ + public void insert(T element) { + RBNode toInsert = new RBNode<>(element); + RBNode previous = null; + RBNode current = root; + + while (current != null) { + previous = current; + int compareResults = toInsert.getElement().compareTo(current.getElement()); + if (compareResults < 0) { // We assume no duplicates in this implementation. + current = current.getLeft(); + } else { + current = current.getRight(); + } + } + + toInsert.setParent(previous); + if (previous == null) { + root = toInsert; + } else { + int compareResults = toInsert.getElement().compareTo(previous.getElement()); + if (compareResults < 0) { + previous.setLeft(toInsert); + } else { + previous.setRight(toInsert); + } + } + + if (toInsert.getParent() == null) { + toInsert.changeColor(RBNode.VAL.BLACK); // Root is black! + return; + } + + if (toInsert.getParent().getParent() == null) { // Grandparent is root! + return; + } + + this.fixInsert(toInsert); // We need to fix the tree :( } - } - /** - * Helper function that fixes node with a brother that is red. - * - * @param node The black node. - * @param brother The red node (our sibling node), that we need to fix to black. - */ - private void fixRedSibling(RBNode node, RBNode brother) { - // Recolor nodes (remember that we are black!) - brother.tag = false; - node.parent.tag = true; - - // Rotate node. - if (node.equals(node.parent.left)) { - leftRotate(node.parent); - } else { - rightRotate(node.parent); + /** + * Helper function to re-balance tree post insert operations. + * + * @param node The node to fix from. + */ + private void fixInsert(RBNode node) { + RBNode uncle; + while (node.getParent().getColor() == RBNode.VAL.RED) { + // If the parent of the current node is the right node of the grandparent. + if (node.getParent() == node.getParent().getParent().getRight()) { + uncle = node.getParent().getParent().getLeft(); + if (uncle.getColor() == RBNode.VAL.RED) { // If uncle is currently red. + // We fix our uncle, our parent and the grandparent. + uncle.changeColor(RBNode.VAL.BLACK); + node.getParent().changeColor(RBNode.VAL.BLACK); + node.getParent().getParent().changeColor(RBNode.VAL.RED); + node = node.getParent().getParent(); + } else { // Our uncle is currently black. + if (node.getParent().getLeft() == node) { // If we are the left child of our parent. + node = node.getParent(); + this.rightRotate(node); + } + // Fix our parents and grandparents, uncle is fine :) + node.getParent().changeColor(RBNode.VAL.BLACK); + node.getParent().getParent().changeColor(RBNode.VAL.RED); + this.leftRotate(node.getParent().getParent()); + } + } else { // Mirror copy of the above! + uncle = node.getParent().getParent().getRight(); + if (uncle.getColor() == RBNode.VAL.RED) { + uncle.changeColor(RBNode.VAL.BLACK); + node.getParent().changeColor(RBNode.VAL.BLACK); + node.getParent().getParent().changeColor(RBNode.VAL.RED); + node = node.getParent().getParent(); + } else { + if (node.getParent().getRight() == node) { + node = node.getParent(); + this.leftRotate(node); + } + node.getParent().changeColor(RBNode.VAL.BLACK); + node.getParent().getParent().changeColor(RBNode.VAL.RED); + this.rightRotate(node.getParent().getParent()); + } + } + if (node == this.root) { // We have fixed up to the root. + break; + } + } + root.changeColor(RBNode.VAL.BLACK); // Ensure root remains black. } - } - - /** - * Helper function that fixes node with a brother is black and has at least one red child. - * - * @param node The node. - * @param brother The node's sibling. - */ - private void fixBlackSibling(RBNode node, RBNode brother) { - // If we are the left child and our brother's right child is black. - if (node.parent.left.equals(node) && - (brother.right == null || !brother.right.getTag())) { - // Recolor our brother and his child. - brother.left.tag = false; - brother.tag = true; - - // Fix position and update to our new brother. - rightRotate(brother); - brother = node.parent.right; - } else if (!node.parent.left.equals(node) && - (brother.left == null || !brother.left.getTag())){ - // If we are the right child and our brother's left child is black. - brother.right.tag = false; - brother.tag = true; - leftRotate(brother); - brother = node.parent.left; + /** + * Finds the minimum element in the tree. + * + * @param node The node the tree is rooted from. + * @return The node containing the minimum element in the tree. + */ + public RBNode findMin(RBNode node) { + while (node.getLeft() != this.nilNode) { + node = node.getRight(); + } + return node; } - // Brother has at least one red child, and the outer child with respect to our position - // is red. - brother.tag = node.parent.tag; - node.parent.tag = false; - if (node.parent.left.equals(node)) { - // Recolor nodes. - brother.right.tag = false; - // Readjust by bring parent about us. - leftRotate(node.parent); - } else { - if (brother.left == null) {return;} - brother.left.tag = false; - rightRotate(node.parent); + /** + * Helper function to transpose node b into node a's position. + * + * @param a The node to be transposed out. + * @param b The node to be transposed in. + */ + private void swap(RBNode a, RBNode b) { + if (a.getParent() == null) { + root = b; + } else if (a == a.getParent().getLeft()) { + a.getParent().setLeft(b); + } else { + a.getParent().setRight(b); + } + b.setParent(a.getParent()); } - } - /** - * Prints out in-order traversal of tree rooted at node. - * - * @param node Node which the tree is rooted at. - */ - public void printInOrder(RBNode node) { - if (node == null) { - return; - } - if (node.left != null) { - printInOrder(node.left); - } - System.out.print(node.toString() + " "); - if (node.right != null) { - printInOrder(node.right); + /** + * To delete a node containing a certain element from the tree. + * Note that this implementation does not account for duplicates of the same element. + * + * @param element The element we need to delete from the tree. + */ + public void delete(T element) { + RBNode node = root; + RBNode target = this.nilNode; + while (node != null) { + int compareResult = node.getElement().compareTo(element); + switch (compareResult) { + case 0: + target = node; + break; + case 1: + node = node.getLeft(); + break; + default: + node = node.getRight(); + break; + } + } + if (target == this.nilNode) { + return; + } + RBNode temp = target; + RBNode.VAL initialColor = temp.getColor(); + RBNode fin; + if (target.getLeft() == this.nilNode) { + fin = temp.getRight(); + this.swap(target, target.getLeft()); + } else if (target.getRight() == this.nilNode) { + fin = temp.getLeft(); + this.swap(target, target.getRight()); + } else { + temp = this.findMin(target.getRight()); + initialColor = temp.getColor(); + fin = temp.getRight(); + if (temp.getParent() == target) { + fin.setParent(temp); + } else { + this.swap(temp, temp.getRight()); + temp.setRight(target.getRight()); + temp.getRight().setParent(temp); + } + this.swap(target, temp); + temp.setLeft(target.getLeft()); + temp.getLeft().setParent(temp); + temp.changeColor(target.getColor()); + } + if (initialColor.equals(RBNode.VAL.BLACK)) { + this.fixDelete(fin); // We need to fix the final node if we removed a black. + } } - } - /** - * Prints out pre-order traversal of tree rooted at node. - * - * @param node node which the tree is rooted at. - */ - public void printPreOrder(RBNode node) { - if (node == null) { - return; + /** + * Function to re-balance the tree post delete operations. + * + * @param node The node to fix upwards from. + */ + private void fixDelete(RBNode node) { + RBNode temp; + while (node != null && node.getColor().equals(RBNode.VAL.BLACK)) { + if (node == node.getParent().getLeft()) { // Current node is the left child. + temp = node.getParent().getRight(); + if (temp.getColor().equals(RBNode.VAL.RED)) { // Our sibling node is red. + temp.changeColor(RBNode.VAL.BLACK); + node.getParent().changeColor(RBNode.VAL.RED); + this.leftRotate(node.getParent()); + temp = node.getParent().getRight(); + } + if (temp.getColor().equals(RBNode.VAL.BLACK) + && temp.getColor().equals(RBNode.VAL.BLACK)) { // Left & Right child are black. + temp.changeColor(RBNode.VAL.RED); + node = node.getParent(); + } else { + if (temp.getRight().getColor().equals(RBNode.VAL.BLACK)) { + temp.getLeft().changeColor(RBNode.VAL.BLACK); + temp.changeColor(RBNode.VAL.RED); + this.rightRotate(temp); + temp = node.getParent().getRight(); + } + temp.changeColor(node.getParent().getColor()); + node.getParent().changeColor(RBNode.VAL.BLACK); + temp.getRight().changeColor(RBNode.VAL.BLACK); + this.leftRotate(node.getParent()); + node = root; + } + } else { // Current node is the right child, mirror of the above :) + temp = node.getParent().getLeft(); + if (temp.getColor().equals(RBNode.VAL.RED)) { + temp.changeColor(RBNode.VAL.BLACK); + node.getParent().changeColor(RBNode.VAL.RED); + this.rightRotate(node.getParent()); + temp = node.getParent().getLeft(); + } + if (temp.getRight().getColor().equals(RBNode.VAL.BLACK) + && temp.getLeft().getColor().equals(RBNode.VAL.BLACK)) { + temp.changeColor(RBNode.VAL.RED); + node = node.getParent(); + } else { + if (temp.getLeft().getColor().equals(RBNode.VAL.BLACK)) { + temp.getRight().changeColor(RBNode.VAL.BLACK); + temp.changeColor(RBNode.VAL.RED); + this.leftRotate(temp); + temp = node.getParent().getLeft(); + } + temp.changeColor(node.getParent().getColor()); + node.getParent().changeColor(RBNode.VAL.BLACK); + temp.getLeft().changeColor(RBNode.VAL.BLACK); + this.rightRotate(node.getParent()); + node = root; + } + } + } + node.changeColor(RBNode.VAL.BLACK); // We ensure our root remains black! } - System.out.print(node.toString() + " "); - if (node.left != null) { - printPreOrder(node.left); - } - if (node.right != null) { - printPreOrder(node.right); - } - } - /** - * Prints out level-order traversal of tree rooted at node. - * - * @param node node which the tree is rooted at. - * - * @return String representing the level order. - */ - public String getLevelOrder(RBNode node) { - if (node == null) { - return ""; - } - Queue> q = new LinkedList<>(); - StringBuilder sb = new StringBuilder(); - q.add(node); - while (!q.isEmpty()) { - RBNode curr = q.poll(); - sb.append(curr.toString() + " "); - if (curr.left != null) { - q.add(curr.left); - } - if (curr.right != null) { - q.add(curr.right); - } + /** + * Gets level order of the tree. + * + * @param node The node the tree is rooted at. + * @return String representation of the level order of the tree. + */ + public String getLevelOrder(RBNode node) { + if (node == this.nilNode || node == null) { + return ""; + } + StringBuilder sb = new StringBuilder(); + Queue> q = new LinkedList<>(); + q.add(node); + while (!q.isEmpty()) { + RBNode current = q.poll(); + sb.append(current.getElement()); + if (current.getLeft() != null && current.getLeft() != this.nilNode) { + q.add(current.getLeft()); + } + if (current.getRight() != null && current.getRight() != this.nilNode) { + q.add(current.getRight()); + } + } + return sb.toString(); } - return sb.toString(); - } - /** - * Prints out post-order traversal of tree rooted at node. - * - * @param node node which the tree is rooted at. - */ - private void printPostOrder(RBNode node) { - if (node.left != null) { - printPostOrder(node.left); + /** + * Gets depth of node. + * + * @param node The node the tree is rooted at. + * @return The depth of the node within the tree. + */ + public int getDepth(RBNode node) { + if (node == null || node == this.nilNode) { + return 0; + } + int leftHeight = getDepth(node.getLeft()); + int rightHeight = getDepth(node.getRight()); + return 1 + Math.max(leftHeight, rightHeight); } - if (node.right != null) { - printPostOrder(node.right); - } - System.out.print(node.toString() + " "); - } - - /** - * Gets the depth of the tree rooted at the node. - * - * @param node node which the tree is rooted at. - */ - public int getDepth(RBNode node) { - if (node == null) {return 0;} - int l_depth = getDepth(node.left); - int r_depth = getDepth(node.right); - return Math.max(l_depth, r_depth) + 1; - } } diff --git a/src/main/java/dataStructures/rbTree/TombstoneNode.java b/src/main/java/dataStructures/rbTree/TombstoneNode.java deleted file mode 100644 index e070b72f..00000000 --- a/src/main/java/dataStructures/rbTree/TombstoneNode.java +++ /dev/null @@ -1,12 +0,0 @@ -package dataStructures.rbTree; - -public class TombstoneNode> extends RBNode { - public TombstoneNode() { - super(null, false); - } - - @Override - public boolean equals(Object other) { - return false; - } -} diff --git a/src/test/java/dataStructures/rbTree/RBTreeTest.java b/src/test/java/dataStructures/rbTree/RBTreeTest.java index d963b612..50957a40 100644 --- a/src/test/java/dataStructures/rbTree/RBTreeTest.java +++ b/src/test/java/dataStructures/rbTree/RBTreeTest.java @@ -6,31 +6,31 @@ public class RBTreeTest { @Test public void testInsertAndSearch() { RBTree tree = new RBTree<>(); - Assert.assertEquals(null, tree.findNode(10)); - tree.insertNode(1); - tree.insertNode(2); - tree.insertNode(3); - Assert.assertEquals((Integer) 1, tree.findNode(1).getValue()); - Assert.assertEquals((Integer) 2, tree.findNode(2).getValue()); - Assert.assertEquals((Integer) 3, tree.findNode(3).getValue()); + 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)); } @Test public void testDeleteAndSearch() { RBTree tree = new RBTree<>(); - Assert.assertEquals(null, tree.findNode(10)); - tree.insertNode(1); - tree.insertNode(5); - tree.insertNode(8); - tree.insertNode(2); - tree.insertNode(3); - Assert.assertEquals((Integer) 1, tree.findNode(1).getValue()); - Assert.assertEquals((Integer) 2, tree.findNode(2).getValue()); - Assert.assertEquals((Integer) 3, tree.findNode(3).getValue()); - tree.deleteNode(3); - Assert.assertEquals(null, tree.findNode(3)); - tree.deleteNode(1); - Assert.assertEquals(null, tree.findNode(1)); + Assert.assertEquals(null, tree.get(10)); + tree.insert(1); + tree.insert(5); + tree.insert(8); + 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)); + tree.delete(3); + Assert.assertEquals(null, tree.get(3)); + tree.delete(1); + Assert.assertEquals(null, tree.get(1)); } @Test @@ -38,34 +38,34 @@ public void testRBRotations() { RBTree tree = new RBTree<>(); // Testing insert rotations - Assert.assertEquals("", tree.getLevelOrder(tree.root)); - tree.insertNode(1); - tree.insertNode(2); - tree.insertNode(3); - Assert.assertEquals("2 1 3 ", tree.getLevelOrder(tree.root)); + Assert.assertEquals("", tree.getLevelOrder(tree.getRoot())); + tree.insert(1); + tree.insert(2); + tree.insert(3); + Assert.assertEquals("2 1 3 ", tree.getLevelOrder(tree.getRoot())); - tree.insertNode(4); - tree.insertNode(5); - Assert.assertEquals("2 1 4 3 5 ", tree.getLevelOrder(tree.root)); + tree.insert(4); + tree.insert(5); + Assert.assertEquals("2 1 4 3 5 ", tree.getLevelOrder(tree.getRoot())); - tree.insertNode(9); - tree.insertNode(6); - tree.insertNode(7); - tree.insertNode(8); - Assert.assertEquals("4 2 6 1 3 5 8 7 9 ", tree.getLevelOrder(tree.root)); + tree.insert(9); + tree.insert(6); + tree.insert(7); + tree.insert(8); + Assert.assertEquals("4 2 6 1 3 5 8 7 9 ", tree.getLevelOrder(tree.getRoot())); // Testing delete rotations - tree.deleteNode(6); - Assert.assertEquals("4 2 7 1 3 5 8 9 ", tree.getLevelOrder(tree.root)); - Assert.assertEquals(4, tree.getDepth(tree.root)); - tree.deleteNode(5); - Assert.assertEquals("4 2 7 1 3 9 8 ", tree.getLevelOrder(tree.root)); - tree.deleteNode(2); - tree.deleteNode(8); - Assert.assertEquals(3, tree.getDepth(tree.root)); - Assert.assertEquals("4 3 7 1 9 ", tree.getLevelOrder(tree.root)); - tree.deleteNode(4); - Assert.assertEquals("7 3 9 1 ", tree.getLevelOrder(tree.root)); - Assert.assertEquals(3, tree.getDepth(tree.root)); + tree.delete(6); + Assert.assertEquals("4 2 7 1 3 5 8 9 ", tree.getLevelOrder(tree.getRoot())); + Assert.assertEquals(4, tree.getDepth(tree.getRoot())); + tree.delete(5); + Assert.assertEquals("4 2 7 1 3 9 8 ", tree.getLevelOrder(tree.getRoot())); + tree.delete(2); + tree.delete(8); + Assert.assertEquals(3, tree.getDepth(tree.getRoot())); + Assert.assertEquals("4 3 7 1 9 ", tree.getLevelOrder(tree.getRoot())); + tree.delete(4); + Assert.assertEquals("7 3 9 1 ", tree.getLevelOrder(tree.getRoot())); + Assert.assertEquals(3, tree.getDepth(tree.getRoot())); } } From 31c977ea21cf4070ee7a52a1ece01eb22885476c Mon Sep 17 00:00:00 2001 From: yeoshuheng Date: Thu, 8 Feb 2024 22:00:11 +0800 Subject: [PATCH 10/15] feat: Add Insert RB --- .../java/dataStructures/rbTree/RBNode.java | 20 +- .../java/dataStructures/rbTree/RBTree.java | 419 ++++-------------- 2 files changed, 101 insertions(+), 338 deletions(-) diff --git a/src/main/java/dataStructures/rbTree/RBNode.java b/src/main/java/dataStructures/rbTree/RBNode.java index 5fa219e0..7081e6e4 100644 --- a/src/main/java/dataStructures/rbTree/RBNode.java +++ b/src/main/java/dataStructures/rbTree/RBNode.java @@ -38,6 +38,22 @@ enum VAL { */ 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 our RB-Tree node. * Defaults to red. @@ -114,7 +130,7 @@ public RBNode getParent() { * @return The right child node. */ public RBNode getRight() { - return this.left; + return this.right; } /** @@ -140,7 +156,7 @@ public VAL getColor() { * * @param color The color to change to. */ - public void changeColor(VAL color) { + 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 index 79c7cdc5..a9269af0 100644 --- a/src/main/java/dataStructures/rbTree/RBTree.java +++ b/src/main/java/dataStructures/rbTree/RBTree.java @@ -10,388 +10,135 @@ public class RBTree> { /** - * The root node. + * Root of the tree. */ - private RBNode root; + RBNode root; /** - * A representation of the NIL node used in RB-Trees. + * NIL Node */ - private RBNode nilNode; + RBNode nil = new RBNode<>(); /** - * Constructor for the Red-Black Tree. + * Constructor for RB Tree. */ public RBTree() { - nilNode = new RBNode<>(); - root = nilNode; + this.root = nil; } /** - * Gets root of the tree. - * - * @return Root. - */ - public RBNode getRoot() { - return this.root; - } - - /** - * Helper function to conduct left-rotate. - * - * @param node The node to rotate from. - */ - private void leftRotate(RBNode node) { - RBNode right = node.getRight(); - node.setRight(right.getLeft()); - if (right.getLeft() != this.nilNode) { - right.getLeft().setParent(node); - } - right.setParent(node.getParent()); - if (node.getParent() == null) { - root = right; - } else if (node == node.getParent().getLeft()) { // We swap ourselves with our right node. - node.getParent().setLeft(right); - } else { - node.getParent().setRight(right); - } - // We become the child of the right node. - right.setLeft(node); - node.setParent(right); - } - - /** - * Helper function to conduct right-rotate. - * - * @param node The node to rotate from. - */ - private void rightRotate(RBNode node) { - RBNode left = node.getLeft(); - node.setLeft(left.getRight()); - if (left.getRight() != this.nilNode) { - left.getRight().setParent(node); - } - left.setParent(node.getParent()); - if (node.getParent() == null) { - root = left; - } else if (node.getParent().getLeft() == node) { - node.getParent().setLeft(left); - } else { - node.getParent().setRight(left); - } - left.setRight(node); - node.setParent(left); - } - - /** - * Gets an element from the tree. - * In our implementation, this represents more of a "check-if-exists" operation. - * But in key-value RB-Trees, such as Java's TreeMap, the get will return the value - * tagged to the key, whilst the search will be conducted based on the key value. - * - * @param element The element in the tree. - * @return The element we are looking for. - */ - public T get(T element) { - RBNode current = root; - while (current != null) { - int compareResults = current.getElement().compareTo(element); - switch (compareResults) { - case 1: - return current.getElement(); - case -1: - current = current.getLeft(); - break; - default: - current = current.getRight(); - break; - } - } - return null; - } - - /** - * The element to insert into the tree. - * + * Inserts element into tree. * @param element The element to insert. */ public void insert(T element) { - RBNode toInsert = new RBNode<>(element); - RBNode previous = null; - RBNode current = root; - - while (current != null) { - previous = current; - int compareResults = toInsert.getElement().compareTo(current.getElement()); - if (compareResults < 0) { // We assume no duplicates in this implementation. - current = current.getLeft(); + RBNode toAdd = new RBNode<>(element); + RBNode prev = nil; + RBNode curr = root; + while (curr != nil) { + prev = curr; + if (element.compareTo(curr.getElement()) < 0) { + curr = curr.getLeft(); } else { - current = current.getRight(); + curr = curr.getRight(); } } - - toInsert.setParent(previous); - if (previous == null) { - root = toInsert; + toAdd.setParent(prev); + if (prev == nil) { + this.root = toAdd; + } else if (element.compareTo(curr.getElement()) < 0) { + prev.setLeft(toAdd); } else { - int compareResults = toInsert.getElement().compareTo(previous.getElement()); - if (compareResults < 0) { - previous.setLeft(toInsert); - } else { - previous.setRight(toInsert); - } - } - - if (toInsert.getParent() == null) { - toInsert.changeColor(RBNode.VAL.BLACK); // Root is black! - return; + prev.setRight(toAdd); } - - if (toInsert.getParent().getParent() == null) { // Grandparent is root! - return; - } - - this.fixInsert(toInsert); // We need to fix the tree :( + this.fixInsert(toAdd); } /** - * Helper function to re-balance tree post insert operations. - * - * @param node The node to fix from. + * Fixes tree upwards upon insert operation. + * @param node Node to fix upwards from. */ - private void fixInsert(RBNode node) { - RBNode uncle; - while (node.getParent().getColor() == RBNode.VAL.RED) { - // If the parent of the current node is the right node of the grandparent. - if (node.getParent() == node.getParent().getParent().getRight()) { - uncle = node.getParent().getParent().getLeft(); - if (uncle.getColor() == RBNode.VAL.RED) { // If uncle is currently red. - // We fix our uncle, our parent and the grandparent. - uncle.changeColor(RBNode.VAL.BLACK); - node.getParent().changeColor(RBNode.VAL.BLACK); - node.getParent().getParent().changeColor(RBNode.VAL.RED); + 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 { // Our uncle is currently black. - if (node.getParent().getLeft() == node) { // If we are the left child of our parent. + } else { + if (node == node.getParent().getRight()) { // Node is right child. node = node.getParent(); - this.rightRotate(node); + this.leftRotate(node); } - // Fix our parents and grandparents, uncle is fine :) - node.getParent().changeColor(RBNode.VAL.BLACK); - node.getParent().getParent().changeColor(RBNode.VAL.RED); - this.leftRotate(node.getParent().getParent()); + node.getParent().setColor(RBNode.VAL.BLACK); + node.getParent().getParent().setColor(RBNode.VAL.RED); + this.rightRotate(node.getParent().getParent()); } - } else { // Mirror copy of the above! - uncle = node.getParent().getParent().getRight(); - if (uncle.getColor() == RBNode.VAL.RED) { - uncle.changeColor(RBNode.VAL.BLACK); - node.getParent().changeColor(RBNode.VAL.BLACK); - node.getParent().getParent().changeColor(RBNode.VAL.RED); + } 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.getParent().getRight() == node) { + if (node == node.getParent().getLeft()) { node = node.getParent(); - this.leftRotate(node); + this.rightRotate(node); } - node.getParent().changeColor(RBNode.VAL.BLACK); - node.getParent().getParent().changeColor(RBNode.VAL.RED); - this.rightRotate(node.getParent().getParent()); + node.getParent().setColor(RBNode.VAL.BLACK); + node.getParent().getParent().setColor(RBNode.VAL.RED); + this.leftRotate(node.getParent().getParent()); } } - if (node == this.root) { // We have fixed up to the root. - break; - } } - root.changeColor(RBNode.VAL.BLACK); // Ensure root remains black. + root.setColor(RBNode.VAL.BLACK); } /** - * Finds the minimum element in the tree. - * - * @param node The node the tree is rooted from. - * @return The node containing the minimum element in the tree. + * Helper function to conduct a left-rotate. + * @param node The node to rotate on. */ - public RBNode findMin(RBNode node) { - while (node.getLeft() != this.nilNode) { - node = node.getRight(); - } - return node; - } - - /** - * Helper function to transpose node b into node a's position. - * - * @param a The node to be transposed out. - * @param b The node to be transposed in. - */ - private void swap(RBNode a, RBNode b) { - if (a.getParent() == null) { - root = b; - } else if (a == a.getParent().getLeft()) { - a.getParent().setLeft(b); + 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 { - a.getParent().setRight(b); + node.getParent().setRight(temp); } - b.setParent(a.getParent()); + temp.setLeft(node); + node.setParent(temp); } /** - * To delete a node containing a certain element from the tree. - * Note that this implementation does not account for duplicates of the same element. - * - * @param element The element we need to delete from the tree. + * Helper function to conduct a right-rotate. + * @param node The node to rotate on. */ - public void delete(T element) { - RBNode node = root; - RBNode target = this.nilNode; - while (node != null) { - int compareResult = node.getElement().compareTo(element); - switch (compareResult) { - case 0: - target = node; - break; - case 1: - node = node.getLeft(); - break; - default: - node = node.getRight(); - break; - } - } - if (target == this.nilNode) { - return; - } - RBNode temp = target; - RBNode.VAL initialColor = temp.getColor(); - RBNode fin; - if (target.getLeft() == this.nilNode) { - fin = temp.getRight(); - this.swap(target, target.getLeft()); - } else if (target.getRight() == this.nilNode) { - fin = temp.getLeft(); - this.swap(target, target.getRight()); + 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 { - temp = this.findMin(target.getRight()); - initialColor = temp.getColor(); - fin = temp.getRight(); - if (temp.getParent() == target) { - fin.setParent(temp); - } else { - this.swap(temp, temp.getRight()); - temp.setRight(target.getRight()); - temp.getRight().setParent(temp); - } - this.swap(target, temp); - temp.setLeft(target.getLeft()); - temp.getLeft().setParent(temp); - temp.changeColor(target.getColor()); - } - if (initialColor.equals(RBNode.VAL.BLACK)) { - this.fixDelete(fin); // We need to fix the final node if we removed a black. + node.getParent().setRight(temp); } + temp.setRight(node); + node.setParent(temp); } - /** - * Function to re-balance the tree post delete operations. - * - * @param node The node to fix upwards from. - */ - private void fixDelete(RBNode node) { - RBNode temp; - while (node != null && node.getColor().equals(RBNode.VAL.BLACK)) { - if (node == node.getParent().getLeft()) { // Current node is the left child. - temp = node.getParent().getRight(); - if (temp.getColor().equals(RBNode.VAL.RED)) { // Our sibling node is red. - temp.changeColor(RBNode.VAL.BLACK); - node.getParent().changeColor(RBNode.VAL.RED); - this.leftRotate(node.getParent()); - temp = node.getParent().getRight(); - } - if (temp.getColor().equals(RBNode.VAL.BLACK) - && temp.getColor().equals(RBNode.VAL.BLACK)) { // Left & Right child are black. - temp.changeColor(RBNode.VAL.RED); - node = node.getParent(); - } else { - if (temp.getRight().getColor().equals(RBNode.VAL.BLACK)) { - temp.getLeft().changeColor(RBNode.VAL.BLACK); - temp.changeColor(RBNode.VAL.RED); - this.rightRotate(temp); - temp = node.getParent().getRight(); - } - temp.changeColor(node.getParent().getColor()); - node.getParent().changeColor(RBNode.VAL.BLACK); - temp.getRight().changeColor(RBNode.VAL.BLACK); - this.leftRotate(node.getParent()); - node = root; - } - } else { // Current node is the right child, mirror of the above :) - temp = node.getParent().getLeft(); - if (temp.getColor().equals(RBNode.VAL.RED)) { - temp.changeColor(RBNode.VAL.BLACK); - node.getParent().changeColor(RBNode.VAL.RED); - this.rightRotate(node.getParent()); - temp = node.getParent().getLeft(); - } - if (temp.getRight().getColor().equals(RBNode.VAL.BLACK) - && temp.getLeft().getColor().equals(RBNode.VAL.BLACK)) { - temp.changeColor(RBNode.VAL.RED); - node = node.getParent(); - } else { - if (temp.getLeft().getColor().equals(RBNode.VAL.BLACK)) { - temp.getRight().changeColor(RBNode.VAL.BLACK); - temp.changeColor(RBNode.VAL.RED); - this.leftRotate(temp); - temp = node.getParent().getLeft(); - } - temp.changeColor(node.getParent().getColor()); - node.getParent().changeColor(RBNode.VAL.BLACK); - temp.getLeft().changeColor(RBNode.VAL.BLACK); - this.rightRotate(node.getParent()); - node = root; - } - } - } - node.changeColor(RBNode.VAL.BLACK); // We ensure our root remains black! - } - - /** - * Gets level order of the tree. - * - * @param node The node the tree is rooted at. - * @return String representation of the level order of the tree. - */ - public String getLevelOrder(RBNode node) { - if (node == this.nilNode || node == null) { - return ""; - } - StringBuilder sb = new StringBuilder(); - Queue> q = new LinkedList<>(); - q.add(node); - while (!q.isEmpty()) { - RBNode current = q.poll(); - sb.append(current.getElement()); - if (current.getLeft() != null && current.getLeft() != this.nilNode) { - q.add(current.getLeft()); - } - if (current.getRight() != null && current.getRight() != this.nilNode) { - q.add(current.getRight()); - } - } - return sb.toString(); - } - - /** - * Gets depth of node. - * - * @param node The node the tree is rooted at. - * @return The depth of the node within the tree. - */ - public int getDepth(RBNode node) { - if (node == null || node == this.nilNode) { - return 0; - } - int leftHeight = getDepth(node.getLeft()); - int rightHeight = getDepth(node.getRight()); - return 1 + Math.max(leftHeight, rightHeight); - } } From 3159ffa349a2789e638b871ff1112f6edcdd378a Mon Sep 17 00:00:00 2001 From: yeoshuheng Date: Thu, 8 Feb 2024 22:49:11 +0800 Subject: [PATCH 11/15] feat: Add Delete for RB --- .../java/dataStructures/rbTree/RBTree.java | 162 +++++++++++++++++- 1 file changed, 159 insertions(+), 3 deletions(-) diff --git a/src/main/java/dataStructures/rbTree/RBTree.java b/src/main/java/dataStructures/rbTree/RBTree.java index a9269af0..efb0a389 100644 --- a/src/main/java/dataStructures/rbTree/RBTree.java +++ b/src/main/java/dataStructures/rbTree/RBTree.java @@ -26,11 +26,20 @@ public RBTree() { this.root = nil; } + /** + * Getter for root. + * @return The root of the RB tree. + */ + public RBNode getRoot() { + return this.root; + } + /** * Inserts element into tree. * @param element The element to insert. + * @return The newly added node. */ - public void insert(T element) { + public RBNode insert(T element) { RBNode toAdd = new RBNode<>(element); RBNode prev = nil; RBNode curr = root; @@ -51,11 +60,12 @@ public void insert(T element) { prev.setRight(toAdd); } this.fixInsert(toAdd); + return toAdd; } /** - * Fixes tree upwards upon insert operation. - * @param node Node to fix upwards from. + * 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)) { @@ -97,6 +107,28 @@ public void fixInsert(RBNode node) { 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. @@ -141,4 +173,128 @@ private void rightRotate(RBNode 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 != 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); + 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); + } + } + } From 9d5531f706ddbf62d251f67a85a1c8e230c1bbe0 Mon Sep 17 00:00:00 2001 From: yeoshuheng Date: Thu, 8 Feb 2024 23:20:44 +0800 Subject: [PATCH 12/15] feat: Add more features to RB tree --- .../java/dataStructures/rbTree/RBNode.java | 14 ------ .../java/dataStructures/rbTree/RBTree.java | 47 +++++++++++++++++-- .../dataStructures/rbTree/RBTreeTest.java | 39 +++++++-------- 3 files changed, 63 insertions(+), 37 deletions(-) diff --git a/src/main/java/dataStructures/rbTree/RBNode.java b/src/main/java/dataStructures/rbTree/RBNode.java index 7081e6e4..4af99192 100644 --- a/src/main/java/dataStructures/rbTree/RBNode.java +++ b/src/main/java/dataStructures/rbTree/RBNode.java @@ -54,20 +54,6 @@ public RBNode(T element, RBNode left, RBNode right) { this.parent = null; } - /** - * Constructor for our RB-Tree node. - * Defaults to red. - * - * @param element The element to add. - */ - public RBNode(T element) { - this.element = element; - this.left = null; - this.right = null; - this.color = VAL.RED; - this.parent = null; - } - /** * Constructor for a NIL node. */ diff --git a/src/main/java/dataStructures/rbTree/RBTree.java b/src/main/java/dataStructures/rbTree/RBTree.java index efb0a389..5911ebdf 100644 --- a/src/main/java/dataStructures/rbTree/RBTree.java +++ b/src/main/java/dataStructures/rbTree/RBTree.java @@ -34,13 +34,52 @@ 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); + RBNode toAdd = new RBNode<>(element, nil, nil); RBNode prev = nil; RBNode curr = root; while (curr != nil) { @@ -54,7 +93,7 @@ public RBNode insert(T element) { toAdd.setParent(prev); if (prev == nil) { this.root = toAdd; - } else if (element.compareTo(curr.getElement()) < 0) { + } else if (element.compareTo(prev.getElement()) < 0) { prev.setLeft(toAdd); } else { prev.setRight(toAdd); @@ -195,7 +234,7 @@ public void transplant(RBNode a, RBNode b) { * @return The node with the minimum value. */ public RBNode getMin(RBNode node) { - while (node != nil) { + while (node.getLeft() != nil) { node = node.getLeft(); } return node; @@ -216,7 +255,7 @@ public void delete(RBNode node) { x = node.getLeft(); this.transplant(node.getLeft(), node); } else { - y = this.getMin(node); + y = this.getMin(node.getRight()); deletedColor = y.getColor(); x = y.getRight(); if (y.getParent() == node) { diff --git a/src/test/java/dataStructures/rbTree/RBTreeTest.java b/src/test/java/dataStructures/rbTree/RBTreeTest.java index 50957a40..20dc1d02 100644 --- a/src/test/java/dataStructures/rbTree/RBTreeTest.java +++ b/src/test/java/dataStructures/rbTree/RBTreeTest.java @@ -19,17 +19,17 @@ public void testInsertAndSearch() { public void testDeleteAndSearch() { RBTree tree = new RBTree<>(); Assert.assertEquals(null, tree.get(10)); - tree.insert(1); + RBNode del1 = tree.insert(1); tree.insert(5); tree.insert(8); tree.insert(2); - tree.insert(3); + 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)); - tree.delete(3); + tree.delete(del2); Assert.assertEquals(null, tree.get(3)); - tree.delete(1); + tree.delete(del1); Assert.assertEquals(null, tree.get(1)); } @@ -40,32 +40,33 @@ public void testRBRotations() { // Testing insert rotations Assert.assertEquals("", tree.getLevelOrder(tree.getRoot())); tree.insert(1); - tree.insert(2); + RBNode del2 = tree.insert(2); tree.insert(3); Assert.assertEquals("2 1 3 ", tree.getLevelOrder(tree.getRoot())); - tree.insert(4); - tree.insert(5); + RBNode del4 = tree.insert(4); + RBNode del5 = tree.insert(5); Assert.assertEquals("2 1 4 3 5 ", tree.getLevelOrder(tree.getRoot())); tree.insert(9); - tree.insert(6); + RBNode del6 = tree.insert(6); tree.insert(7); - tree.insert(8); + 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(6); + 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(5); - Assert.assertEquals("4 2 7 1 3 9 8 ", tree.getLevelOrder(tree.getRoot())); - tree.delete(2); - tree.delete(8); - Assert.assertEquals(3, tree.getDepth(tree.getRoot())); - Assert.assertEquals("4 3 7 1 9 ", tree.getLevelOrder(tree.getRoot())); - tree.delete(4); - Assert.assertEquals("7 3 9 1 ", tree.getLevelOrder(tree.getRoot())); - Assert.assertEquals(3, tree.getDepth(tree.getRoot())); + tree.delete(del5); + Assert.assertEquals("4 2 8 1 3 7 9 ", tree.getLevelOrder(tree.getRoot())); + tree.delete(del2); + tree.delete(del8); + Assert.assertEquals(null, tree.get(8)); + Assert.assertEquals(4, tree.getDepth(tree.getRoot())); + Assert.assertEquals("9 4 3 7 1 ", tree.getLevelOrder(tree.getRoot())); + tree.delete(del4); + Assert.assertEquals("9 7 3 1 ", tree.getLevelOrder(tree.getRoot())); + Assert.assertEquals(4, tree.getDepth(tree.getRoot())); } } From 720c624967bdf83c4d9b26550239785648f9040f Mon Sep 17 00:00:00 2001 From: yeoshuheng Date: Fri, 9 Feb 2024 00:22:41 +0800 Subject: [PATCH 13/15] fix: Fix delete rebalance in RB tree --- src/main/java/dataStructures/rbTree/RBTree.java | 2 +- src/test/java/dataStructures/rbTree/RBTreeTest.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/dataStructures/rbTree/RBTree.java b/src/main/java/dataStructures/rbTree/RBTree.java index 5911ebdf..7a622d15 100644 --- a/src/main/java/dataStructures/rbTree/RBTree.java +++ b/src/main/java/dataStructures/rbTree/RBTree.java @@ -332,8 +332,8 @@ private void fixDelete(RBNode node) { node = this.root; } } - node.setColor(RBNode.VAL.BLACK); } + node.setColor(RBNode.VAL.BLACK); } } diff --git a/src/test/java/dataStructures/rbTree/RBTreeTest.java b/src/test/java/dataStructures/rbTree/RBTreeTest.java index 20dc1d02..e7d2ce8f 100644 --- a/src/test/java/dataStructures/rbTree/RBTreeTest.java +++ b/src/test/java/dataStructures/rbTree/RBTreeTest.java @@ -63,10 +63,10 @@ public void testRBRotations() { tree.delete(del2); tree.delete(del8); Assert.assertEquals(null, tree.get(8)); - Assert.assertEquals(4, tree.getDepth(tree.getRoot())); - Assert.assertEquals("9 4 3 7 1 ", tree.getLevelOrder(tree.getRoot())); + Assert.assertEquals(3, tree.getDepth(tree.getRoot())); + Assert.assertEquals("4 3 9 1 7 ", tree.getLevelOrder(tree.getRoot())); tree.delete(del4); - Assert.assertEquals("9 7 3 1 ", tree.getLevelOrder(tree.getRoot())); - Assert.assertEquals(4, tree.getDepth(tree.getRoot())); + Assert.assertEquals("7 3 9 1 ", tree.getLevelOrder(tree.getRoot())); + Assert.assertEquals(3, tree.getDepth(tree.getRoot())); } } From 9100abc75e7539b36c83095adf8c39e8dfadd009 Mon Sep 17 00:00:00 2001 From: yeoshuheng Date: Fri, 9 Feb 2024 00:36:45 +0800 Subject: [PATCH 14/15] fix: Fix checkstyle for RB --- .../java/dataStructures/rbTree/RBTree.java | 4 +- .../dataStructures/rbTree/RBTreeTest.java | 120 +++++++++--------- 2 files changed, 62 insertions(+), 62 deletions(-) diff --git a/src/main/java/dataStructures/rbTree/RBTree.java b/src/main/java/dataStructures/rbTree/RBTree.java index 7a622d15..e8b50d7a 100644 --- a/src/main/java/dataStructures/rbTree/RBTree.java +++ b/src/main/java/dataStructures/rbTree/RBTree.java @@ -12,12 +12,12 @@ public class RBTree> { /** * Root of the tree. */ - RBNode root; + private RBNode root; /** * NIL Node */ - RBNode nil = new RBNode<>(); + private RBNode nil = new RBNode<>(); /** * Constructor for RB Tree. diff --git a/src/test/java/dataStructures/rbTree/RBTreeTest.java b/src/test/java/dataStructures/rbTree/RBTreeTest.java index e7d2ce8f..dd19172b 100644 --- a/src/test/java/dataStructures/rbTree/RBTreeTest.java +++ b/src/test/java/dataStructures/rbTree/RBTreeTest.java @@ -3,70 +3,70 @@ 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)); - } + @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)); + } - @Test - public void testDeleteAndSearch() { - RBTree tree = new RBTree<>(); - Assert.assertEquals(null, tree.get(10)); - 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)); - tree.delete(del2); - Assert.assertEquals(null, tree.get(3)); - tree.delete(del1); - Assert.assertEquals(null, tree.get(1)); - } + @Test + public void testDeleteAndSearch() { + RBTree tree = new RBTree<>(); + Assert.assertEquals(null, tree.get(10)); + 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)); + tree.delete(del2); + Assert.assertEquals(null, tree.get(3)); + tree.delete(del1); + Assert.assertEquals(null, tree.get(1)); + } - @Test - public void testRBRotations() { - RBTree tree = new RBTree<>(); + @Test + public void testRBRotations() { + 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())); + // 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())); - RBNode del4 = tree.insert(4); - RBNode del5 = tree.insert(5); - Assert.assertEquals("2 1 4 3 5 ", tree.getLevelOrder(tree.getRoot())); + RBNode del4 = tree.insert(4); + RBNode del5 = tree.insert(5); + Assert.assertEquals("2 1 4 3 5 ", tree.getLevelOrder(tree.getRoot())); - 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())); + 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())); - tree.delete(del2); - tree.delete(del8); - Assert.assertEquals(null, tree.get(8)); - Assert.assertEquals(3, tree.getDepth(tree.getRoot())); - 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(3, tree.getDepth(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())); + tree.delete(del2); + tree.delete(del8); + Assert.assertEquals(null, tree.get(8)); + Assert.assertEquals(3, tree.getDepth(tree.getRoot())); + 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(3, tree.getDepth(tree.getRoot())); + } } From 4899a3e4189fb6ad7523f15bd25fc7a231856467 Mon Sep 17 00:00:00 2001 From: yeoshuheng Date: Fri, 9 Feb 2024 00:46:04 +0800 Subject: [PATCH 15/15] feat: Add check for red black properties --- .../java/dataStructures/rbTree/RBTree.java | 29 +++++++++++++++++++ .../dataStructures/rbTree/RBTreeTest.java | 12 +++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/main/java/dataStructures/rbTree/RBTree.java b/src/main/java/dataStructures/rbTree/RBTree.java index e8b50d7a..764928be 100644 --- a/src/main/java/dataStructures/rbTree/RBTree.java +++ b/src/main/java/dataStructures/rbTree/RBTree.java @@ -336,4 +336,33 @@ private void fixDelete(RBNode node) { 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/test/java/dataStructures/rbTree/RBTreeTest.java b/src/test/java/dataStructures/rbTree/RBTreeTest.java index dd19172b..5de96693 100644 --- a/src/test/java/dataStructures/rbTree/RBTreeTest.java +++ b/src/test/java/dataStructures/rbTree/RBTreeTest.java @@ -13,12 +13,14 @@ public void testInsertAndSearch() { 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); @@ -27,14 +29,17 @@ public void testDeleteAndSearch() { 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 testRBRotations() { + public void testRedBlackRotations() { RBTree tree = new RBTree<>(); // Testing insert rotations @@ -43,10 +48,12 @@ public void testRBRotations() { 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); @@ -60,13 +67,16 @@ public void testRBRotations() { 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())); } }