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\$