this pointer (if necessary), or Visualizing the this Pointer otherwiseThe complete Tree template example consists of four files, but this section only presents two: Employee.h and Tree.h. You can find links to all the example files at the bottom of the page.
#include <iostream>
#include <string>
using namespace std;
class Employee
{
private:
string name;
string address;
public:
Employee(string n = "", string a = "") : name(n), address(a) {} // (a)
bool operator==(Employee& e) { return name == e.name; } // (b)
bool operator<(Employee& e) { return name < e.name; }
friend ostream& operator<<(ostream& out, Employee& me) // (c)
{
out << me.name << " " << me.address;
return out;
}
};
#include <iostream>
#include <iomanip>
using namespace std;
template <class T>
class Tree
{
private:
T data;
Tree<T>* left = nullptr; // (a)
Tree<T>* right = nullptr;
public:
~Tree();
T* insert(T key); // (b)
T* search(T key);
void remove(T key);
void list() { if (right != nullptr) right->_list(); } // (c)
void tree_view(int level = -1); // (d)
private:
void remove(Tree<T>* top, Tree<T>* bottom); // (e)
void _list(); // (f)
int subtrees(); // (g)
};
private _list function recursively descends the tree, printing the stored data as it visits each node.
template <class T>
Tree<T>::~Tree()
{
if (left != nullptr)
delete left;
if (right != nullptr)
delete right;
//cout << data << endl; // shows deletion
}
delete operator deletes an object through a pointer, it automatically triggers a call to the object's destructor, making the Tree destructor a recursive function. The function recursively descends each subtree, stopping when both are null and deallocates the nodes (i.e., returns the nodes' memory to the heap) as it returns. The commented-out cout statement prints the node's contents as the function removes, illustrating the function's recursive behavior.
template <class T> T* Tree<T>::insert(T key) { Tree<T>* top = this; // (a) Tree<T>* bottom = right; while (bottom != nullptr) // (b) { if (bottom->data == key) // (c) return &bottom->data; top = bottom; // (d) bottom = (key < bottom->data) ? bottom->left : bottom->right; } bottom = new Tree; // (e) bottom->data = key; ((top != this && key < top->data) ? top->left : top->right) = bottom; // (f) return &bottom->data; // (g) }
!.top != this ensures the first insertion is on the root's right subtree. Pointers are valid l-values, so the assignment operator inserts the new node as its parent's left or right subtree.
template <class T> T* Tree<T>::search(T key) { Tree<T>* bottom = right; // (a) while (bottom != nullptr) // (b) { if (bottom->data == key) // (c) return &bottom->data; bottom = (key < bottom->data) ? bottom->left : bottom->right; // (d) } return nullptr; // (e) }
nullptr.
nullptr if key is not stored in the tree.
template <class T> void Tree<T>::remove(T key) { Tree<T>* top = this; // (a) Tree<T>* bottom = right; while (bottom != nullptr) // (b) { if (bottom->data == key) // (c) { remove(top, bottom); return; } top = bottom; // (d) bottom = (key < bottom->data) ? bottom->left : bottom->right; } }
public Tree remove function.
This function descends the tree, looking for the node to remove. The two remove functions separate the tree descent and removal logic, simplifying the remove operation.
public remove function finds the node matching key, it calls the private remove (highlighted in blue), passing to it the top and bottom pointers.
template <class T> void Tree<T>::remove(Tree<T>* top, Tree<T>* bottom) { switch (bottom->subtrees()) // (a) { case 0: // (b) //cout << "CASE 1" << endl; if (top->left == bottom) top->left = nullptr; else top->right = nullptr; delete bottom; return; case 1: // (c) //cout << "CASE 2" << endl; if (top->left == bottom) top->left = (bottom->right == nullptr) ? bottom->left : bottom->right; else if (top->right == bottom) top->right = (bottom->right == nullptr) ? bottom->left : bottom->right; bottom->left = bottom->right = nullptr; delete bottom; return; case 2: // (d) //cout << "CASE 3" << endl; top = bottom; Tree<T>* succ = bottom->right; while (succ->left != nullptr) { top = succ; succ = succ->left; } bottom->data = succ->data; remove(top, succ); // (e) return; } }
private Tree remove function.
A straightforward implementation of the three remove operation cases identified in the previous section resulted in a large, unwieldy structure of nested if-statements. Although the functions in this implementation continue to follow the preceding case analysis, they use several simplifying devices: The two remove functions separate the tree descent and removal logic; a separate function, subtrees, counts a node's filled subtrees, selecting the removal case; the private remove function uses recursion to solve the two-subtree case (case 3 - 2 subtrees); finally, the functions replace many of the if-else-statements with conditional operators performing the same logic but taking less space.
delete operation doesn't cause runaway recursion).
template <class T>
void Tree<T>::_list()
{
if (left != nullptr)
left->_list();
cout << data << endl;
if (right != nullptr)
right->_list();
}
private Tree _list function.
The _list function recursively descends the tree, printing the stored data in-order. Moving the cout statement to the top of the function prints the data pre-order, while moving it to the bottom prints the data post-order. The _list function assumes that operator<< is overloaded for the template class replacing T.
template <class T>
void Tree<T>::tree_view(int level)
{
if (level < 0) // (a)
{
if (right != nullptr)
right->tree_view(0);
return;
}
cout << setw(level) << "" << data << endl; // (b)
if (left != nullptr) // (c)
left->tree_view(level+4);
else
cout << setw(level+4) << '-' << endl;
if (right != nullptr) // (d)
right->tree_view(level+4);
else
cout << setw(level+4) << '-' << endl;
}
template <class T>
int Tree<T>::subtrees()
{
if (left == nullptr && right == nullptr) // (a)
return 0;
else if (left == nullptr || right == nullptr) // (b)
return 1;
else // (c)
return 2;
}
private subtrees function.
The subtrees function counts the current node's subtree (i.e., the number of subtrees of the node referenced by the this pointer). Moving the test out of the remove functions simplifies the removal logic, eliminating one if-else-statement.
#include <iostream>
#include <string>
#include "Tree.h" // (a)
#include "Employee.h"
using namespace std;
int main()
{
Tree<Employee> tree; // (b)
. . .
}
| View | Download | Comments |
|---|---|---|
| Employee.h | Employee.h | A class replacing the template variable T in the examples |
| Tree.h | Tree.h | A binary tree class implemented with one template variable |
| driver.cpp | driver.cpp | A driver program for testing and validating the binary tree class |
| Employee.cpp | Employee.cpp | An interactive binary tree program |