Add examples of red-black tree algorithms
+ Using pseudocode examples from MIT Introduction to Algorithms
This commit is contained in:
parent
f45e479603
commit
202953de49
|
@ -15,12 +15,15 @@
|
||||||
|
|
||||||
int main (const int argc, const char * argv[])
|
int main (const int argc, const char * argv[])
|
||||||
{
|
{
|
||||||
BinarySearchTree testTree;
|
RedBlackTree testTree;
|
||||||
std::vector<int> inputValues {2, 6, 9, 1, 5, 8, 10};
|
// std::vector<int> inputValues {2, 6, 9, 1, 5, 8, 10};
|
||||||
// Alternative input values
|
// Alternative input values
|
||||||
// std::vector<int> 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<int> 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 << ", ";
|
for (const auto &value : inputValues) std::cout << value << ", ";
|
||||||
std::cout << std::endl;
|
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
|
// Test removing a node, printing the result in-order
|
||||||
std::cout << "\nRemoving root value...\n";
|
std::cout << "\nRemoving root value...\n";
|
||||||
testTree.remove(testTree.getRoot()->element);
|
testTree.remove(testTree.getRoot()->element);
|
||||||
|
testTree.remove(testTree.getRoot()->element);
|
||||||
// Can use inline function to remove a value directly for testing -
|
// Can use inline function to remove a value directly for testing -
|
||||||
// testTree.remove(6);
|
// testTree.remove(6);
|
||||||
|
|
||||||
|
@ -53,20 +57,20 @@ int main (const int argc, const char * argv[])
|
||||||
|
|
||||||
// Test copy constructor
|
// Test copy constructor
|
||||||
std::cout << "\nCloning testTree to testTree2...\n";
|
std::cout << "\nCloning testTree to testTree2...\n";
|
||||||
BinarySearchTree testTree2 = testTree;
|
RedBlackTree testTree2 = testTree;
|
||||||
std::cout << "Inorder traversal of the cloned testTree2: \n";
|
std::cout << "Inorder traversal of the cloned testTree2: \n";
|
||||||
testTree2.printInOrder();
|
testTree2.printInOrder();
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
|
|
||||||
// Test assignment operator
|
// Test assignment operator
|
||||||
std::cout << "\nCloning testTree to testTree3...\n";
|
std::cout << "\nCloning testTree to testTree3...\n";
|
||||||
BinarySearchTree testTree3;
|
RedBlackTree testTree3;
|
||||||
testTree3 = testTree;
|
testTree3 = testTree;
|
||||||
std::cout << "Inorder traversal of the cloned testTree3 tree: \n";
|
std::cout << "Inorder traversal of the cloned testTree3 tree: \n";
|
||||||
testTree3.printInOrder();
|
testTree3.printInOrder();
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
|
|
||||||
// Test emptying the BST
|
// Test emptying the RBT
|
||||||
std::cout << "\nEmptying testTree...\n";
|
std::cout << "\nEmptying testTree...\n";
|
||||||
testTree.makeEmpty();
|
testTree.makeEmpty();
|
||||||
std::cout << "testTree isEmpty: " << (testTree.isEmpty() ? "true" : "false");
|
std::cout << "testTree isEmpty: " << (testTree.isEmpty() ? "true" : "false");
|
||||||
|
@ -82,15 +86,15 @@ int main (const int argc, const char * argv[])
|
||||||
testTree3.printInOrder();
|
testTree3.printInOrder();
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
|
|
||||||
std::cout << "\nChecking if tree contains value of 6: ";
|
std::cout << "\nChecking if tree contains value of 7: ";
|
||||||
std::cout << (testTree2.contains(6) ? "true" : "false") << std::endl;
|
std::cout << (testTree2.contains(7) ? "true" : "false") << std::endl;
|
||||||
std::cout << "Checking if tree contains value of 600: ";
|
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: "
|
std::cout << "\nSuccessor of node with value 7: "
|
||||||
<< testTree2.successor(testTree2.search(6))->element;
|
<< testTree2.successor(testTree2.search(7))->element;
|
||||||
std::cout << "\nPredecessor of node with value 6: "
|
std::cout << "\nPredecessor of node with value 7: "
|
||||||
<< testTree2.predecessor(testTree2.search(6))->element;
|
<< testTree2.predecessor(testTree2.search(7))->element;
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,76 +15,94 @@
|
||||||
* Constructors, Destructors, Operators
|
* Constructors, Destructors, Operators
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
|
||||||
/** BinarySearchTree Copy Assignment Operator
|
// Use a static member for nil
|
||||||
* @brief Empty the calling object's root BinaryNode, and copy the rhs data
|
// + 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
|
* Runs in O( n ) time, since we visit each node in the RBT once
|
||||||
* + Where n is the total number of nodes within the BST
|
* + 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
|
* 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
|
* + 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
|
* @param rhs The RBT to copy, beginning from its root RedBlackNode
|
||||||
* @return BinarySearchTree The copied BinarySearchTree object
|
* @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 the objects are already equal, do nothing
|
||||||
if (this == &rhs) return *this;
|
if (this == &rhs) return *this;
|
||||||
|
|
||||||
// Empty this->root
|
// Empty this->root
|
||||||
makeEmpty(root);
|
makeEmpty(root);
|
||||||
|
|
||||||
|
// if (root == nil) root = new RedBlackNode();
|
||||||
// Copy rhs to this->root
|
// Copy rhs to this->root
|
||||||
root = clone(rhs.root);
|
root = clone(rhs.root);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* BinaryNode Copy Constructor
|
RedBlackTree::RedBlackTree(const RedBlackTree &rhs) {
|
||||||
*
|
root = clone(rhs.getRoot());
|
||||||
* 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
|
* Public Member Functions
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
|
||||||
/** contains
|
/** 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
|
* 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 BST
|
* + 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 value The value to search for within the RBT
|
||||||
* @param start The root BinaryNode to begin the search
|
* @param start The root RedBlackNode to begin the search
|
||||||
* @return true If the value is found within the root node or its children
|
* @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
|
* @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 tree is empty
|
||||||
if (start == nullptr) return false;
|
if (start == nil) return false;
|
||||||
// If x is smaller than our current value
|
// If x is smaller than our current value
|
||||||
else if (value < start->element) return contains(value, start->left);
|
else if (value < start->element) return contains(value, start->left);
|
||||||
// If x is larger than our current value, check the right node
|
// 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;
|
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
|
/** 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
|
* 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
|
* + 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
|
// Base case: When all nodes have been deleted, tree is a nullptr
|
||||||
// + Breaks from recursion
|
// + Breaks from recursion
|
||||||
if (tree != nullptr) {
|
if (tree != nil) {
|
||||||
makeEmpty(tree->left);
|
makeEmpty(tree->left);
|
||||||
makeEmpty(tree->right);
|
makeEmpty(tree->right);
|
||||||
delete tree;
|
delete tree;
|
||||||
tree = nullptr;
|
// Set each deleted node = nil, overwriting unused children with nullptr
|
||||||
|
tree = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** isEmpty
|
/** 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 )
|
* Runs in constant time, O( 1 )
|
||||||
*
|
*
|
||||||
* @return true If this->root node points to an empty tree (nullptr)
|
* @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
|
/** 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
|
* + Uses recursion
|
||||||
*
|
*
|
||||||
* Runs in O( height ) time, since in the worst case we insert the node at the
|
* Runs in O( lg(n) ) time, since the height of RBTs are <= lg(n)
|
||||||
* + bottom of the BST.
|
* + 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 newValue The value to be inserted
|
||||||
* @param start The BinaryNode to begin insertion
|
* @param start The RedBlackNode to begin insertion
|
||||||
* @param prevNode The last checked BinaryNode
|
* @param prevNode The last checked RedBlackNode
|
||||||
* + prevNode is used to initialize new node's parent
|
* + prevNode is used to initialize new node's parent
|
||||||
*/
|
*/
|
||||||
void BinarySearchTree::insert(const int &newValue,
|
void RedBlackTree::insert(const int &newValue,
|
||||||
BinaryNode *&start, BinaryNode *prevNode)
|
RedBlackNode *&start, RedBlackNode *prevNode)
|
||||||
{
|
{
|
||||||
// Base case: We found a valid position which is empty for the newValue
|
// 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
|
// Build a new node, place it at the current position
|
||||||
// + Breaks out of recursion
|
// + Breaks out of recursion after this code block finishes
|
||||||
start = new BinaryNode(newValue, nullptr, nullptr, prevNode);
|
|
||||||
|
// 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->left, start);
|
||||||
else if (newValue > start->element) insert(newValue, start->right, start);
|
else if (newValue > start->element) insert(newValue, start->right, start);
|
||||||
|
@ -152,62 +484,103 @@ void BinarySearchTree::insert(const int &newValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
/** remove
|
/** 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
|
* Runs in O( lg(n) ) time, since the height of RBTs are <= lg(n)
|
||||||
* + remove() and transplant() otherwise run in constant time, without findMin()
|
* + 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
|
// removeNode has no left node; Replace removeNode with removeNode->right
|
||||||
// + It doesn't matter if removeNode->right is nullptr or a valid node
|
// + 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
|
// + Since there is no left node, this is the only possible valid transplant
|
||||||
|
|
||||||
// Transplant the right node and its subtree over this node
|
// Transplant the right node and its subtree over this node
|
||||||
|
fixupNode = removeNode->right;
|
||||||
transplant(removeNode, 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 has no right node; Replace removeNode with removeNode->right
|
||||||
// + removeNode->left exists, in this case
|
// + removeNode->left exists, in this case
|
||||||
|
fixupNode = removeNode->left;
|
||||||
transplant(removeNode, removeNode->left);
|
transplant(removeNode, removeNode->left);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// removeNode has a right and left node, find the next value in-order
|
// The node to remove has both left and right child nodes
|
||||||
// + findMin(removeNode->right) returns the next largest value in the BST
|
// + We should find a replacement within the child with a larger subtree
|
||||||
BinaryNode *minNode = findMin(removeNode->right);
|
|
||||||
|
|
||||||
// If the next value in-order is not removeNode->right
|
// Compare the depth of subtrees for both children of removeNode...
|
||||||
if (minNode->parent != removeNode) {
|
// + Red child node indicates the equal or longer subtree
|
||||||
// replace minNode with the next largest attached value, minNode->right
|
// + Black child node indicates the depth of the child's subtree is smaller
|
||||||
transplant(minNode, minNode->right);
|
if (removeNode->left->color) {
|
||||||
// Set minNode->right to the node at removeNode->right
|
// + findMax(removeNode->right) returns the next smallest value in the RBT
|
||||||
// + Update the parent of removeNode->right accordingly in the next line
|
replacementNode = findMax(removeNode->left);
|
||||||
minNode->right = removeNode->right;
|
originalColor = replacementNode->color;
|
||||||
minNode->right->parent = minNode;
|
|
||||||
|
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
|
if (replacementNode->parent == removeNode) {
|
||||||
// + Update the minNode's left and parent nodes in the following lines
|
fixupNode->parent = replacementNode;
|
||||||
transplant(removeNode, minNode);
|
}
|
||||||
minNode->left = removeNode->left;
|
else {
|
||||||
minNode->left->parent = minNode;
|
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
|
/** printInOrder
|
||||||
* @brief Uses recursion to output left subtree, root node, then right subtrees
|
* @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
|
* 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);
|
printInOrder(start->left);
|
||||||
std::cout << start->element << " ";
|
std::cout << start->element << " ";
|
||||||
printInOrder(start->right);
|
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
|
* @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
|
* 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->left);
|
||||||
printPostOrder(start->right);
|
printPostOrder(start->right);
|
||||||
std::cout << start->element << " ";
|
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
|
* @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
|
* 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 << " ";
|
std::cout << start->element << " ";
|
||||||
printPreOrder(start->left);
|
printPreOrder(start->left);
|
||||||
printPreOrder(start->right);
|
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
|
* @brief Search for a given value within a tree or subtree using recursion
|
||||||
*
|
*
|
||||||
* Runs in O( height ) time
|
* 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 value The value to search for
|
||||||
* @param start The node to start the search from; Can be a subtree
|
* @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
|
* + Returns nullptr if the node was not found
|
||||||
*/
|
*/
|
||||||
BinarySearchTree::BinaryNode *BinarySearchTree::search(
|
RedBlackTree::RedBlackNode *RedBlackTree::search(
|
||||||
const int &value, BinaryNode *start) const
|
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
|
// + 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->right);
|
||||||
else if (start->element > value) return search(value, start->left);
|
else if (start->element > value) return search(value, start->left);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** findMin
|
/** 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
|
* + This example uses a while loop; findMax uses recursion
|
||||||
*
|
*
|
||||||
* Runs in O( height ) time
|
* 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
|
* @param start The root RedBlackNode to begin checking values
|
||||||
* @return A pointer to the BinaryNode which contains the smallest value
|
* @return A pointer to the RedBlackNode which contains the smallest value
|
||||||
* + Returns nullptr if BST is empty
|
* + 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 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
|
// If current node has no smaller children, it is min
|
||||||
return start;
|
return start;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** findMax
|
/** 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
|
* + This example uses recursion; findMin uses a while loop
|
||||||
* ++ Both functions can be implemented using a loop or recursion
|
* ++ Both functions can be implemented using a loop or recursion
|
||||||
*
|
*
|
||||||
* Runs in O( height ) time
|
* 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
|
* @param start The root RedBlackNode to begin checking values
|
||||||
* @return A pointer to the BinaryNode which contains the largest value
|
* @return A pointer to the RedBlackNode which contains the largest value
|
||||||
* + returns nullptr if BST is empty
|
* + 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 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
|
// 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
|
// Move down the right side of our tree and check again
|
||||||
return findMax(start->right);
|
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
|
* @brief Finds the previous value in-order from the value at a given startNode
|
||||||
*
|
*
|
||||||
* Runs in O( height ) time
|
* 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
|
* @param startNode The node containing the value to find predecessor of
|
||||||
* @return The node which is the predecessor of startNode
|
* @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 a parent, walk up the tree until we reach the top
|
||||||
// + If startNode has no parent, we set it to nullptr and return
|
// + If startNode has no parent, we set it to nullptr and return
|
||||||
BinaryNode *temp = startNode->parent;
|
RedBlackNode *temp = startNode->parent;
|
||||||
while (temp != nullptr && temp->left == startNode) {
|
while (temp != nil && temp->left == startNode) {
|
||||||
startNode = temp;
|
startNode = temp;
|
||||||
temp = temp->parent;
|
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
|
* @brief Finds the next value in-order from the value at a given startNode
|
||||||
*
|
*
|
||||||
* Runs in O( height ) time
|
* 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
|
* @param startNode The node containing the value to find successor of
|
||||||
* @return The node which is the successor of startNode
|
* @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 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 a parent, walk up the tree until we reach the top
|
||||||
// + If startNode has no parent, we set it to nullptr and return
|
// + If startNode has no parent, we set it to nullptr and return
|
||||||
BinaryNode *temp = startNode->parent;
|
RedBlackNode *temp = startNode->parent;
|
||||||
while (temp != nullptr && temp->right == startNode) {
|
while (temp != nil && temp->right == startNode) {
|
||||||
startNode = temp;
|
startNode = temp;
|
||||||
temp = temp->parent;
|
temp = temp->parent;
|
||||||
}
|
}
|
||||||
|
@ -368,20 +741,22 @@ BinarySearchTree::BinaryNode * BinarySearchTree::successor(BinaryNode *startNode
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
|
||||||
/** clone
|
/** 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
|
* Runs in O( n ) time, since each node must be copied individually
|
||||||
*
|
*
|
||||||
* @param start The node to begin cloning from
|
* @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
|
// 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
|
// Construct all child nodes through recursion, return root node
|
||||||
return new BinaryNode(start);
|
return new RedBlackNode(start);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** transplant
|
/** transplant
|
||||||
|
@ -394,12 +769,12 @@ BinarySearchTree::BinaryNode * BinarySearchTree::clone(BinaryNode *start)
|
||||||
* @param oldNode The node to overwrite with newNode
|
* @param oldNode The node to overwrite with newNode
|
||||||
* @param newNode The new node to take the place of oldNode
|
* @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
|
// case 1: If oldNode is the root node at this->root
|
||||||
// + 2: if the oldNode is the left child of it's parent
|
// + 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
|
// + 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) {
|
else if (oldNode == oldNode->parent->left) {
|
||||||
// Update the parent of oldNode to reflect the transplant
|
// Update the parent of oldNode to reflect the transplant
|
||||||
oldNode->parent->left = newNode;
|
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 we did not replace oldNode with a nullptr, update newNode's parent
|
||||||
if (newNode != nullptr) newNode->parent = oldNode->parent;
|
newNode->parent = oldNode->parent;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,71 +13,88 @@
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
// TODO: Add balance() method to balance overweight branches
|
enum Color {Black, Red};
|
||||||
class BinarySearchTree {
|
|
||||||
|
class RedBlackTree {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// BinaryNode Structure
|
// RedBlackNode Structure
|
||||||
struct BinaryNode{
|
struct RedBlackNode{
|
||||||
int element;
|
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
|
// Ctor for specific element, lhs, rhs
|
||||||
BinaryNode(const int &el, BinaryNode *lt, BinaryNode *rt, BinaryNode *p)
|
RedBlackNode(const int &el, Color c,
|
||||||
:element(el), left(lt), right(rt), parent(p) {};
|
RedBlackNode *lt, RedBlackNode *rt, RedBlackNode *p)
|
||||||
// Ctor for a node and any downstream nodes
|
:element(el), color(c), left(lt), right(rt), parent(p) {};
|
||||||
explicit BinaryNode(BinaryNode * toCopy);
|
// Ctor for copying a node and any downstream nodes
|
||||||
|
explicit RedBlackNode(RedBlackNode * toCopy);
|
||||||
};
|
};
|
||||||
|
static RedBlackNode *nil;
|
||||||
|
|
||||||
BinarySearchTree() : root(nullptr) {};
|
RedBlackTree() : root(nil) {};
|
||||||
BinarySearchTree(const BinarySearchTree &rhs) : root(rhs.clone(rhs.root)) {};
|
RedBlackTree(const RedBlackTree &rhs);;
|
||||||
BinarySearchTree& operator=(const BinarySearchTree& rhs);
|
RedBlackTree& operator=(const RedBlackTree& rhs);
|
||||||
~BinarySearchTree() { makeEmpty(root);};
|
~RedBlackTree() { makeEmpty(root);};
|
||||||
inline BinaryNode * getRoot() const { return 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
|
// Check if value is within the tree or subtree
|
||||||
inline bool contains(const int &value) const { return contains(value, root);}
|
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
|
// Empties a given tree or subtree
|
||||||
inline void makeEmpty() { makeEmpty(root);}
|
inline void makeEmpty() { makeEmpty(root);}
|
||||||
void makeEmpty(BinaryNode *&tree);
|
void makeEmpty(RedBlackNode *&tree);
|
||||||
// Checks if this BST is empty
|
// Checks if this RBT is empty
|
||||||
bool isEmpty() const;
|
bool isEmpty() const;
|
||||||
|
|
||||||
// Insert and remove values from a tree or subtree
|
// Insert and remove values from a tree or subtree
|
||||||
inline void insert(const int &x) { insert(x, root, nullptr);}
|
inline void insert(const int &x) { insert(x, root, nil);}
|
||||||
void insert(const int &newValue, BinaryNode *&start, BinaryNode *prevNode);
|
void insert(const int &newValue, RedBlackNode *&start, RedBlackNode *prevNode);
|
||||||
inline void remove(const int &x) { remove(search(x, root));}
|
inline void remove(const int &x) { remove(search(x, root));}
|
||||||
void remove(BinaryNode *removeNode);
|
void remove(RedBlackNode *removeNode);
|
||||||
|
|
||||||
// Traversal functions
|
// Traversal functions
|
||||||
inline void printInOrder() const { printInOrder(root);}
|
inline void printInOrder() const { printInOrder(root);}
|
||||||
inline void printPostOrder() const { printPostOrder(root);}
|
inline void printPostOrder() const { printPostOrder(root);}
|
||||||
inline void printPreOrder() const { printPreOrder(root);}
|
inline void printPreOrder() const { printPreOrder(root);}
|
||||||
// Overloaded to specify traversal of a subtree
|
// Overloaded to specify traversal of a subtree
|
||||||
void printInOrder(BinaryNode *start) const;
|
void printInOrder(RedBlackNode *start) const;
|
||||||
void printPostOrder(BinaryNode *start) const;
|
void printPostOrder(RedBlackNode *start) const;
|
||||||
void printPreOrder(BinaryNode *start) const;
|
void printPreOrder(RedBlackNode *start) const;
|
||||||
|
|
||||||
// Find a BinaryNode containing value starting at a given tree / subtree node
|
// Find a BinaryNode containing value starting at a given tree / subtree node
|
||||||
inline BinaryNode * search(const int &value) const { return search(value, root);}
|
inline RedBlackNode * search(const int &value) const
|
||||||
BinaryNode * search(const int &value, BinaryNode *start) const;
|
{ return search(value, root);}
|
||||||
inline BinaryNode * findMin() const { return findMin(root);}
|
RedBlackNode * search(const int &value, RedBlackNode *start) const;
|
||||||
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;
|
inline RedBlackNode * findMin() const { return findMin(root);}
|
||||||
BinaryNode * successor(BinaryNode *startNode) const;
|
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:
|
private:
|
||||||
// BST Private Member Functions
|
RedBlackNode * clone(RedBlackNode *start);
|
||||||
static BinaryNode * clone(BinaryNode *start);
|
void transplant(RedBlackNode *oldNode, RedBlackNode *newNode);
|
||||||
void transplant(BinaryNode *oldNode, BinaryNode *newNode);
|
|
||||||
|
|
||||||
BinaryNode *root;
|
// The root node for the RBT
|
||||||
|
RedBlackNode *root;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // REDBLACK_H
|
#endif // REDBLACK_H
|
||||||
|
|
Loading…
Reference in New Issue