I just created a BinarySeachTree class in c++ which is supposed to store words read from a txt and the number of occurances of those words and i would just like some feedback from you. I think it works correctly and i used valgrind to check if it creates any memory leaks (and it doesn't). I am just worried for some things like the two definitions of some functions, as i am not sure if it is a good practice the way i implemented it. There are a lot of comments in the code that i hope help make the code more understandable. Also i am required to do a technical report for my code and i am not sure how to go on about this, so any comments on this will be greatly appreciated.
Some questions I have are:
-Will the node class be inherited by the subclasses of the BinarySearchTree?
-Should the Node class have a destructor?
-I have to create the AVLTree class which inherits from BinarySearchTree and for the Insert function i will have to keep track of the height variable of Node. Would it be considered sloppy to just copy and paste the Insert function and just add some commands to keep track of height? If so, how could i go on about this?
Thank you for your time.
Header:
#ifndef BINARYSEARCHTREE_H
#define BINARYSEARCHTREE_H
#include <string>
#include <iostream>
using namespace std;
class BinarySearchTree
{
public:
BinarySearchTree(); //constructor which creates an empty tree with a NULL root pointer
~BinarySearchTree(); //recursively deletes tree using postorder traversal by calling the Clear() method
void Insert(string); //insert method is used to insert and also count occurences if while searching the tree we find that the given string already exists
bool Search(string); //1st definition of search: this method will be used by main to search and display the contents of a node whose word is equal to the string
bool Delete(string); //1st definition: deletes the node with the given string as its word, if it exists, by calling the 2nd Delete() definition and is only used by main
void Preorder() {Preorder(root);} //For the traversal methods we use two definitions of each method, one that is called by main and one by the class,
void Inorder() {Inorder(root);} //as main does not have access to the root
void Postorder() {Postorder(root);}
protected:
//Node class to be used only by BinarySearchTree and its children
class Node
{
public:
Node(string s) {word = s; occurences = 1;right = left = NULL;} //Node's constructor initialises node's values
void print() {cout<<"Word: "<<word<<" Occurences: "<<occurences<<endl;}
//Node's variables are set to public because Node will only be used inside BinarySearchTree and its chidlren
int occurences;
string word;
Node* right;
Node* left;
int height; //this variable will be used only by BinarySearchTree's subclass AVLTree
};
Node* root;
//methods only accessible by BinarySearchTree and its children
bool Search(string, Node*&, Node*&); //2nd definition of search: this method will only be used by the class and its children to search for a node to be deleted and finds both the node and its parent
void Preorder(Node*);
void Inorder(Node*);
void Postorder(Node*);
void Clear(Node*);
void FindMinOfRight(Node*, Node*&, Node*&); //this method will be used to delete a node that has two subtrees and it finds the minimum node (and its parent) of the right subtree of the node passed to it
bool Delete(Node*, Node*);//2nd definition: first node is the child to be deleted and the second node is the parent
private:
};
#endif // BINARYSEARCHTREE_H
Implementation:
#include "BinarySearchTree.h"
BinarySearchTree::BinarySearchTree()
{
root = NULL;
}
BinarySearchTree::~BinarySearchTree()
{
Clear(root);
}
void BinarySearchTree::FindMinOfRight(Node* n, Node* &m, Node* &pm)
{
Node* prev_n = n;
n = n->right;
while(n->left)
{
prev_n = n;
n = n->left;
}
m = n; //min
pm = prev_n; //parent min
}
void BinarySearchTree::Clear(Node* n)
{
if(!n)
return;
Clear(n->left);
Clear(n->right);
delete n;
}
void BinarySearchTree::Insert(string s)
{
if(root) //on the first insertion the new node is the root
{
Node* n = root;
Node* prev_n = NULL;
while(n)
{
prev_n = n; //pointer variable to keep track of the parent node once n becomes NULL
if(s > n->word) //if s is bigger than the current node's word we go to the right subtree
n = n->right;
else
{
if(s < n->word) //if s is smaller than current node's word we go to the left subtree
n = n->left;
else //if s already exists in a node we just increment occurances by 1 and return
{
n->occurences++;
return;
}
}
}
if(s > prev_n->word) //we create a new node in the place of the null pointer
{
prev_n->right = new Node(s);
if(!prev_n->right) //we check if the memory allocation succeded
cout<<"Memory allocation failed"<<endl;
}
else
{
prev_n->left = new Node(s);
if(!prev_n->left)
cout<<"Memory allocation failed"<<endl;
}
}
else
{
root = new Node(s);
if(!root)
cout<<"Memory allocation failed"<<endl;
}
}
bool BinarySearchTree::Search(string s)
{
Node* n = root;
while(n)
{
if(s > n->word) //if s is bigger than the node's word we search the right subtree of the node
n = n->right;
else
{
if(s < n->word) //if s is smaller than the node's word we search the left subtree of the node
n = n->left;
else //else the search was succesful
{
cout<<"Word: "<<n->word<<" Occurences: "<<n->occurences<<endl;
return true;
}
}
}
//if we find a null pointer that means that we have reached a leaf of the tree and that s is not in it
return false;
}
bool BinarySearchTree::Search(string s, Node* &c, Node* &p)
{
Node* n = root;
Node* prev_n = NULL;
while(n)
{
if(s > n->word)
{
prev_n = n;
n = n->right;
}
else
{
if(s < n->word)
{
prev_n = n;
n = n->left;
}
else
{
c = n; //we use identical code to the 1st definition of search with the exception that instead of printing the node's content we store the node's and the node's parent's adress in the parameters
p = prev_n;
return true;
}
}
}
return false;
}
bool BinarySearchTree::Delete(string s)
{
Node* c = NULL;
Node* p = NULL;
if(!(Search(s, c, p)))
return false;
return Delete(c, p);
}
bool BinarySearchTree::Delete(Node* c, Node* p)
{
bool flag = false; //we use this flag to determine whether the node to be deleted is the right(true) or the left(false) child of its parent
if(p && p->right == c)
flag = true;
//case 1: node to be deleted is a leaf
if(!c->right && !c->left)
{
if(p) //if there is no parent we are deleting the root
{
if(flag) //we set the pointer of the parent (left or right) that points to the node to be deleted to NULL then we delete the node
p->right = NULL;
else
p->left = NULL;
delete c;
}
else
{
delete root;
root = NULL;
}
return true;
}
//case 2: node to be deleted has only one subtree
if(c->right && !c->left)
{
if(p) //again we check to see if the node to be deleted is the root
{
if(flag) //we check if the node to be deleted is the right or the left child of its parent so we can bypass it and delete it
p->right = c->right;
else
p->left = c->right;
delete c;
}
else
{
Node* temp = root->right;
delete root;
root = temp;
}
return true;
}
if(!c->right && c->left)
{
if(p)
{
if(flag)
p->right = c->left;
else
p->left = c->left;
delete c;
}
else
{
Node* temp = root->left;
delete root;
root = temp;
}
return true;
}
//case 3: node to be deleted has two subtrees
Node* min_right;
Node* pmin_right;
Node* temp;
if(p)
{
FindMinOfRight(c, min_right, pmin_right);
temp = new Node(min_right->word); //we store the contents of the min_right node that will take the place of the node to be deleted in a temp node
temp->occurences = min_right->occurences;
Delete(min_right, pmin_right); //we first delete the min_right node because there is a chance the node to be deleted and the parent of the min_right are the same node
// this way we make sure the every node's chidlren will be correct
//also this call of Delete() will fall one one of the first two cases as min_right will either be a leaf or have a right subtree
temp->right = c->right; //then we assign to the temp node the children of the node to be deleted (as they should be after min_right is deleted)
temp->left = c->left;
delete c;
if(flag) //finally we replace the node to be deleted with the min_right node
p->right = temp;
else
p->left = temp;
return true;
}
else //in this case we want to delete the root so we work as we did above with the exception that the root has no parent
{
FindMinOfRight(root, min_right, pmin_right);
temp = new Node(min_right->word);
temp->occurences = min_right->occurences;
Delete(min_right, pmin_right);
temp->right = root->right;
temp->left = root->left;
delete root;
root = temp;
return true;
}
return false; //in case something goes wrong
}
void BinarySearchTree::Preorder(Node* n)
{
if(!n)
return;
n->print();
Preorder(n->left);
Preorder(n->right);
}
void BinarySearchTree::Inorder(Node* n)
{
if(!n)
return;
Inorder(n->left);
n->print();
Inorder(n->right);
}
void BinarySearchTree::Postorder(Node* n)
{
if(!n)
return;
Postorder(n->left);
Postorder(n->right);
n->print();
}