I have implemented a binary search tree with 2 operations to begin with. I have tried to implement it using the best practices I could think of. Overall, I am happy with the implementation. But, I want someone else's opinion about what they think about the design or any bad practice I have used.
I wanted to get rid of getLeftRef() and getRightRef() functions in Node class but they are being used in insert_ and delete_ function as parameters for the recursive call. Because in the insert_ and delete_ function we are reseating the unique_ptr, I passed it by reference and according to isocpp guidelines, it is ok to pass by unique_ptr reference if it is being used to reseat the unique_ptr. It would be great to have someone's review of this bit.
P.S. I am using C++11 so I cannot use make_unique instead of using new to allocate unique_ptr.
//
// BST.hpp
// PracticeC++
//
#ifndef BST_hpp
#define BST_hpp
#include <memory>
// Binary Search Tree class
class BST
{
private:
// Binary Search Tree node
class Node
{
public:
Node(int val, Node* p) : data(val), parent(p), left(nullptr), right(nullptr) {}
bool isLeftChild() { return parent->left.get() == this; }
bool isRightChild() { return parent->right.get() == this; }
Node* getLeft() { return left.get(); }
Node* getRight() { return right.get(); }
Node* getParent() { return parent; }
std::unique_ptr<Node>& getLeftRef() { return left; }
std::unique_ptr<Node>& getRightRef() { return right; }
int getData() { return data; }
std::unique_ptr<Node> extract()
{ return isLeftChild() ? std::move(parent->left) : std::move(parent->right); }
std::unique_ptr<Node> extractLeft() { return std::move(left); }
std::unique_ptr<Node> extractRight() { return std::move(right); }
void setLeft(std::unique_ptr<Node> n)
{
if (!n)
return;
n->Reparent(this);
left = std::move(n);
}
void setRight(std::unique_ptr<Node> n)
{
if (!n)
return;
n->Reparent(this);
right = std::move(n);
}
void resetParent() { parent = nullptr; }
void setData(int val) { data = val; }
private:
// member variables
int data;
std::unique_ptr<Node> left;
std::unique_ptr<Node> right;
Node* parent;
void Reparent(Node* newParent) { parent = newParent; }
};
void insert_(std::unique_ptr<Node>& root, int val, Node* parent);
bool remove_(std::unique_ptr<Node>& root, int val, Node* parent);
std::unique_ptr<Node> extractInOrderPredOrSucc(Node* node, bool isPred);
Node* find(Node* n, int val);
Node* findInOrderPredecessor(Node* node);
Node* findInOrderSuccessor(Node* node);
// Member variables
std::unique_ptr<Node> root;
public:
void insert(int val) { insert_(root, val, nullptr); }
bool remove(int val) { return remove_(root, val, nullptr); }
};
#endif /* BST_hpp */
//
// BST.cpp
// PracticeC++
//
//
#include "BST.hpp"
BST::Node* BST::findInOrderPredecessor(BST::Node* node)
{
if (!node || !node->getLeft())
return nullptr;
node = node->getLeft();
while (node->getRight())
node = node->getRight();
return node;
}
BST::Node* BST::findInOrderSuccessor(BST::Node* node)
{
if (!node)
return nullptr;
else if (!node->getRight())
return node->getParent();
node = node->getLeft();
while (node->getRight())
node = node->getRight();
return node;
}
std::unique_ptr<BST::Node> BST::extractInOrderPredOrSucc(BST::Node* node, bool isPred)
{
if (isPred)
{
Node* n = findInOrderPredecessor(node);
if (!n)
return nullptr;
auto subtree = n->extract();
subtree->getParent()->setRight(subtree->extractLeft());
subtree->resetParent();
return subtree;
}
else
{
Node* n = findInOrderSuccessor(node);
if (!n)
return nullptr;
auto subtree = n->extract();
subtree->getParent()->setLeft(subtree->extractRight());
subtree->resetParent();
return subtree;
}
}
void BST::insert_(std::unique_ptr<Node>& node, int val, Node* parent)
{
if (!node)
{
node = std::unique_ptr<Node>(new Node(val, parent));
return;
}
return (val <= node->getData()
? insert_(node->getLeftRef(), val, node.get())
: insert_(node->getRightRef(), val, node.get()));
}
bool BST::remove_(std::unique_ptr<Node>& node, int val, Node* parent)
{
if (!node)
return false;
if (val == node->getData())
{
if (!node->getLeft())
{
if (!node->getRight())
node.reset();
else
node->setRight(node->extractRight());
}
else
{
if (!node->getRight())
node->setLeft(node->extractLeft());
else
{
// Both children exist, so replace with either in-order pred or succ
auto predNode = extractInOrderPredOrSucc(node.get(), true);
predNode->setLeft(node->extractLeft());
predNode->setRight(node->extractRight());
node->isLeftChild()
? node->getParent()->setLeft(std::move(predNode))
: node->getParent()->setRight(std::move(predNode));
}
}
return true;
}
return (val < node->getData()
? remove_(node->getLeftRef(), val, node.get())
: remove_(node->getRightRef(), val, node.get()));
}