I am learning algorithms and trying to implement them in c++, I have chosen to try to implement a red-black tree due to its self-balancing properties and its ability to stop the worst case of O(n) when searching for a value. I also needed to allow duplicates to have a vector to hold duplicates in node objects, and a key to hold the main value the node holds. Can someone please review my code and suggest and improvements? if I have even implemented a red-black tree correctly (I have no need for deletion operations). I have also been thinking if I should change my Node* to std::shared_ptr, would this be a good idea? or should I keep them as raw pointers.
#define _CRT_SECURE_NO_WARNINGS #include <algorithm> #include <cctype> #include <iostream> #include <memory> #include <string> #include <vector> #include <ctime> using namespace std; // CHANGE THIS // Complex type used for the BST class Transaction { private: std::string desc; time_t timestamp; std::string value; bool isWithdrawal; public: explicit Transaction(std::string value, std::string reason = "None.") : desc(reason), timestamp(time(nullptr)), value(std::move(value)) { // timestamp is current date/time based on current // system // Lambda to convert reason to lower to we can identify elements easier std::transform(reason.begin(), reason.end(), reason.begin(), [](const unsigned char c) { return std::tolower(c); }); // Check if reason is a withdrawal if so update the member function this->isWithdrawal = reason.find("withdrawal") != std::string::npos; } std::string toString() const { // convert timestamp to string form const char* string_timestamp = ctime(×tamp); if (this->isWithdrawal) { return "-- " + desc + ": -\x9C" + value + " on " + string_timestamp; } return "-- " + desc + ": \x9C" + value + " on " + string_timestamp; } // Getters std::string getValue() const { return this->value; } double getValueNum() const { return std::stod(this->value); } std::string getDesc() const { return this->desc; } time_t getTimestamp() const { return this->timestamp; } bool getIsWithdrawal() const { return this->isWithdrawal; } friend std::ostream& operator<<(std::ostream& os, const Transaction& transaction); }; std::ostream& operator<<(std::ostream& os, const Transaction& transaction) { os << transaction.toString(); return os; } // class RBTree implements the operations in Red Black Tree class RBTree { private: // data structure that represents a node in the tree struct Node { double key = 0.0; std::vector<std::shared_ptr<Transaction>> data; // holds the key Node* parent = nullptr; // pointer to the parent Node* left = nullptr; // pointer to left child Node* right = nullptr; // pointer to right child int color = -1; // 1 -> Red, 0 -> Black std::string toString() const { std::string result; for (const auto& item : data) { result += item->toString() + "\n"; } return result; } }; Node* root; Node* TNULL; // initializes the nodes with appropriate values // all the pointers are set to point to the null pointer void initializeNULLNode(Node* node, Node* parent) { node->key = 0.0; node->parent = parent; node->left = nullptr; node->right = nullptr; node->color = 0; } Node* searchTreeHelper(Node* node, const double key) { if (node == TNULL || key == node->key) { return node; } if (key < node->key) { return searchTreeHelper(node->left, key); } return searchTreeHelper(node->right, key); } // Traverse through tree and print all data in order void inOrderTraversal(Node* node) const { if (node != TNULL) { inOrderTraversal(node->left); for(const auto& item : node->data){ std::cout << *item; } inOrderTraversal(node->right); } } void rbTransplant(Node* u, Node* v) { if (u->parent == nullptr) { root = v; } else if (u == u->parent->left) { u->parent->left = v; } else { u->parent->right = v; } v->parent = u->parent; } // fix the red-black tree void fixInsert(Node* k) { Node* u; while (k->parent->color == 1) { if (k->parent == k->parent->parent->right) { u = k->parent->parent->left; // uncle if (u->color == 1) { // case 3.1 u->color = 0; k->parent->color = 0; k->parent->parent->color = 1; k = k->parent->parent; } else { if (k == k->parent->left) { // case 3.2.2 k = k->parent; rightRotate(k); } // case 3.2.1 k->parent->color = 0; k->parent->parent->color = 1; leftRotate(k->parent->parent); } } else { u = k->parent->parent->right; // uncle if (u->color == 1) { // mirror case 3.1 u->color = 0; k->parent->color = 0; k->parent->parent->color = 1; k = k->parent->parent; } else { if (k == k->parent->right) { // mirror case 3.2.2 k = k->parent; leftRotate(k); } // mirror case 3.2.1 k->parent->color = 0; k->parent->parent->color = 1; rightRotate(k->parent->parent); } } if (k == root) { break; } } root->color = 0; } public: RBTree() : TNULL(new Node) { TNULL->color = 0; TNULL->left = nullptr; TNULL->right = nullptr; root = TNULL; } // search the tree for the key k // and return the corresponding node Node* searchTree(const double k) { return searchTreeHelper(this->root, k); } // find the node with the minimum key Node* minimum(Node* node) const { while (node->left != TNULL) { node = node->left; } return node; } // find the node with the maximum key Node* maximum(Node* node) const { while (node->right != TNULL) { node = node->right; } return node; } // find the successor of a given node Node* successor(Node* x) const { // if the right subtree is not null, // the successor is the leftmost node in the // right subtree if (x->right != TNULL) { return minimum(x->right); } // else it is the lowest ancestor of x whose // left child is also an ancestor of x. Node* y = x->parent; while (y != TNULL && x == y->right) { x = y; y = y->parent; } return y; } // find the predecessor of a given node Node* predecessor(Node* x) const { // if the left subtree is not null, // the predecessor is the rightmost node in the // left subtree if (x->left != TNULL) { return maximum(x->left); } Node* y = x->parent; while (y != TNULL && x == y->left) { x = y; y = y->parent; } return y; } // rotate left at node x void leftRotate(Node* x) { Node* y = x->right; x->right = y->left; if (y->left != TNULL) { y->left->parent = x; } y->parent = x->parent; if (x->parent == nullptr) { this->root = y; } else if (x == x->parent->left) { x->parent->left = y; } else { x->parent->right = y; } y->left = x; x->parent = y; } // rotate right at node x void rightRotate(Node* x) { Node* y = x->left; x->left = y->right; if (y->right != TNULL) { y->right->parent = x; } y->parent = x->parent; if (x->parent == nullptr) { this->root = y; } else if (x == x->parent->right) { x->parent->right = y; } else { x->parent->left = y; } y->right = x; x->parent = y; } // insert the key to the tree in its appropriate position // and fix the tree void insert(const std::shared_ptr<Transaction>& key) { // Ordinary Binary Search Insertion Node* node = new Node; node->key = key->getValueNum(); node->parent = nullptr; node->data.push_back(key); node->left = TNULL; node->right = TNULL; node->color = 1; // new node must be red Node* y = nullptr; Node* x = this->root; while (x != TNULL) { y = x; if (node->key == x->key) { // Duplicate insert x->data.emplace_back(key); delete node; // Delete node as we don't need it, due to duplicate data return; } if (node->key < x->key) { x = x->left; } else { x = x->right; } } // y is parent of x node->parent = y; if (y == nullptr) { root = node; } else if (node->key < y->key) { y->left = node; } else { y->right = node; } // if new node is a root node, simply return if (node->parent == nullptr) { node->color = 0; return; } // if the grandparent is null, simply return if (node->parent->parent == nullptr) { return; } // Fix the tree fixInsert(node); } Node* getRoot() const { return this->root; } void printAllNodesInOrder() const { inOrderTraversal(getRoot()); } }; // TO DO: // Try convert Node* to shared ptr // cleanup int main() { RBTree bst; bst.insert(std::make_shared<Transaction>("1500", "Deposit")); bst.insert(std::make_shared<Transaction>("1500", "Deposit 2")); bst.insert(std::make_shared<Transaction>("500.50", "Deposit")); bst.insert(std::make_shared<Transaction>("2000.69", "Deposit")); std::cout << bst.searchTree(1500)->toString(); bst.printAllNodesInOrder(); return 0; } ```
RBTree
already features a lot of code. Do you know leaning RB trees (Andersson/Sedgewick, making the case for short code)?)\$\endgroup\$