Binary Tree


  1. Abstract idea of a tree:

  2.  A tree is a hierarchical data structure: an element of a tree is located below some elements and above some other elements.

    Here is an example of a tree holding letters: 

          tree
          ----
           j    <-- root
         /   \
        f      k  
      /   \      \
     a     h      z    <-- leaves

    Tree Vocabulary

    Let's now introduce some vocabulary with our sample tree... The element at the top of the tree is called the root. The elements that are directly under an element are called its children. The element directly above another element is called its parent. For example, a is a child of f and f is the parent of a. Finally, elements with no children are called leaves.


    Aside: If you were to draw the picture above upside down, it would look like a real tree, with the leaves at the top and the root at the bottom...However, we usually draw tree data structures as we've done above.

    Uses

    There are many reasons to use a tree to store information. One reason might be because you want to store information that naturally forms a hierarchy. For example, the file system on a computer:
    file system
    -----------
         /    <-- root
      /     \
    ...    home
          /    \
       ugrad    course
        /      /   |   \
      ...  cs101 cs112 cs113
    Despite the hierarchical order of the structure of the tree, the order enforced on objects in the tree will depend on how we use the tree. This just means that unlike a stack whose operations are usually limited to push and pop, there are many different kinds of trees and ways to use them. Thus, this flexibility makes them more akin to linked lists.

    Recursive Data Structure

    A tree can be viewed as a recursive data structure: trees are made up of subtrees.

    For example, let's look at our tree of letters and examine the part starting at f and everything under it... 

          tree
          ----
           j
         /   \
        f      k  
      /   \      \
     a     h      z
    In this subtree, f is a root.

    Binary Trees

    We can talk about trees where the number of children that any element has is limited. In the tree above, no element has more than 2 children. For the rest of this example, we will enforce this to be the case.

    A tree whose elements have at most 2 children is called a binary tree.

    Since each element in a binary tree can have only 2 children, we typically name them the left and right child.

  3. Tree representation in C++:

  4.  Since we want to be able to represent a tree in C++, how are we going to store this hierarchical structure?

    Similarly to a linked list, we can have a node that contains data and two pointers: to the left child and to the right child. If a node does not have a right or a left child, the respective pointer is NULL.

    So, our first example tree of letters would look something like:

               |
               v
             -----
             | j |
             |---|
             | | |
             /---\
            v     v
        -----     -----
        | f |     | k |
        |---|     |---|
        | | |     |0| |
        /---\     ----\
       v     v         v
    -----   -----      -----
    | a |   | h |      | z |
    |---|   |---|      |---|
    |0|0|   |0|0|      |0|0|
    -----   -----      -----
    Also, just as we need a pointer to the first node to keep track of a linked list; here, we need a pointer to the root node to keep track of a tree.

    Special case: Empty Tree

    If a tree is empty, its pointer to the root is NULL.
  5. Implementation of a tree:

  6.  Implementing a tree in C++ is a tricky design problem: it is hard (if at all possible) to come up with a structure that's simple and convenient at the same time.

    The model that we consider here is fairly straightforward. However, as we will see, it does not quite satisfy the recursive property that we have stated above: "a subtree of a tree is itself a tree". This causes some problems in writing recursive functions for this model.

    Our approach is to have two separate classes:

    The class BTNode contains a data field and pointers to 2 possible children, the left one and the right one:
    class BTNode {
    public:
      ...
    
    private:
      ItemType data;
      BTNode *lChild;
      BTNode *rChild;
    };
    The class BTree has a pointer to the root node of the tree, which is NULL if the tree is empty.
    class BTree {
    public:
      ...
    
    private:
      BTNode* rootPtr;
    };
  7. Implementation of the classes.

  8.  Class BTNode has the following methods:

    class BTNode {
    public:
      // Constructors.
      BTNode();
      BTNode(ItemType);
    
      // Destructor
      ~BTNode();
    
      // Get fields of node.
      ItemType getData() const;
      BTNode *getLeftChild() const;
      BTNode *getRightChild() const;
    
      // Set fields of node.
      void setData(ItemType);
      void setLeftChild(BTNode *);
      void setRightChild(BTNode *);
    
    private:
      ItemType data;
      BTNode *lChild;
      BTNode *rChild;
    };
    The destructor is needed, since memory for the children is allocated dynamically, and therefore needs to be de-allocated when a node gets out of scope.

    The methods for the class BTree are as follows:

    class BTree{
    public:
            // tree destructor
            ~BTree();
            
            // check if the tree is empty
            bool isEmpty();
    
            // set the value of the root
            void setRoot(BTNode *);
    
            // get the value of the root
            BTNode *getRoot();
    
    private:
            BTNode *rootPtr;
    };
    There may be more methods defined for a tree, s.a. various traversals, inserting a node, etc., but for this lab we limit ourselves to the methods given above.

    Lab assignment

    Please download the following files: btnode.h, btnode.cpp, btree.h, btree.cpp, treetest.cpp, itemtype.h, Makefile, and also the data files empty, data1, data2 We use the following representation of a tree: we write a tree in pre-order, separating data for each node by square brackets [ and ]. For instance, the tree
          tree
          ----
           j   
         /   \
        f      k  
      /   \      \
     a     h      z
    will be stored as
    [j[f[a[][]][h[][]]][k[][z[][]]]]
    In fact, it is stored in the file data2.

    Exercise  In the file treetest.cpp write and test the functions described below. Write these functions one-by-one, compile and test your program before starting the other one.

    Note that each tree function has an auxiliary function for nodes that performs a recursive call. Important: Make sure that all the functions that use trees as arguments are call-by-reference. If a tree is passed by value, it gets out of scope, and its destructor is called.

    1. A function that computes the height of a tree.
    2. A function that computes number of nodes of a tree
    3. A function that takes two trees by reference and changes the second one to be the mirror image of the first one, i.e. all right children of the first tree become left children of the scond tree, and vice versa.



    BU CAS CS - Binary Tree

    This page created by Jisook Youn <jisook@cs.bu.edu>.
    Modified by Jing Zhong <jingzh@cs.bu.edu>.
    Material adapted for C++ from Binary Tree (C version by Robert I. Pitts <rip@cs.bu.edu>).