From 202953de49c63736aefa5869f00a9b963759b055 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Tue, 8 Jun 2021 20:48:33 -0400 Subject: [PATCH] Add examples of red-black tree algorithms + Using pseudocode examples from MIT Introduction to Algorithms --- cpp/algorithms/trees/redblack/driver.cpp | 34 +- cpp/algorithms/trees/redblack/redblack.cpp | 641 ++++++++++++++++----- cpp/algorithms/trees/redblack/redblack.h | 89 +-- 3 files changed, 580 insertions(+), 184 deletions(-) diff --git a/cpp/algorithms/trees/redblack/driver.cpp b/cpp/algorithms/trees/redblack/driver.cpp index 6c9351f..cd0c529 100644 --- a/cpp/algorithms/trees/redblack/driver.cpp +++ b/cpp/algorithms/trees/redblack/driver.cpp @@ -15,12 +15,15 @@ int main (const int argc, const char * argv[]) { - BinarySearchTree testTree; - std::vector inputValues {2, 6, 9, 1, 5, 8, 10}; + RedBlackTree testTree; +// std::vector inputValues {2, 6, 9, 1, 5, 8, 10}; // Alternative input values -// std::vector inputValues {10, 12, 6, 4, 20, 8, 7, 15, 13}; + // + Provides a larger RBT, where every path from the root to a leaf node + // ++ Also constructs the same RBT as seen in MIT Intro to Algorithms + std::vector inputValues {26, 17, 41, 14, 21, 30, 47, 10, 16, 19, 23, 28, + 38, 7, 12, 15, 20, 35, 39, 3}; - std::cout << "Building binary search tree with input: "; + std::cout << "Building red-black tree with input: "; for (const auto &value : inputValues) std::cout << value << ", "; std::cout << std::endl; @@ -44,6 +47,7 @@ int main (const int argc, const char * argv[]) // Test removing a node, printing the result in-order std::cout << "\nRemoving root value...\n"; testTree.remove(testTree.getRoot()->element); + testTree.remove(testTree.getRoot()->element); // Can use inline function to remove a value directly for testing - // testTree.remove(6); @@ -53,20 +57,20 @@ int main (const int argc, const char * argv[]) // Test copy constructor std::cout << "\nCloning testTree to testTree2...\n"; - BinarySearchTree testTree2 = testTree; + RedBlackTree testTree2 = testTree; std::cout << "Inorder traversal of the cloned testTree2: \n"; testTree2.printInOrder(); std::cout << std::endl; // Test assignment operator std::cout << "\nCloning testTree to testTree3...\n"; - BinarySearchTree testTree3; + RedBlackTree testTree3; testTree3 = testTree; std::cout << "Inorder traversal of the cloned testTree3 tree: \n"; testTree3.printInOrder(); std::cout << std::endl; - // Test emptying the BST + // Test emptying the RBT std::cout << "\nEmptying testTree...\n"; testTree.makeEmpty(); std::cout << "testTree isEmpty: " << (testTree.isEmpty() ? "true" : "false"); @@ -82,15 +86,15 @@ int main (const int argc, const char * argv[]) testTree3.printInOrder(); std::cout << std::endl; - std::cout << "\nChecking if tree contains value of 6: "; - std::cout << (testTree2.contains(6) ? "true" : "false") << std::endl; + std::cout << "\nChecking if tree contains value of 7: "; + std::cout << (testTree2.contains(7) ? "true" : "false") << std::endl; std::cout << "Checking if tree contains value of 600: "; - std::cout << (testTree2.contains(600) ? "true" : "false") << std::endl; + std::cout << (testTree2.contains(700) ? "true" : "false") << std::endl; - std::cout << "\nSuccessor of node with value 6: " - << testTree2.successor(testTree2.search(6))->element; - std::cout << "\nPredecessor of node with value 6: " - << testTree2.predecessor(testTree2.search(6))->element; + std::cout << "\nSuccessor of node with value 7: " + << testTree2.successor(testTree2.search(7))->element; + std::cout << "\nPredecessor of node with value 7: " + << testTree2.predecessor(testTree2.search(7))->element; std::cout << std::endl; -} \ No newline at end of file +} diff --git a/cpp/algorithms/trees/redblack/redblack.cpp b/cpp/algorithms/trees/redblack/redblack.cpp index 30194dc..e0dd3fa 100644 --- a/cpp/algorithms/trees/redblack/redblack.cpp +++ b/cpp/algorithms/trees/redblack/redblack.cpp @@ -15,76 +15,94 @@ * Constructors, Destructors, Operators *******************************************************************************/ -/** BinarySearchTree Copy Assignment Operator - * @brief Empty the calling object's root BinaryNode, and copy the rhs data +// Use a static member for nil +// + All RBTs will share this value to represent values Not In the List +RedBlackTree::RedBlackNode *RedBlackTree::nil = new RedBlackTree::RedBlackNode(); + + +/* RedBlackNode Copy Constructor * - * Runs in O( n ) time, since we visit each node in the BST once - * + Where n is the total number of nodes within the BST + * Runs in O( n ) time, since we visit each node in the RBT once + * + Where n is the total number of nodes within the RBT + * + * @param rhs An existing RBT to initialize this node (and children) with + */ +RedBlackTree::RedBlackNode::RedBlackNode(RedBlackNode * toCopy) +{ + // Base case, breaks recursion when we hit a null node + // + Returns to the previous call in the stack + if (toCopy == nil) return; + // Set the element of this RedBlackNode to the value in toCopy->element + element = toCopy->element; + // If there is a left / right node, copy it using recursion + // + If there is no left / right node, set them to nullptr + if (toCopy->left != nil) { + left = new RedBlackNode(toCopy->left); + left->parent = this; + } + else left = nil; + + if (toCopy->right != nil) { + right = new RedBlackNode(toCopy->right); + right->parent = this; + } + else right = nil; + + if (toCopy->parent == nil) parent = nil; + + // TODO: Fix the copying of the RBT + color = toCopy->color; +} + +/** RedBlackTree Copy Assignment Operator + * @brief Empty the calling object's root RedBlackNode, and copy the rhs data + * + * Runs in O( n ) time, since we visit each node in the RBT once + * + Where n is the total number of nodes within the RBT * * makeEmpty() and clone() are both O( n ), and we call each sequentially * + This would appear to be O( 2n ), but we drop the constant of 2 * - * @param rhs The BST to copy, beginning from its root BinaryNode - * @return BinarySearchTree The copied BinarySearchTree object + * @param rhs The RBT to copy, beginning from its root RedBlackNode + * @return RedBlackTree The copied RedBlackTree object */ -BinarySearchTree& BinarySearchTree::operator=(const BinarySearchTree &rhs) +RedBlackTree& RedBlackTree::operator=(const RedBlackTree &rhs) { // If the objects are already equal, do nothing if (this == &rhs) return *this; // Empty this->root makeEmpty(root); + +// if (root == nil) root = new RedBlackNode(); // Copy rhs to this->root root = clone(rhs.root); return *this; } -/* BinaryNode Copy Constructor - * - * Runs in O( n ) time, since we visit each node in the BST once - * + Where n is the total number of nodes within the BST - * - * @param rhs An existing BST to initialize this node (and children) with - */ -BinarySearchTree::BinaryNode::BinaryNode(BinaryNode * toCopy) -{ - // Base case, breaks recursion when we hit a null node - // + Returns to the previous call in the stack - if (toCopy == nullptr) return; - // Set the element of this BinaryNode to the value in toCopy->element - element = toCopy->element; - // If there is a left / right node, copy it using recursion - // + If there is no left / right node, set them to nullptr - if (toCopy->left != nullptr) { - left = new BinaryNode(toCopy->left); - left->parent = this; - } - if (toCopy->right != nullptr) { - right = new BinaryNode(toCopy->right); - right->parent = this; - } +RedBlackTree::RedBlackTree(const RedBlackTree &rhs) { + root = clone(rhs.getRoot()); } - /******************************************************************************* * Public Member Functions *******************************************************************************/ /** contains - * @brief Determines if value exists within a BinaryNode and its children + * @brief Determines if value exists within a RedBlackNode and its children * - * Runs in O( height ) time, given the height of the current BST - * + In the worst case, we search for a node at the bottom of the BST + * Runs in O( height ) time, given the height of the current RBT + * + In the worst case, we search for a node at the bottom of the RBT * - * @param value The value to search for within the BST - * @param start The root BinaryNode to begin the search + * @param value The value to search for within the RBT + * @param start The root RedBlackNode to begin the search * @return true If the value is found within the root node or its children * @return false If the value is not found within the root node or its children */ -bool BinarySearchTree::contains(const int &value, BinaryNode *start) const +bool RedBlackTree::contains(const int &value, RedBlackNode *start) const { // If tree is empty - if (start == nullptr) return false; + if (start == nil) return false; // If x is smaller than our current value else if (value < start->element) return contains(value, start->left); // If x is larger than our current value, check the right node @@ -92,59 +110,373 @@ bool BinarySearchTree::contains(const int &value, BinaryNode *start) const else return true; } +/** rotateLeft + * @brief Rotates a node and it's children counter-clockwise around a pivotNode + * + * Runs in O( 1 ) constant time, only immediately available values are used + * + * @param pivotNode The node to begin rotation from + */ +void RedBlackTree::rotateLeft(RedBlackNode *pivotNode) +{ + // To rotateRight, we must relocate the rightChild node + RedBlackNode *rightChild = pivotNode->right; + + pivotNode->right = rightChild->left; + // If the rightChild->left node exists, update it's parent to the pivotNode + if (rightChild->left != nil) rightChild->left->parent = pivotNode; + // After we rotateLeft, the rightChild will be in the position of pivotNode + // + So we update rightChild->parent to reflect this + rightChild->parent = pivotNode->parent; + + // The following conditions relocate the rightChild to its new position + // Case 1: pivotNode has no parent, so it must be the rootNode + // Case 2: pivotNode is the left child of its parent node + // Case 2: pivotNode is the right child of its parent node + if (pivotNode->parent == nil) root = rightChild; + else if (pivotNode == pivotNode->parent->left) { + pivotNode->parent->left = rightChild; + } + else pivotNode->parent->right = rightChild; + + // The rightChild is now the parent of pivotNode + // + Since we rotated left, set rightChild->left to point to the pivotNode + // + Update the pivotNode->parent to reflect this + rightChild->left = pivotNode; + pivotNode->parent = rightChild; +} + +/** rotateRight + * @brief Rotates a node and it's children clockwise around a pivotNode + * + * Runs in O( 1 ) constant time, only immediately available values are used + * + * @param pivotNode The node to begin rotation from + */ +void RedBlackTree::rotateRight(RedBlackTree::RedBlackNode *pivotNode) +{ + // To rotateRight, we must relocate the leftChild node + RedBlackNode *leftChild = pivotNode->left; + + pivotNode->left = leftChild->right; + // If the leftChild->left node exists, update it's parent to the pivotNode + if (leftChild->left != nil) leftChild->right->parent = pivotNode; + // After we rotateRight, the leftChild will be in the position of pivotNode + // + So we update leftChild->parent to reflect this + leftChild->parent = pivotNode->parent; + + // The following conditions relocate the leftChild to its new position + // Case 1: pivotNode has no parent, so it must be the rootNode + // Case 2: pivotNode is the left child of its parent node + // Case 2: pivotNode is the right child of its parent node + if (pivotNode->parent == nil) root = leftChild; + else if (pivotNode == pivotNode->parent->left) { + pivotNode->parent->left = leftChild; + } + else pivotNode->parent->right = leftChild; + + // The leftChild is now the parent of pivotNode + // + Since we rotated right, set leftChild->right to point to the pivotNode + // + Update the pivotNode->parent to reflect this + leftChild->right = pivotNode; + pivotNode->parent = leftChild; +} + +/** insertFixup + * @brief Corrects RBT properties to enforce proper node colors after insertion + * + * Runs in O( lg(n) ) time, where n is the number of nodes in the RBT + * + * @param start The node to begin the fixup operation from + */ +void RedBlackTree::insertFixup(RedBlackTree::RedBlackNode *start) +{ + // While the parent of start node is valid and colored red + // + Check for the two red nodes in a row, and update their colors + while (start->parent != nil && start->parent->color == Red) { + + // If the parent of start node is a *left* child + if (start->parent == start->parent->parent->left) { + // Check the *right* uncle node to enforce RBT properties + // + Since the start->parent->right would be the start node's sibling + // ++ The start->parent->parent->left would be the start node's uncle + RedBlackNode *uncleNode = start->parent->parent->right; + + // If the uncleNode is red, we are in case 1 + // + In case 1, start->parent->color and uncleNode->color are both Red + if (uncleNode->color == Red) { + start->parent->color = Black; + uncleNode->color = Black; + start->parent->parent->color = Red; + // Advance start to it's grandparent node + start = start->parent->parent; + } + else { // Otherwise, if case 1 is not violated... + + // If the start node is a right child, we are in case 2 + // + In case 2, uncleNode is Black, and start node is a right child + if (start == start->parent->right) { + // Advance start up the tree to it's parent node, then *rotateLeft* + start = start->parent; + rotateLeft(start); + // After this rotation, we have forced ourselves into case 3 + // + So, whether this case (2) executes or not, we end up in case 3 + } + + // No need for an if statement here, since the start node must be either + // + a right or left child. It cannot be neither always run these steps. + // ++ The only time these steps are skipped, is when we are in case 1 + + // Start node is a left child, we are in case 3 + // + In case 3, uncleNode is Black and start node is a left child + start->parent->color = Black; + start->parent->parent->color = Red; + // Rotate around the grandparent node + rotateRight(start->parent->parent); + } + + } + else { // If the parent of start node is a *right* child + // We follow the same 3 cases as above.. + // + but with all left / right rotations and references swapped + + // Check the *left* uncle node to enforce RBT properties + RedBlackNode *uncleNode = start->parent->parent->left; + // If the uncleNode is red, we are in case 1 + if (uncleNode != nil && uncleNode->color == Red) { + start->parent->color = Black; + uncleNode->color = Black; + start->parent->parent->color = Red; + // Advance start to it's grandparent node + start = start->parent->parent; + } + else { // Otherwise, if case 1 is not violated... + + // If the start node is a left child, we are in case 2 + if (start == start->parent->left) { + // Advance start up the tree to it's parent node, then *rotateRight* + start = start->parent; + rotateRight(start); + // After this rotation, we have forced ourselves into case 3 + // + So, whether this case (2) executes or not, we end up in case 3 + } + + // The only time these steps are skipped, is when we are in case 1 + // + Always perform these steps when not in case 1 + + // Start node is a *right* child, we are in case 3 + start->parent->color = Black; + start->parent->parent->color = Red; + // Rotate around the grandparent node + rotateLeft(start->parent->parent); + } + } + + // The while() loop will always terminate after case 2 and (or) 3 is ran + } + + // The root of the RBT should always be black + root->color = Black; +} + +/** deleteFixup + * @brief Corrects RBT properties to enforce proper node colors after removal + * + * Runs in O( lg(n) ) time, where n is the number of nodes in the RBT + * + * @param start The node to begin the fixup operation from + */ +void RedBlackTree::deleteFixup(RedBlackTree::RedBlackNode *start) +{ + // Until we reach the root of the RBT, move the extra black node up the tree + while (start != root && start->color == Black) { + // If the start node is a *left* child + if (start == start->parent->left) { + + RedBlackNode *siblingNode = start->parent->right; + // If start node's siblingNode is colored Red, we are in case 1 + // + In case 1, the only requirement is siblingNode is Red + if (siblingNode->color == Red) { + // Color the siblingNode Black, and the start->parent node Red + siblingNode->color = Black; + start->parent->color = Red; + + // rotateLeft around the parent node, and update the siblingNode + // + siblingNode now represents the new sibling + // + start->parent is now a Red node with two Black siblings + // ++ This is ideal, since Red nodes can only have Black children nodes + rotateLeft(start->parent); + siblingNode = start->parent->right; + } + + // If the siblingNode is Black, we are in case 2 + // + In case 2, siblingNode is Black and has two Black children nodes + if (siblingNode->left->color == Black && siblingNode->right->color == Black) { + // Color the siblingNode Red and advance start node up the tree + siblingNode->color = Red; + start = start->parent; + } + else { // If either one of the siblingNode's children are Red... + + // If the siblingNode->right child node is Black, we are in case 3 + // + In case 3, the start node's siblingNode is black + // ++ And the siblingNode's right child is Black + if (siblingNode->right->color == Black) { + // Color the left child of siblingNode Black, and siblingNode to Red + siblingNode->left->color = Black; + siblingNode->color = Red; + + // rotateRight(siblingNode) places the Red siblingNode as the right + // + child of the previous siblingNode->left + rotateRight(siblingNode); + // Update the siblingNode of start node to reflect the new sibling + siblingNode = start->parent->right; + // After rotateRight(siblingNode), we have put ourselves into case 4 + } + + // siblingNode->left is colored Black, we are in case 4 + // + In case 4, the start node's sibling is Black + // ++ And the siblingNode's right child is Red + siblingNode->color = start->parent->color; + start->parent->color = Black; + siblingNode->right->color = Black; + // rotateLeft around the start->parent node, placing the siblingNode + // + In the previous position of the start->parent node + rotateLeft(start->parent); + // The previous start->parent node is now siblingNode's left child + + // Setting the start node to root node ensures the while() terminates + start = root; + } + } + else { // If the start node is a right child + // We follow the same 3 cases as above.. + // + but with all left / right rotations and references swapped + + RedBlackNode *siblingNode = start->parent->left; + // If start node's siblingNode is colored Red, we are in case 1 + if (siblingNode->color == Red) { + // Color the siblingNode Black, and the start->parent node Red + siblingNode->color = Black; + start->parent->color = Red; + + // *rotateRight* around the parent node, and update the siblingNode + rotateRight(start->parent); + siblingNode = start->parent->left; + } + + // If the siblingNode is Black, we are in case 2 + if (siblingNode->left->color == Black && siblingNode->right->color == Black) { + siblingNode->color = Red; + start = start->parent; + } + else { // If either one of the siblingNode's children are Red... + + // If the siblingNode->left child node is Black, we are in case 3 + if (siblingNode->left->color == Black) { + // Color the *left* child of siblingNode Black, and siblingNode to Red + siblingNode->right->color = Black; + siblingNode->color = Red; + + // rotateLeft(siblingNode) places the Red siblingNode as the *left* + // + child of the previous siblingNode->right + rotateLeft(siblingNode); + // Update the siblingNode of start node to reflect the new sibling + siblingNode = start->parent->left; + // After rotateRight(siblingNode), we have put ourselves into case 4 + } + + // siblingNode->left is colored Black, we are in case 4 + siblingNode->color = start->parent->color; + start->parent->color = Black; + siblingNode->left->color = Black; + + // rotateRight around the start->parent node, placing the siblingNode + // + In the previous position of the start->parent node + rotateRight(start->parent); + + // Setting the start node to root node ensures the while() terminates + start = root; + } + } + + } + + // TODO: I could only get this working by using nil->parent + // to store the parent of the last replacementNode within removeNode() + // Is this expected, or have I missed something and hacked this? + + // Update nil->parent to nullptr if it is any other value + // + When we transplant(), we temporarily store the parent of the relocatedNode + // + relocatedNode is seen passed to transplant() within remove() + if (nil->parent != nullptr) nil->parent = nullptr; +} + /** makeEmpty - * @brief Recursively delete the given root BinaryNode and all of its children + * @brief Recursively delete the given root RedBlackNode and all of its children * * Runs in O( n ) time, since we need to visit each node in the tree once * + Where n is the total number of nodes in the tree * - * @param tree The root BinaryNode to delete, along with all child nodes + * @param tree The root RedBlackNode to delete, along with all child nodes */ -void BinarySearchTree::makeEmpty(BinarySearchTree::BinaryNode * & tree) +void RedBlackTree::makeEmpty(RedBlackTree::RedBlackNode * & tree) { // Base case: When all nodes have been deleted, tree is a nullptr // + Breaks from recursion - if (tree != nullptr) { + if (tree != nil) { makeEmpty(tree->left); makeEmpty(tree->right); delete tree; - tree = nullptr; + // Set each deleted node = nil, overwriting unused children with nullptr + tree = nil; } + } /** isEmpty - * @brief Determine whether or not the calling BST object is empty + * @brief Determine whether or not the calling RBT object is empty * * Runs in constant time, O( 1 ) * * @return true If this->root node points to an empty tree (nullptr) - * @return false If this->root node points to a constructed BinaryNode + * @return false If this->root node points to a constructed RedBlackNode */ -bool BinarySearchTree::isEmpty() const +bool RedBlackTree::isEmpty() const { - return root == nullptr; + return root == nil; } /** insert - * @brief Insert a value into the tree starting at a given BinaryNode + * @brief Insert a value into the tree starting at a given RedBlackNode * + Uses recursion * - * Runs in O( height ) time, since in the worst case we insert the node at the - * + bottom of the BST. + * Runs in O( lg(n) ) time, since the height of RBTs are <= lg(n) + * + Where n is the number of nodes in the RBT + * + The *sequential* call to insertFixup is also O( lg(n) ).. + * ++ Appears to be O( 2lg(n) ), but we drop the constant 2; No extra overhead * * @param newValue The value to be inserted - * @param start The BinaryNode to begin insertion - * @param prevNode The last checked BinaryNode + * @param start The RedBlackNode to begin insertion + * @param prevNode The last checked RedBlackNode * + prevNode is used to initialize new node's parent */ -void BinarySearchTree::insert(const int &newValue, - BinaryNode *&start, BinaryNode *prevNode) +void RedBlackTree::insert(const int &newValue, + RedBlackNode *&start, RedBlackNode *prevNode) { // Base case: We found a valid position which is empty for the newValue - if (start == nullptr) { + if (start == nil) { // Build a new node, place it at the current position - // + Breaks out of recursion - start = new BinaryNode(newValue, nullptr, nullptr, prevNode); + // + Breaks out of recursion after this code block finishes + + // TODO: Valgrind thinks there is a memory leak here + // It seems to me that calling `delete start` would delete the static nil? + start = new RedBlackNode(newValue, Red, nil, nil, prevNode); + + // Enforce RBT properties on the entire subtree of start node + // + By default, start is the root node of the RBT unless specified + // + This is done with a call to the inlined insert(int) function + insertFixup(start); } else if (newValue < start->element) insert(newValue, start->left, start); else if (newValue > start->element) insert(newValue, start->right, start); @@ -152,62 +484,103 @@ void BinarySearchTree::insert(const int &newValue, } /** remove - * @brief Removes a value from the BST of the given BinaryNode + * @brief Removes a value from the RBT of the given RedBlackNode * - * Runs in O( height ) time, where findMin() is the limiting function - * + remove() and transplant() otherwise run in constant time, without findMin() + * Runs in O( lg(n) ) time, since the height of RBTs are <= lg(n) + * + Where n is the number of nodes in the RBT + * + The *sequential* call to removeFixup is also O( lg(n) ).. + * ++ Appears to be O( 2lg(n) ), but we drop the constant 2; No extra overhead * - * @param removeNode The BinaryNode to remove from the BST + * @param removeNode The RedBlackNode to remove from the RBT */ -void BinarySearchTree::remove(BinaryNode *removeNode) +void RedBlackTree::remove(RedBlackNode *removeNode) { - if (removeNode->left == nullptr) { + RedBlackNode *replacementNode = removeNode; + Color originalColor = removeNode->color; + + // This node will be passed to removeFixup() later to enforce RBT properties + RedBlackNode *fixupNode; + + if (removeNode->left == nil) { // removeNode has no left node; Replace removeNode with removeNode->right // + It doesn't matter if removeNode->right is nullptr or a valid node // + Since there is no left node, this is the only possible valid transplant // Transplant the right node and its subtree over this node + fixupNode = removeNode->right; transplant(removeNode, removeNode->right); } - else if (removeNode->right == nullptr) { + else if (removeNode->right == nil) { // removeNode has no right node; Replace removeNode with removeNode->right // + removeNode->left exists, in this case + fixupNode = removeNode->left; transplant(removeNode, removeNode->left); } else { - // removeNode has a right and left node, find the next value in-order - // + findMin(removeNode->right) returns the next largest value in the BST - BinaryNode *minNode = findMin(removeNode->right); + // The node to remove has both left and right child nodes + // + We should find a replacement within the child with a larger subtree - // If the next value in-order is not removeNode->right - if (minNode->parent != removeNode) { - // replace minNode with the next largest attached value, minNode->right - transplant(minNode, minNode->right); - // Set minNode->right to the node at removeNode->right - // + Update the parent of removeNode->right accordingly in the next line - minNode->right = removeNode->right; - minNode->right->parent = minNode; + // Compare the depth of subtrees for both children of removeNode... + // + Red child node indicates the equal or longer subtree + // + Black child node indicates the depth of the child's subtree is smaller + if (removeNode->left->color) { + // + findMax(removeNode->right) returns the next smallest value in the RBT + replacementNode = findMax(removeNode->left); + originalColor = replacementNode->color; + + fixupNode = replacementNode->right; + + if (replacementNode->parent == removeNode) { + fixupNode->parent = replacementNode; + } + else { + transplant(replacementNode, replacementNode->right); + replacementNode->right = removeNode->right; + replacementNode->right->parent = replacementNode; + } + transplant(removeNode, replacementNode); + replacementNode->left = removeNode->left; + replacementNode->left->parent = replacementNode; + replacementNode->color = removeNode->color; } + else { + // If the removeNode->left child is black, use the right subtree + // + findMin(removeNode->right) returns the next largest value in the RBT + replacementNode = findMin(removeNode->right); + originalColor = replacementNode->color; + fixupNode = replacementNode->right; - // Replace removeNode with the next node in-order - // + Update the minNode's left and parent nodes in the following lines - transplant(removeNode, minNode); - minNode->left = removeNode->left; - minNode->left->parent = minNode; + if (replacementNode->parent == removeNode) { + fixupNode->parent = replacementNode; + } + else { + transplant(replacementNode, replacementNode->right); + replacementNode->right = removeNode->right; + replacementNode->right->parent = replacementNode; + } + transplant(removeNode, replacementNode); + replacementNode->left = removeNode->left; + replacementNode->left->parent = replacementNode; + replacementNode->color = removeNode->color; + + } } + + // If originalColor of the replacementNode is black, enforce RBT properties + if (!originalColor) deleteFixup(fixupNode); } /** printInOrder * @brief Uses recursion to output left subtree, root node, then right subtrees * * Runs in O( n ) time, since we need to visit each node in the tree once - * + Where n is the total number of nodes within the BST + * + Where n is the total number of nodes within the RBT * - * @param start The root BinaryNode to begin the 'In Order' output + * @param start The root RedBlackNode to begin the 'In Order' output */ -void BinarySearchTree::printInOrder(BinaryNode *start) const +void RedBlackTree::printInOrder(RedBlackNode *start) const { - if(start != nullptr) { + if(start != nil) { printInOrder(start->left); std::cout << start->element << " "; printInOrder(start->right); @@ -218,13 +591,13 @@ void BinarySearchTree::printInOrder(BinaryNode *start) const * @brief Uses recursion to output left subtree, right subtree, then the root * * Runs in O( n ) time, since we need to visit each node in the tree once - * + Where n is the total number of nodes within the BST + * + Where n is the total number of nodes within the RBT * - * @param start The root BinaryNode to begin the 'Post Order' output + * @param start The root RedBlackNode to begin the 'Post Order' output */ -void BinarySearchTree::printPostOrder(BinaryNode *start) const +void RedBlackTree::printPostOrder(RedBlackNode *start) const { - if (start != nullptr) { + if (start != nil) { printPostOrder(start->left); printPostOrder(start->right); std::cout << start->element << " "; @@ -235,13 +608,13 @@ void BinarySearchTree::printPostOrder(BinaryNode *start) const * @brief Uses recursion to output the root, then left subtree, right subtrees * * Runs in O( n ) time, since we need to visit each node in the tree once - * + Where n is the total number of nodes within the BST + * + Where n is the total number of nodes within the RBT * - * @param start The root BinaryNode to begin the 'Pre Order' output + * @param start The root RedBlackNode to begin the 'Pre Order' output */ -void BinarySearchTree::printPreOrder(BinaryNode *start) const +void RedBlackTree::printPreOrder(RedBlackNode *start) const { - if (start != nullptr) { + if (start != nil) { std::cout << start->element << " "; printPreOrder(start->left); printPreOrder(start->right); @@ -252,64 +625,64 @@ void BinarySearchTree::printPreOrder(BinaryNode *start) const * @brief Search for a given value within a tree or subtree using recursion * * Runs in O( height ) time - * + In the worst case, we are searching for a node at the bottom of the BST + * + In the worst case, we are searching for a node at the bottom of the RBT * * @param value The value to search for * @param start The node to start the search from; Can be a subtree - * @return A pointer to the BinaryNode containing the value within the BST + * @return A pointer to the RedBlackNode containing the value within the RBT * + Returns nullptr if the node was not found */ -BinarySearchTree::BinaryNode *BinarySearchTree::search( - const int &value, BinaryNode *start) const +RedBlackTree::RedBlackNode *RedBlackTree::search( + const int &value, RedBlackNode *start) const { - // Base case: If BST is empty, or holds the value we are searching for + // Base case: If RBT is empty, or holds the value we are searching for // + Breaks out of recursion - if (start == nullptr || start->element == value) return start; + if (start == nil || start->element == value) return start; else if (start->element < value) return search(value, start->right); else if (start->element > value) return search(value, start->left); } /** findMin - * @brief Find the minimum value within the BST of the given BinaryNode + * @brief Find the minimum value within the RBT of the given RedBlackNode * + This example uses a while loop; findMax uses recursion * * Runs in O( height ) time - * + In the worst case, we traverse to to the left-most bottom of the BST + * + In the worst case, we traverse to to the left-most bottom of the RBT * - * @param start The root BinaryNode to begin checking values - * @return A pointer to the BinaryNode which contains the smallest value - * + Returns nullptr if BST is empty + * @param start The root RedBlackNode to begin checking values + * @return A pointer to the RedBlackNode which contains the smallest value + * + Returns nullptr if RBT is empty */ -BinarySearchTree::BinaryNode * BinarySearchTree::findMin(BinaryNode *start) const +RedBlackTree::RedBlackNode * RedBlackTree::findMin(RedBlackNode *start) const { // If our tree is empty - if (start == nullptr) return nullptr; + if (start == nil) return nullptr; - while (start->left != nullptr) start = start->left; + while (start->left != nil) start = start->left; // If current node has no smaller children, it is min return start; } /** findMax - * @brief Find the maximum value within the BST of the given BinaryNode + * @brief Find the maximum value within the RBT of the given RedBlackNode * + This example uses recursion; findMin uses a while loop * ++ Both functions can be implemented using a loop or recursion * * Runs in O( height ) time - * + In the worst case, we traverse to to the right-most bottom of the BST + * + In the worst case, we traverse to to the right-most bottom of the RBT * - * @param start The root BinaryNode to begin checking values - * @return A pointer to the BinaryNode which contains the largest value - * + returns nullptr if BST is empty + * @param start The root RedBlackNode to begin checking values + * @return A pointer to the RedBlackNode which contains the largest value + * + returns nullptr if RBT is empty */ -BinarySearchTree::BinaryNode * BinarySearchTree::findMax(BinaryNode *start) const +RedBlackTree::RedBlackNode * RedBlackTree::findMax(RedBlackNode *start) const { // If our tree is empty - if (start == nullptr) return nullptr; + if (start == nil) return nullptr; // Base case: If current node has no larger children, it is max; Break recursion - if (start->right == nullptr) return start; + if (start->right == nil) return start; // Move down the right side of our tree and check again return findMax(start->right); @@ -319,19 +692,19 @@ BinarySearchTree::BinaryNode * BinarySearchTree::findMax(BinaryNode *start) cons * @brief Finds the previous value in-order from the value at a given startNode * * Runs in O( height ) time - * + In the worst case we traverse to the bottom of the BST + * + In the worst case we traverse to the bottom of the RBT * * @param startNode The node containing the value to find predecessor of * @return The node which is the predecessor of startNode */ -BinarySearchTree::BinaryNode * BinarySearchTree::predecessor(BinaryNode *startNode) const +RedBlackTree::RedBlackNode * RedBlackTree::predecessor(RedBlackNode *startNode) const { - if (startNode->left != nullptr) return findMax(startNode->left); + if (startNode->left != nil) return findMax(startNode->left); // If startNode has a parent, walk up the tree until we reach the top // + If startNode has no parent, we set it to nullptr and return - BinaryNode *temp = startNode->parent; - while (temp != nullptr && temp->left == startNode) { + RedBlackNode *temp = startNode->parent; + while (temp != nil && temp->left == startNode) { startNode = temp; temp = temp->parent; } @@ -342,20 +715,20 @@ BinarySearchTree::BinaryNode * BinarySearchTree::predecessor(BinaryNode *startNo * @brief Finds the next value in-order from the value at a given startNode * * Runs in O( height ) time - * + In the worst case we traverse to the bottom of the BST + * + In the worst case we traverse to the bottom of the RBT * * @param startNode The node containing the value to find successor of * @return The node which is the successor of startNode */ -BinarySearchTree::BinaryNode * BinarySearchTree::successor(BinaryNode *startNode) const +RedBlackTree::RedBlackNode * RedBlackTree::successor(RedBlackNode *startNode) const { // If there is a right subtree, next value in-order is findMin(rightSubtree) - if (startNode->right != nullptr) return findMin(startNode->right); + if (startNode->right != nil) return findMin(startNode->right); // If startNode has a parent, walk up the tree until we reach the top // + If startNode has no parent, we set it to nullptr and return - BinaryNode *temp = startNode->parent; - while (temp != nullptr && temp->right == startNode) { + RedBlackNode *temp = startNode->parent; + while (temp != nil && temp->right == startNode) { startNode = temp; temp = temp->parent; } @@ -368,20 +741,22 @@ BinarySearchTree::BinaryNode * BinarySearchTree::successor(BinaryNode *startNode *******************************************************************************/ /** clone - * @brief Clone a BST node and all its children using recursion + * @brief Clone a RBT node and all its children using recursion * * Runs in O( n ) time, since each node must be copied individually * * @param start The node to begin cloning from - * @return A pointer to the BinaryNode which is root node of the copied tree + * @return A pointer to the RedBlackNode which is root node of the copied tree */ -BinarySearchTree::BinaryNode * BinarySearchTree::clone(BinaryNode *start) +RedBlackTree::RedBlackNode * RedBlackTree::clone(RedBlackNode *start) { + if (root == nil) root = new RedBlackNode(); + // Base case: There is nothing to copy, break from recursion - if (start == nullptr) return nullptr; + if (start == nil) return nil; // Construct all child nodes through recursion, return root node - return new BinaryNode(start); + return new RedBlackNode(start); } /** transplant @@ -394,12 +769,12 @@ BinarySearchTree::BinaryNode * BinarySearchTree::clone(BinaryNode *start) * @param oldNode The node to overwrite with newNode * @param newNode The new node to take the place of oldNode */ -void BinarySearchTree::transplant(BinaryNode *oldNode, BinaryNode *newNode) +void RedBlackTree::transplant(RedBlackNode *oldNode, RedBlackNode *newNode) { // case 1: If oldNode is the root node at this->root // + 2: if the oldNode is the left child of it's parent // + 3: case if the oldNode is the right child of it's parent - if (oldNode->parent == nullptr) root = newNode; + if (oldNode->parent == nil) root = newNode; else if (oldNode == oldNode->parent->left) { // Update the parent of oldNode to reflect the transplant oldNode->parent->left = newNode; @@ -410,5 +785,5 @@ void BinarySearchTree::transplant(BinaryNode *oldNode, BinaryNode *newNode) } // If we did not replace oldNode with a nullptr, update newNode's parent - if (newNode != nullptr) newNode->parent = oldNode->parent; + newNode->parent = oldNode->parent; } diff --git a/cpp/algorithms/trees/redblack/redblack.h b/cpp/algorithms/trees/redblack/redblack.h index e21048c..f251752 100644 --- a/cpp/algorithms/trees/redblack/redblack.h +++ b/cpp/algorithms/trees/redblack/redblack.h @@ -13,71 +13,88 @@ #include -// TODO: Add balance() method to balance overweight branches -class BinarySearchTree { +enum Color {Black, Red}; + +class RedBlackTree { public: - // BinaryNode Structure - struct BinaryNode{ + // RedBlackNode Structure + struct RedBlackNode{ int element; - BinaryNode *left, *right, *parent; + Color color = Black; + RedBlackNode *left{}, *right{}, *parent{}; + RedBlackNode() : element(INT32_MIN), color(Black) {} // Ctor for specific element, lhs, rhs - BinaryNode(const int &el, BinaryNode *lt, BinaryNode *rt, BinaryNode *p) - :element(el), left(lt), right(rt), parent(p) {}; - // Ctor for a node and any downstream nodes - explicit BinaryNode(BinaryNode * toCopy); + RedBlackNode(const int &el, Color c, + RedBlackNode *lt, RedBlackNode *rt, RedBlackNode *p) + :element(el), color(c), left(lt), right(rt), parent(p) {}; + // Ctor for copying a node and any downstream nodes + explicit RedBlackNode(RedBlackNode * toCopy); }; + static RedBlackNode *nil; - BinarySearchTree() : root(nullptr) {}; - BinarySearchTree(const BinarySearchTree &rhs) : root(rhs.clone(rhs.root)) {}; - BinarySearchTree& operator=(const BinarySearchTree& rhs); - ~BinarySearchTree() { makeEmpty(root);}; - inline BinaryNode * getRoot() const { return root;} + RedBlackTree() : root(nil) {}; + RedBlackTree(const RedBlackTree &rhs);; + RedBlackTree& operator=(const RedBlackTree& rhs); + ~RedBlackTree() { makeEmpty(root);}; + // Inlined functions provide less verbose interface for using the RBT + inline RedBlackNode * getRoot() const { return root;} + + void rotateLeft(RedBlackNode *pivotNode); + void rotateRight(RedBlackNode *pivotNode); + void insertFixup(RedBlackNode * start); + void deleteFixup(RedBlackNode * start); // Check if value is within the tree or subtree inline bool contains(const int &value) const { return contains(value, root);} - bool contains(const int &value, BinaryNode *start) const; + bool contains(const int &value, RedBlackNode *start) const; // Empties a given tree or subtree inline void makeEmpty() { makeEmpty(root);} - void makeEmpty(BinaryNode *&tree); - // Checks if this BST is empty + void makeEmpty(RedBlackNode *&tree); + // Checks if this RBT is empty bool isEmpty() const; // Insert and remove values from a tree or subtree - inline void insert(const int &x) { insert(x, root, nullptr);} - void insert(const int &newValue, BinaryNode *&start, BinaryNode *prevNode); + inline void insert(const int &x) { insert(x, root, nil);} + void insert(const int &newValue, RedBlackNode *&start, RedBlackNode *prevNode); inline void remove(const int &x) { remove(search(x, root));} - void remove(BinaryNode *removeNode); + void remove(RedBlackNode *removeNode); // Traversal functions inline void printInOrder() const { printInOrder(root);} inline void printPostOrder() const { printPostOrder(root);} inline void printPreOrder() const { printPreOrder(root);} // Overloaded to specify traversal of a subtree - void printInOrder(BinaryNode *start) const; - void printPostOrder(BinaryNode *start) const; - void printPreOrder(BinaryNode *start) const; + void printInOrder(RedBlackNode *start) const; + void printPostOrder(RedBlackNode *start) const; + void printPreOrder(RedBlackNode *start) const; // Find a BinaryNode containing value starting at a given tree / subtree node - inline BinaryNode * search(const int &value) const { return search(value, root);} - BinaryNode * search(const int &value, BinaryNode *start) const; - inline BinaryNode * findMin() const { return findMin(root);} - inline BinaryNode * findMax() const { return findMax(root);} - // Find nodes with min / max values starting at a given tree / subtree node - BinaryNode * findMin(BinaryNode *start) const; - BinaryNode * findMax(BinaryNode *start) const; + inline RedBlackNode * search(const int &value) const + { return search(value, root);} + RedBlackNode * search(const int &value, RedBlackNode *start) const; - BinaryNode * predecessor(BinaryNode *startNode) const; - BinaryNode * successor(BinaryNode *startNode) const; + inline RedBlackNode * findMin() const { return findMin(root);} + inline RedBlackNode * findMax() const { return findMax(root);} + // Find nodes with min / max values starting at a given tree / subtree node + RedBlackNode * findMin(RedBlackNode *start) const; + RedBlackNode * findMax(RedBlackNode *start) const; + + inline RedBlackNode * predecessor(const int &value) const + { return predecessor(search(value));} + RedBlackNode * predecessor(RedBlackNode *startNode) const; + inline RedBlackNode * successor(const int &value) const + { return successor(search(value));} + RedBlackNode * successor(RedBlackNode *startNode) const; private: - // BST Private Member Functions - static BinaryNode * clone(BinaryNode *start); - void transplant(BinaryNode *oldNode, BinaryNode *newNode); + RedBlackNode * clone(RedBlackNode *start); + void transplant(RedBlackNode *oldNode, RedBlackNode *newNode); - BinaryNode *root; + // The root node for the RBT + RedBlackNode *root; }; #endif // REDBLACK_H