13.6.2. Binary Trees

A binary tree is one kind of dynamic data structure. Dynamic data structures get their name from two characteristics. First, they are constructed from blocks of memory dynamically allocated with the new operator. Second, they are organized and held together by pointers, making it possible to reorganize them dynamically (i.e., while the program is running). CS 2420 will cover binary trees and other dynamic data structures in more detail. Our purpose here is to demonstrate how templates can generalize dynamic data structures.

How binary trees (and dynamic data structures in general) organize data is independent of the kind of data that they store. Ideally, we would like to have a library of data structures and allow the user to choose which one best solves a particular problem. To use the data structure, the user would take it from the library and then somehow tell the program what kind of data they wish to store in it. Template variables act as placeholders until the user specifies a "real" data type, which replaces the template variable.

We can implement binary trees in many ways. However we implement them, they usually support several standard operations and some optional operations as well:

  1. create the data structure; done with a constructor in C++
  2. destroy the structure when it is no long needed; done with a destructor in C++
  3. insert a new data item in the structure; the insert operation may allow or disallow the insertion of duplicate data in the tree
  4. search for a given data item; the search is typically based on part of the stored data, called the key; if the key is found, then the program is able to access or modify all the stored data
  5. remove an existing data item from the structure
  6. visit perform some user-specified operation on each data item stored in the structure
  7. list all information stored in the data structure; this is a special case of the visit operation

A Binary Tree Implementation

template <class T>
class Tree
{
	private:
		T	data;
		Tree*	left;
		Tree*	right;
	    . . .
An empty node is represented by a square divided into three parts: the data and two pointers. The data part is empty and the pointers are both null. A picture representing the fist insertion operation. The tree consists of two nodes. 'root' points to the top node, and root's right pointer and 'down' both point to the newly inserted node.
(a)(b)(c)
One binary tree implementation.
  1. A partial class specification based on templates.
  2. Each instance of of the Tree is class is abstractly represented by a square, often called a node. The top rectangle represents the stored data, while the two inner squares represent the left and right pointers. The Greek letter λ denotes a language-independent null pointer. A logically empty tree consists of one Tree object that is defined in the client program, and is frequently named root.
  3. The first data item stored in the tree is arbitrarily added to the right sub-tree; the left pointer on the top Tree object is never used. The insertion algorithm uses two pointers named root and down. The two pointers are moved down the tree in such a way that (except for the time that it takes to execute two adjacent statements) root is always one level above down.
The insertion operation begins with 'root' pointing to the top tree node, and 'down' pointing to root's right sub-tree. The example assumes that the 'down' node has two sub-trees; that is, both the 'left' and 'right' pointers of the 'down' node are filled. The example further assumes that the 'left' and 'right' pointers of the nodes below 'down' anre null. So, the three has a totoal of four nodes, including 'root,' arranged in three levels. The program has descended one level in the tree. 'root' is updated to point to the previous 'down' node, and 'down' is updated to point to the 'right' sub-tree. Choosing the rigth sub-tree over the left done arbitrarily for the illustration. The program has descended one more level. 'root' is again updated to point to the previous 'down' node, and down is now null which stops the descent.
(a)(b)(c)
The insertion operation. When a program inserts or searches for a node in a binary tree, it descends the tree from the top to the bottom, updating the root and down pointers as it works its way down the tree. As the program descends the tree, it compares two data values. The first value is the target data, the data for which the program is searching or intends to insert in the tree. The second value is the data stored in the node to which root currently points. If the target value is less than the root node value, the program follows the left sub-tree; otherwise, it follows the right sub-tree.
  1. The insert function begins by initializing the two pointers root and down:
    Tree* root = this;
    Tree* down = right;
  2. The insert function continues to update both pointers while it looks for the bottom of the tree (note that this process assumes that operator< is defined for the stored data type):
    root = down;
    if (data < down->data)
    	down = down->left;
    else
    	down = down->right;
  3. The bottom of the tree is reached when down becomes nullptr; this is where the new Tree object is inserted (this process again assumes that operator< is defined for the stored data type):
    down = new Tree;
    down->data = data;
    if (data < down->data)
    	root->left = down;
    else
    	root->right = down;
The search operation is similar to insert, but it is simpler and only requires one pointer. The examples presented in the following sections omit the remove and visit operations to simplify the demonstrations.