Binary Search Trees - Some Notes

We discussed the different ways to traverse a binary search tree (BST): level-order, in-order, pre-order, and post-order. It is important to know: 

how to traverse a BST and list the nodes visited in these various orders 
how to reconstruct a BST if a nodes and the order in which the tree was traversed is listed 
how to write recursive code to perform the tree traversal in these orders 

Here, we will focus on inserting and deleting a node into a BST. 

Inserting and Deleting 

To insert a node into a BST, the following algorithm could be used: 

if the tree is empty, insert the data as root 
if the tree is not empty, take a new pointer that points at the root. 
if the node to be inserted is the same as the current node, stop - insertion failed. 
if the node to be inserted is larger than the current node, and right child is not empty, change pointer to right child 
if the node to be inserted is smaller than the current node, and left child is not empty, change pointer to left child 
repeat this process until the left or right child is empty and insert at that position. 

First, look at the following tree below and insert the numbers 65, 35, 100, and 91 (if possible). 

 
Next, try to write some code that will insert a node into a BST. Recall the definition of a node is: 

class Node 
{ 
   public: Element data; 
   public: Node *left, *right; 
   public: Node(void); 
   public: Node(Element); 
};  

To be a little more formal, here is the definition of a BST together with the insert method specification: 

class BST 
{ 
private: Node *root; 
public: void insert(Element); 
// sets ERROR to false if insertion was successful, to true if it 
was not successful. 
}; 

Here is the complete code for the insertion. This is only a suggestion, and there are several other, perhaps better, ways of doing this:

int BST::insert(Element e)
{
   Node *pnew = new Node(e);
   if (root == 0)
   {
      root = pnew;
      error = 0;
   }
   else
   {
      Node *p = root;
      while ((p->data != e) && (p->left != 0) && (p->right != 0) )
      {
	 if (p->data < e)
	   p = p->right;
	 else
	   p = p->left;
      }
      if ((p->data < e) && (p->right == 0))
      {
	 p->right = pnew;
	 error = 0;
      }
      else if ( (p->data > e) && (p->left == 0))
      {
	 p->left = pnew;
	 error=0;
      }
      else
      {
         delete pnew;
	 error = 1;
      }
   }
}

This code is fairly complicated, because it acually does two tasks in one: it first searches for the element to be inserted, and if it did not find it, then it inserts it at the appropriate positioin. Therefore, we should try to simplify the insertion by introduction a separate function that searches for an element. Our insertion function can then try to use that function instead of duplicating the search process. 

Here is some code that searches for an element in a binary search tree. For input, it takes a pointer to the root of the tree. When the procedure is finished, the pointer p will point to the node containing the element to search for, or to null if the element can not be found. 

void BST::FindANode(Node *p, Element e)
{
   while ((p != 0) && (p->data != e))
   {
      if (p->data < e)
	 p = p->right;
      else
	 p = p->left;
   }
}

But, this function can not be used for our insertion routine, because to insert, we need to pretend to search for the node, and if the node can not be found, we need to have a pointer to the previous node considered. At that node the new element should be inserted as either right or left child. But the above function can be easily modified to deliver this pointer:

void FindNodes(Node *p, Node *parent, Element e)
{
   while ((p != 0) && (p->data != e))
   {
      parent = p;
      if (p->data < e)
	 p = p->right;
      else
	 p = p->left;
   }
}

Now this function can be used by passing as input a pointer to the root of the tree. When the function finishes, and p points to null, it means that the element could not be found, and hence can be inserted. What's more, the node parent will point to the node where the new element can be inserted as either the right or left child. 

Now, rewrite the insert method to use this new method FindNodes. You could follow this algorithm:

use FindNodes to search for the element 
if element was found in the tree, report failure 
else create a new node pnew containing the tree element and set the parent pointer of p to the new pointer pnew and report success
Note that if p is the root than parent is not valid 

Next, let’s focus on deletion. Deleting a key from a BST is broken up into three distinct cases: 

deleting a node with no children (i.e. a leaf) 
deleting a node with only one child 
deleting a node with two children 

You might find one of the following algorithms helpful: 

Find node p with the value to be deleted and its parent (note that the function FindNodes from above could be used here). 

if the node p is a leaf then:
remove the node 
if node p has one child only then
Determine if p is the right or left child of parent
Reset right or left pointer of parent to the non-empty child of p
if node p has two children, then
Find the right-most node in the left subtree of p, call it righty
Move righty's content to p
Reset the pointer of righty's parent to righty's left child

Note that if p is the root than parent is not valid

We will not discuss the code for an actual deletion. Rather, you need to practice this algorithm on trees that are printed. For example, a typical question on an exam would be: 

Draw the binary search tree after each operation. Each insertion and deletion operates on the original tree, and the new tree is again a binary search tree. 

  
 
a) Insert 65 

Solution:  

b) Insert 35 

 Solution:  can’t insert, it’s already there. 

c) Delete 70 

Solution:  

d) Delete 60 

Solution:  

e) Delete 50 

Solution:  

f) First delete 50, then 45. 

Solution:  

We will review these discussions on Monday, if necessary, together with a general review for the second exam.