I am studying generic binary search trees (BST) and AVL trees (AVL) on some notes that contain implementation pseudocodes. I am a bit puzzled about some details of their implementation.
The BST is based on the struct Node below
struct Node{
int key;
Node* parent;
Node* left;
Node* right;
//constructors
}
//methods
The AVL version is basically the same with a few fields more for balancing the tree (I'll call it AVLNode for clarity, but there's no such distinction on the notes):
struct AVLNode{
int key;
int height;
int size;
AVLNode* parent;
AVLNode* leftchild;
AVLNode* rightchild;
//constructors
}
//methods
A lot of operations are the same between the two trees and I can easily use templates in order to reuse them on both trees. However, consider the operation insert, which inserts a new node. The code for a BST is something like
//Insert node with key k in tree with root R
void insert(const int& k, Node* root){
Node* N=find(k, root); //finds where to insert the node
if (N->key>k)
N->leftchild=new Node(k,N); //inserts as a left child
else
N->rightchild=new Node(k,N); //inserts as a right child
}
Now, the point is that the insert operation of an AVL tree is basically the same. The pseudocode presented in the notes is as follows:
void avlInsert(int k, AVLNode* R){
insert(k,R); //same operations as for Nodes, shown above
AVLNode* N=find(x,R); //find node inserted (generic operation for BST)
rebalance(N); //perform balancing operations specific to AVL trees
}
I'm a bit puzzled at this point, I know that the above is just a pseudocode but I was wondering whether there is a way to reuse the operation insert already provided for Node. Using template specialization would just mean writing a different specialization insert<AVLNode> for AVLNode, so that's not what I'm referring to.
I think a way would be to define AVLNode as a child class of Node and then use something like
struct AVLNode : Node {
//implementation
}
void avlInsert(int k, AVLNode* R){
Node *root=R;
insert(k,root);
AVLNode* N=find(x,R);
rebalance(N);
}
but I'm not quite sure this would work and I don't know how to manage the pointers to parent and the childs (i.e. they must be pointers to Node inside Node and to AVLNode inside AVLNode).
Is there a way to avoid rewriting the same code?
You could use CRTP here. This would allow you to create the left right and parent nodes in the baseclass. For example consider something like this:
template<typename T>
struct BaseNode{
int key;
T* parent;
T* left;
T* right;
};
struct AVLNode : public BaseNode<AVLNode>{
int height;
int size;
AVLNode(const int&k, AVLNode*root){};
AVLNode(){};
};
struct Node : public BaseNode<Node>{
Node(const int&k, Node*root){};
Node(){};
};
template<typename T>
T* find(const int& k, T* root){return root;};
template<typename T>
void insert(const int& k, T* root){
T* N=find(k, root); //finds where to insert the node
if (N->key>k)
N->left=new T(k,N); //inserts as a left child
else
N->right=new T(k,N); //inserts as a right child
}
void test(){
AVLNode avl_root;
Node node_root;
insert(42, &avl_root);
insert(42, &node_root);
}
The downside is that the compiler will generate more code than necessary. Because it creates a new insert function for every type. This might not be a problem for you, but something worth considering. See godbolt for the generated code.
As an aside. Please please please please don't use raw pointers and new and delete. You'll be going to get so many memory leaks, especially if a pointer gets "lost" because its parent gets deleted. Consider using smart pointers like unique_ptr or shared_ptr
Related
I know there are many questions about that issue but nothing seems to work for me or it's too complex for me to understand.
So I have template Node
template <typename T>
class Node {
public:
T value;
Node* right;
Node* left;
Node(T value, Node<T>* right, Node<T>* left);
};
And Tree
template <typename T>
class Tree {
public:
Node<T>* root;
Tree();
~Tree() {}
void insert(T value);
Node<T>* search(T value, Node<T>* root) noexcept(false);
};
Now I want to create different Tree templates basing on what the user chose. Users can choose int, double, or string, these are the only options. I tried to use base class solution but my problem is that Tree uses type T in functions (and in Node) so I don't know how I should declare them in, let's call it BaseTree. Then I would be able to something like this:
BaseTree* tree;
tree = new Tree<int>();
I'm looking for a simple solution, I'm sort of beginner and it surprises me how this simple issue is so difficult for me to solve.
The solution is to templatize the generic functions also. Let's say you have something like this in mind:
void traverse(BaseTree const& tree) noexcept {
/* Logic here */
}
Instead, you do:
template<typename T>
void traverse(Tree<T> const& tree) noexcept {
/* Logic here */
}
This also helps that you can use Node<T> to refer to the node instead of fabricating BaseNode and whatnot again.
So, the is the problem statement where I am stuck and trying to seek help for it is the following: As a part of my assignment, we have been asked to print the levels of the all the non-leaf nodes of a Binary Search Tree. We have been the function's template:
int Print_Non_leaf_Level(BST *Root)
And below is the memory of an object of the following class acts as the node of a linked list representing Binary Search Tree of integers.
class BST{
private: int info;
BST *left;
BST *right;
public: BST () {} //NULL Constructor.
};
Following is what I tried to do, but I am not sure if my logic is correct.
int Print_Non_leaf_Level(BST *Root){
BST *temp;
int level=-1;
temp = Root;
if(temp!=null){
if(temp->left !=null || temp->right != null){
++level;
cout<<level<<'\n';
}
Print_Non_leaf_Level(temp->left);
Print_Non_leaf_Level(temp->right);
return level;
}
}
I am learning C++ and to do so, I'm implementing some common data structures.
I started implementing a Binary Search Tree and it went all right, so now I'm implementing a Red Black Tree because I want to work through extending the BST Nodes into RBT Nodes (by just adding a bool color to them), and here is when my problems come.
This is my Node definition:
// NODE (key, value) //
class Node {
public:
int key;
int value; // value is integer for simplicity
Node *parent;
Node *left;
Node *right;
Node();
Node(int key, int value);
Node *grandparent();
Node *uncle();
};
And then I have my RedBlackNode:
// RED BLACK NODE (node with color) //
class RedBlackNode : public Node {
public:
int key;
int value;
RedBlackNode *parent;
RedBlackNode *left;
RedBlackNode *right;
bool color;
RedBlackNode();
RedBlackNode(int key, int value);
RedBlackNode *grandparent();
RedBlackNode *uncle();
};
That looks exactly the same, except for the color, and because grandparent() and uncle() return a RedBlackNode and not a Node.
The same happens with my BinarySearchTree methods Insert(key, value) and Search(key), and I would like to know if I have to rewrite all the methods only because of that.
I've been thinking also in using templates, but that doesn't make sense to me, because a BST will only be formed by Nodes, and a RBT will be formed by colored Nodes.
Thanks for your time in advance.
Let's suppose we have a RedBlack-Tree implementation which consists of 2 classes:
Tree - holds the pointer to the Node *root of the tree and defines all operations over the tree (Insert, Delete, etc)
Node - a data storage, which holds pointers to Node *parent, Node *left, Node *right nodes and std::string key.
The Tree::Insert() has the following implementation:
void Tree::Insert(const std::string &key)
{
Node *z = new Node(key);
// adding node logic
}
Now the task: every node has to store the time of its creation.
Limitations: the base tree implementation should be modified as less as possible and should contain details of specific extensions (so it should know nothing about the creation time property).
My thoughts: extending NodeWithTime : Node and adding unsigned int creation_time property.
Where I'm in stuck: how would we instantiate the node now?
Any proposals?
PS: it's neither a homework or a job task - I'm just learning c++ and data structures.
It's relatively simple. First, the Node struct:
template<typename T> struct Node {
Node(T t) : value(std::move(t)), time(RightNow()) {}
T value;
TimeType time;
std::unique_ptr<Node> left;
std::unique_ptr<Node> right;
};
A quick helper make_unique:
template<typename T, typename... Args> std::unique_ptr<T> make_unique(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args...)));
}
template<typename T> void Tree<T>::Insert(T key) {
auto z = make_unique<Node<T>>(std::move(key));
// insert
}
First, I fixed your crappy new and delete and replaced it with smart pointers. Then I also made your tree a template because who needs a tree that can only do one type? Then I swapped out your const T& with a T so that it might live with move-only types.
Then I just added a Time field and called RightNow() in the constructor. The exact TimeType and RightNow() you use depends on your needs and what exactly you mean by "time of it's creation". Are we talking about "6th July, 2013"? Or a very-high-resolution clock? In any case, these "creation time" details do not impact the tree.
Edit: Wait, you want to have one tree type where only some of the nodes know the creation time? Or just to alter the tree so that all the nodes know the creation time? I did #2, but for #1, you could indeed simply inherit from Node. To wit,
template<typename T> struct Node {
Node(T t) : value(std::move(t)) {}
T value;
std::unique_ptr<Node> left;
std::unique_ptr<Node> right;
};
template<typename T> struct NodeWithTime : Node<T> {
TimeType time;
NodeWithTime(T t) : Node(std::move(t)), time(RightNow()) {}
};
template<typename T> void Tree<T>::insert(T t) {
std::unique_ptr<Node> nodeptr;
if (IWantToStoreCreationTime)
nodeptr = make_unique<NodeWithTime<T>>(std::move(t));
else
nodeptr = make_unique<Node>(std::move(t));
// insert
}
I am trying to write a node deletion for a binary tree. These are my node and tree structures:
class node{
public:
int value;
node* left;
node* right;
~node();
};
class tree{
public:
node* root;
....
};
And this is the function I wrote:
void tree::del(node** r, int x){
if(*r)
{
if((*r)->value==x)
{
if(!(*r)->left)
*r= (*r)->right;
else if(!(*r)->right)
*r= (*r)->left;
else
{
int k= delMax((*r)->left);
(*r)->value= k;
}
}
else if((*r)->value > x)
{
node* k= (*r)->left;
del(&k, x);
}
else
{
node* k= (*r)->right;
del(&k, x);
}
}}
My problem is that once I get to the desired element, the pointers change but then when the tree is rebuilt recursively it goes back to what it was originally and no element is deleted. I thought passing a pointer to the pointer would solve this but it didn't. delMax deletes the maximum element from the tree and it works correctly on its own.
Also, in the destructors for the last two classes, how should I place the deletes? because if I just put delete right; delete left; in ~node() and delete root in ~tree() I get an error that I'm corrupting the heap.
Thanks!
By making a local variable k and passing its address, the assignments through *r affect the local variable rather than any pointer in the tree.
As an aside, writing node *&r might save you several & and *s.