Binary Trees

A tree is a type of linked data structure of nodes. Every node has multiple pointers that point to other nodes (or nullptr). The whole structure is pointed to with a root pointer, which points to the root node. If a node points to another, the node it points to is the child node and the node itself is the parent node. If a tree doesn’t have any nodes, it is called an empty tree. If node doesn’t have any children (it doesn’t point to anything), it is called a leaf node.

A binary tree is a type of tree in which every node has at most two children nodes (left and right pointer). Two nodes that descend form the same parent node are called sibling nodes. A sub-tree is a term that can be used to describe any node that isn’t the root node. The node depth is how many levels of siblings a certain node is from the root node. The root node has a depth of 0, it’s children have a depth of 1, etc.

A full binary tree is a binary tree in which every non-leaf node has two children, and every leaf node has the same depth.

Traversals

There are four different ways to traverse the nodes of a binary tree (to visit every node of a tree): - pre-order, - in-order, - post-order, and - level-order.

PRE-ORDER

  1. Do something with the current node (it could be to print it out, change the value, etc.)
  2. Go to the nodes in the left sub-tree and repeat process
  3. Go to the nodes in the right sub-tree and repeat process

Code:

void preorder(Node *cur)
{
     if (cur == nullptr)     // If the tree is an empty tree
          return;

     cout << cur->value;     // In this case we print out the value

     preorder(cur->left);     // Recursively do the same to the left and right sub-trees
     preorder(cur->right);
}
        a
    b        c
d       e

Example:

Current root is a
Print out a
Go to left sub-tree
     Print out b
     Go to left sub-tree
          Print out d
          Go to left sub-tree
               Return (no child)
          Go to right sub-tree
               Return
     Go to right sub-tree
          Print out b
          Go to left sub-tree
               Return
          Go to right sub-tree
               Return
Go to right sub-tree
     Print out c
     Go to left sub-tree
          Return
     Go to right sub-tree
          Return

Output:

a b d e c

How to visualize it: Go down every line of nodes diagonally, top to bottom

IN-ORDER

  1. Go to the nodes in the left sub-tree
  2. Do something with the current node
  3. Go to the nodes in the right sub-tree

Notice that in this order we first go all the way to the left before printing anything out (this includes the root node)

Code:

void inorder(Node *cur)
{
     if (cur == nullptr)
          return;

     inorder(cur->left);

     cout << cur->value;

     inorder(cur->right);
}
        a
    b        c
d       e

Example:

Current root is a
Go to left sub-tree (b)
     Go to left sub-tree (d)
          Go to left sub-tree (null)
               Return
          Print out d
          Go to right sub-tree (null)
               Return
     Print out b
     Go to right sub-tree(e)
          Go to left sub-tree (null)
               Return
          Print out e
          Go to right sub-tree (null)
               Return
Print out a
Go to right sub-tree (c)
     Go to left sub-tree (null)
          Return
     Print out c
     Go to right sub-tree (null)
          Return

Output:

d b e a c

How to visualize it: start at the left side of tree, go vertically right, bottom to top

POST-ORDER

  1. Go to the nodes in the left sub-tree
  2. Go to the nodes in the right sub-tree
  3. Do something with the current node

Code:

void postorder(Node *cur)
{
     if (cur == nullptr)
          return;

     postorder(cur->left);

     postorder(cur->right);

     cout << cur->value;
}
        a
    b        c
d       e

Example:

Current root is a
Go to the left sub-tree (b)
     Go to the left sub-tree (d)
          Go to the left sub-tree (null)
               Return
          Go to the right sub-tree (null)
               Return
          Print out d
     Go to the right sub-tree(e)
          Go to the left sub-tree (null)
               Return
          Go to the right sub-tree (null)
               Return
          Print out e
     Print out b
Go to the right sub-tree (c)
     Go to the left sub-tree (null)
         Return
     Go to the right sub-tree (null)
         Return
     Print out c
Print out a

Output:

d e b c a

How to visualize it: start at the bottom of the tree, go horizontally up, left to right

LEVEL-ORDER

  1. Use a pointer variable and a queue of pointers to nodes
  2. Push the root node pointer onto the queue
  3. While the queue is not empty,
    1. Pop the top node
    2. Do something to the node
    3. Push the node’s children to the queue

Code:

void levelorder(Node *cur)
{
     if (cur == nullptr)
          return;
     Node *temp;
     queue<Node*> toProcess;
     toProcess.push_back(cur);
     while (!toProcess.empty())
     {
          temp = toProcess.front();
          toProcess.pop();
          cout << temp->value;
          if (temp->left != nullptr)
               toProcess.push_back(temp->left);
          if (temp->right != nullptr)
               toProcess.push_back(temp->right);
     }
}
        a
    b        c
d       e

Example:

Current root is a
Push a
QUEUE: a
Pop a, print out a
QUEUE:
Push b
Push c
QUEUE: b c
Pop b, print out b
QUEUE: c
Push d
Push e
QUEUE: c d e
Pop c, print out c
QUEUE: d e
Don't push anything
Pop d, print out d
QUEUE: e
Don't push anything
Pop e, print out e
Don't push anything
Done!

Output:

a b c d e

How to visualize: Start from the top of the tree, go down horizontally, left to right

The big-O of all of these traversals is O(N).

You can also evaluate expressions with a binary tree. For example, 2 + 3 would be represented with this tree:

   +
2     3

Here is the algorithm to evaluate it: 1. If the current node’s value is a number, return its value 2. Recursively evaluate the left sub-tree and get the result 3. Recursively evaluate the right sub-tree and get the result 4. Apply the operator in the current node to the left and right results; return the result.

(5 + 6) * (3 - 1):
          *
    +         -
5      6   3     1

Example:

Current node is *
Go to the left sub-tree
     Current node is +
     Go to left sub-tree
          Current node is 5
          Return 5
     Go to right sub-tree
          Current node is 6
          Return 6
     Apply + to 5 and 6
     Return 11
Go to right sub-tree
     Current node is -
     Go to left sub-tree
          Current node is 3
          Return 3
     Go to right sub-tree
          Current node is 1
          Return 1
     Apply - to 3 and 1
     Return 2
Apply * to 11 and 2
Return 22