From f45e479603f0fd9708d1ef9d8f886f12c7932fda Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Tue, 8 Jun 2021 14:43:39 -0400 Subject: [PATCH] Initial commit for working on red-black tree algorithms --- cpp/algorithms/trees/redblack/CMakeLists.txt | 21 + cpp/algorithms/trees/redblack/driver.cpp | 96 +++++ cpp/algorithms/trees/redblack/redblack.cpp | 414 +++++++++++++++++++ cpp/algorithms/trees/redblack/redblack.h | 83 ++++ 4 files changed, 614 insertions(+) create mode 100644 cpp/algorithms/trees/redblack/CMakeLists.txt create mode 100644 cpp/algorithms/trees/redblack/driver.cpp create mode 100644 cpp/algorithms/trees/redblack/redblack.cpp create mode 100644 cpp/algorithms/trees/redblack/redblack.h diff --git a/cpp/algorithms/trees/redblack/CMakeLists.txt b/cpp/algorithms/trees/redblack/CMakeLists.txt new file mode 100644 index 0000000..14cf7c9 --- /dev/null +++ b/cpp/algorithms/trees/redblack/CMakeLists.txt @@ -0,0 +1,21 @@ +############################################################################### +## Author: Shaun Reed ## +## Legal: All Content (c) 2021 Shaun Reed, all rights reserved ## +## About: A basic CMakeLists configuration to test RBT implementation ## +## ## +## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ## +############################################################################## +# +cmake_minimum_required(VERSION 3.15) + +project ( + #[[NAME]] RedBlackTree + VERSION 1.0 + DESCRIPTION "A project for testing red-black tree algorithms" + LANGUAGES CXX +) + +add_library(lib-redblack "redblack.cpp") + +add_executable(test-redblack "driver.cpp") +target_link_libraries(test-redblack lib-redblack) diff --git a/cpp/algorithms/trees/redblack/driver.cpp b/cpp/algorithms/trees/redblack/driver.cpp new file mode 100644 index 0000000..6c9351f --- /dev/null +++ b/cpp/algorithms/trees/redblack/driver.cpp @@ -0,0 +1,96 @@ +/*############################################################################# +## Author: Shaun Reed ## +## Legal: All Content (c) 2021 Shaun Reed, all rights reserved ## +## About: Driver program to test RBT algorithms from MIT intro to algorithms ## +## ## +## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ## +############################################################################### +*/ + +#include "redblack.h" + +#include +#include + + +int main (const int argc, const char * argv[]) +{ + BinarySearchTree 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}; + + std::cout << "Building binary search tree with input: "; + for (const auto &value : inputValues) std::cout << value << ", "; + std::cout << std::endl; + + for (const auto &value : inputValues) testTree.insert(value); + + std::cout << "\nInorder traversal: \n"; + testTree.printInOrder(); + std::cout << std::endl; + + std::cout << "Postorder traversal: \n"; + testTree.printPostOrder(); + std::cout << std::endl; + + std::cout << "Preorder traversal: \n"; + testTree.printPreOrder(); + std::cout << std::endl; + + std::cout << "\nMinimum value: " << testTree.findMin()->element << std::endl; + std::cout << "Maximum value: " << testTree.findMax()->element << std::endl; + + // Test removing a node, printing the result in-order + std::cout << "\nRemoving root value...\n"; + testTree.remove(testTree.getRoot()->element); + // Can use inline function to remove a value directly for testing - +// testTree.remove(6); + + std::cout << "Inorder traversal: \n"; + testTree.printInOrder(); + std::cout << std::endl; + + // Test copy constructor + std::cout << "\nCloning testTree to testTree2...\n"; + BinarySearchTree 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; + testTree3 = testTree; + std::cout << "Inorder traversal of the cloned testTree3 tree: \n"; + testTree3.printInOrder(); + std::cout << std::endl; + + // Test emptying the BST + std::cout << "\nEmptying testTree...\n"; + testTree.makeEmpty(); + std::cout << "testTree isEmpty: " << (testTree.isEmpty() ? "true" : "false"); + std::cout << "\ntestTree2 isEmpty: " << (testTree2.isEmpty() ? "true" : "false"); + std::cout << "\ntestTree3 isEmpty: " << (testTree3.isEmpty() ? "true" : "false"); + + // Testing integrity of deep copy (cloned) data after deletion of testTree + std::cout << "\n\nTesting integrity of previously cloned testTree2...\n"; + std::cout << "Inorder traversal of the cloned testTree2: \n"; + testTree2.printInOrder(); + std::cout << std::endl; + std::cout << "Inorder traversal of the cloned testTree3: \n"; + 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 << "Checking if tree contains value of 600: "; + std::cout << (testTree2.contains(600) ? "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 << std::endl; + +} \ No newline at end of file diff --git a/cpp/algorithms/trees/redblack/redblack.cpp b/cpp/algorithms/trees/redblack/redblack.cpp new file mode 100644 index 0000000..30194dc --- /dev/null +++ b/cpp/algorithms/trees/redblack/redblack.cpp @@ -0,0 +1,414 @@ +/*############################################################################# +## Author: Shaun Reed ## +## Legal: All Content (c) 2021 Shaun Reed, all rights reserved ## +## About: An example of a red-black tree implementation ## +## The algorithms in this example are seen in MIT Intro to Algorithms ## +## ## +## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ## +############################################################################## +*/ + +#include "redblack.h" + + +/******************************************************************************* +* Constructors, Destructors, Operators +*******************************************************************************/ + +/** BinarySearchTree Copy Assignment Operator + * @brief Empty the calling object's root BinaryNode, and copy the rhs data + * + * 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 + * + * 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 + */ +BinarySearchTree& BinarySearchTree::operator=(const BinarySearchTree &rhs) +{ + // If the objects are already equal, do nothing + if (this == &rhs) return *this; + + // Empty this->root + makeEmpty(root); + // 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; + } +} + + +/******************************************************************************* +* Public Member Functions +*******************************************************************************/ + +/** contains + * @brief Determines if value exists within a BinaryNode 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 + * + * @param value The value to search for within the BST + * @param start The root BinaryNode 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 +{ + // If tree is empty + if (start == nullptr) 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 + else if (value > start->element) return contains(value, start->right); + else return true; +} + +/** makeEmpty + * @brief Recursively delete the given root BinaryNode 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 + */ +void BinarySearchTree::makeEmpty(BinarySearchTree::BinaryNode * & tree) +{ + // Base case: When all nodes have been deleted, tree is a nullptr + // + Breaks from recursion + if (tree != nullptr) { + makeEmpty(tree->left); + makeEmpty(tree->right); + delete tree; + tree = nullptr; + } +} + +/** isEmpty + * @brief Determine whether or not the calling BST 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 + */ +bool BinarySearchTree::isEmpty() const +{ + return root == nullptr; +} + +/** insert + * @brief Insert a value into the tree starting at a given BinaryNode + * + Uses recursion + * + * Runs in O( height ) time, since in the worst case we insert the node at the + * + bottom of the BST. + * + * @param newValue The value to be inserted + * @param start The BinaryNode to begin insertion + * @param prevNode The last checked BinaryNode + * + prevNode is used to initialize new node's parent + */ +void BinarySearchTree::insert(const int &newValue, + BinaryNode *&start, BinaryNode *prevNode) +{ + // Base case: We found a valid position which is empty for the newValue + if (start == nullptr) { + // Build a new node, place it at the current position + // + Breaks out of recursion + start = new BinaryNode(newValue, nullptr, nullptr, prevNode); + } + else if (newValue < start->element) insert(newValue, start->left, start); + else if (newValue > start->element) insert(newValue, start->right, start); + else return; +} + +/** remove + * @brief Removes a value from the BST of the given BinaryNode + * + * Runs in O( height ) time, where findMin() is the limiting function + * + remove() and transplant() otherwise run in constant time, without findMin() + * + * @param removeNode The BinaryNode to remove from the BST + */ +void BinarySearchTree::remove(BinaryNode *removeNode) +{ + if (removeNode->left == nullptr) { + // 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 + transplant(removeNode, removeNode->right); + } + else if (removeNode->right == nullptr) { + // removeNode has no right node; Replace removeNode with removeNode->right + // + removeNode->left exists, in this case + 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); + + // 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; + } + + // 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; + } +} + +/** 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 + * + * @param start The root BinaryNode to begin the 'In Order' output + */ +void BinarySearchTree::printInOrder(BinaryNode *start) const +{ + if(start != nullptr) { + printInOrder(start->left); + std::cout << start->element << " "; + printInOrder(start->right); + } +} + +/** printPostOrder + * @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 + * + * @param start The root BinaryNode to begin the 'Post Order' output + */ +void BinarySearchTree::printPostOrder(BinaryNode *start) const +{ + if (start != nullptr) { + printPostOrder(start->left); + printPostOrder(start->right); + std::cout << start->element << " "; + } +} + +/** printPreOrder + * @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 + * + * @param start The root BinaryNode to begin the 'Pre Order' output + */ +void BinarySearchTree::printPreOrder(BinaryNode *start) const +{ + if (start != nullptr) { + std::cout << start->element << " "; + printPreOrder(start->left); + printPreOrder(start->right); + } +} + +/** search + * @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 + * + * @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 + * + Returns nullptr if the node was not found + */ +BinarySearchTree::BinaryNode *BinarySearchTree::search( + const int &value, BinaryNode *start) const +{ + // Base case: If BST is empty, or holds the value we are searching for + // + Breaks out of recursion + if (start == nullptr || 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 + * + 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 + * + * @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 + */ +BinarySearchTree::BinaryNode * BinarySearchTree::findMin(BinaryNode *start) const +{ + // If our tree is empty + if (start == nullptr) return nullptr; + + while (start->left != nullptr) 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 + * + 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 + * + * @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 + */ +BinarySearchTree::BinaryNode * BinarySearchTree::findMax(BinaryNode *start) const +{ + // If our tree is empty + if (start == nullptr) return nullptr; + + // Base case: If current node has no larger children, it is max; Break recursion + if (start->right == nullptr) return start; + + // Move down the right side of our tree and check again + return findMax(start->right); +} + +/** predecessor + * @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 + * + * @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 +{ + if (startNode->left != nullptr) 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) { + startNode = temp; + temp = temp->parent; + } + return temp; +} + +/** successor + * @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 + * + * @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 +{ + // If there is a right subtree, next value in-order is findMin(rightSubtree) + if (startNode->right != nullptr) 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) { + startNode = temp; + temp = temp->parent; + } + return temp; +} + + +/******************************************************************************* +* Private Member Functions +*******************************************************************************/ + +/** clone + * @brief Clone a BST 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 + */ +BinarySearchTree::BinaryNode * BinarySearchTree::clone(BinaryNode *start) +{ + // Base case: There is nothing to copy, break from recursion + if (start == nullptr) return nullptr; + + // Construct all child nodes through recursion, return root node + return new BinaryNode(start); +} + +/** transplant + * @brief Replaces, or overwrites, a node and with a new node + * + The subtree attaches to oldNode is replace with that of newNode + * + * Runs in constant O( 1 ) time + * + We only need to check and update values immediately available + * + * @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) +{ + // 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; + else if (oldNode == oldNode->parent->left) { + // Update the parent of oldNode to reflect the transplant + oldNode->parent->left = newNode; + } + else if (oldNode == oldNode->parent->right) { + // Update the parent of oldNode to reflect the transplant + oldNode->parent->right = newNode; + } + + // If we did not replace oldNode with a nullptr, update newNode's parent + if (newNode != nullptr) newNode->parent = oldNode->parent; +} diff --git a/cpp/algorithms/trees/redblack/redblack.h b/cpp/algorithms/trees/redblack/redblack.h new file mode 100644 index 0000000..e21048c --- /dev/null +++ b/cpp/algorithms/trees/redblack/redblack.h @@ -0,0 +1,83 @@ +/*############################################################################# +## Author: Shaun Reed ## +## Legal: All Content (c) 2021 Shaun Reed, all rights reserved ## +## About: An example of a red black tree implementation ## +## The algorithms in this example are seen in MIT Intro to Algorithms ## +## ## +## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ## +############################################################################## +*/ + +#ifndef REDBLACK_H +#define REDBLACK_H + +#include + +// TODO: Add balance() method to balance overweight branches +class BinarySearchTree { + +public: + // BinaryNode Structure + struct BinaryNode{ + int element; + BinaryNode *left, *right, *parent; + + // 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); + }; + + 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;} + + // 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; + + // Empties a given tree or subtree + inline void makeEmpty() { makeEmpty(root);} + void makeEmpty(BinaryNode *&tree); + // Checks if this BST 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 remove(const int &x) { remove(search(x, root));} + void remove(BinaryNode *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; + + // 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; + + BinaryNode * predecessor(BinaryNode *startNode) const; + BinaryNode * successor(BinaryNode *startNode) const; + +private: + // BST Private Member Functions + static BinaryNode * clone(BinaryNode *start); + void transplant(BinaryNode *oldNode, BinaryNode *newNode); + + BinaryNode *root; +}; + +#endif // REDBLACK_H