# Binary Tree

Binary Tree is a classical data structure in Computer Science. It is a non-linear data structure and formally a binary tree is either *empty* or a *root* node with a left binary tree and a right binary tree.

The left binary tree is sometimes referred to as *the left subtree* of the root and the right binary tree is referred to as *the right subtree* of the root.

Binary trees most commonly occur in the context of Binary Search Trees (BST), where keys are stored in a sorted fashion but in this article we will only focus on Binary Trees. Binary Search Trees (BST) will be explained deeply in another article.

In this graphical representation of Binary Tree, Node **A** is the *root* and Node **B** and **I** are the left and right children of root **A**.

The *depth* of a node **n** is the number of nodes on the search path from the root to **n**, not including **n** itself. The *height* of a binary tree is the maximum depth of any node in that tree. A *level* of a tree is all nodes at the same depth.

## Implementation of Binary Tree

Just like we did in Linked List, we need to start the implementation by defining a root *Node* and its left and right children.

```
class BinaryTree:
def __init__(self, data):
self.left = None
self.right = None
self.root = data
```

Notice that constructor function expects some kind of data to store in the root. If we want to expand our tree beyond the root node, we will need funcions to insert new data to *left* and *right* nodes.

### insert_left function

Let’s implement `insert_left()`

function together.
We must consider two possibilities to be able to implement this function. First case is a node with no existing left child and the second case is node with an existing left child.

```
def insert_left(self, value):
if self.left is None:
self.left = BinaryTree(value)
else:
new_node = BinaryTree(value)
new_node.left = self.left
self.left = new_node
```

When there is no left child, all we need to do is simply create a left child with creating a new node. In the second case, where there is already an existing left child, we are inserting as new node and pushing existing child node one level down in the tree.

### insert_right function

Implementation of `insert_right()`

function is symmetric with the implementation of `insert_left()`

function.

```
def insert_right(self, value):
if self.right is None:
self.right = BinaryTree(value)
else:
new_node = BinaryTree(value)
new_node.right = self.right
self.right = new_node
```

There will be either no right child or there will be an existing right child which will be pushed down one level with the addition of new right child.

## Implementation Summary

let’s summarize what we implemented so far by adding getter and setter functions to call existing nodes.

```
class BinaryTree:
def __init__(self, data):
self.left = None
self.right = None
self.root = data
def insert_left(self, value):
if self.left is None:
self.left = BinaryTree(value)
else:
new_node = BinaryTree(value)
new_node.left = self.left
self.left = new_node
def insert_right(self, value):
if self.right is None:
self.right = BinaryTree(value)
else:
new_node = BinaryTree(value)
new_node.right = self.right
self.right = new_node
def get_right_child(self):
return self.right
def get_left_child(self):
return self.left
def set_root_value(self, value):
self.root = value
def get_root_value(self):
return self.root
```

## Binary Tree Traversals

*Traversing* is the task of **visiting all nodes of the tree at least once**. The tree is not a linear data structure, hence there are many ways of traversing it.

The three most commonly used traversing methods are; pre-order, in-order and post-order.

### Pre-order Traversal

In this traversal mode, one starts from the *root*, move to *left child*, then *right child*.

So in our case, order will be: **a**, **b**, **d**, **c**, **e**, **f**

```
def pre_order(self):
print(self.value)
if self.left_child:
self.left_child.pre_order()
if self.right_child:
self.right_child.pre_order()
```

### In-order Traversal

In this traversal mode, one starts visiting with the *left child*, followed by its *parent* and then the *right child*.

So in our case, order will be: **b**, **d**, **a**, **e**, **c**, **f**

```
def in_order(self):
if self.left:
self.left.in_order()
print(self.root)
if self.right:
self.right.in_order()
```

### Post-order Traversal

In this traversal mode, one starts from the *left child*, move to the *right child*, and terminate at the *root*.

So in our case, order will be: **d**, **b**, **e**, **f**, **c**, **a**

```
def post_order(self):
if self.left:
self.left.post_order()
if self.right:
self.right.post_order()
print(self.root)
```

## Example

To test our code, we can try to implement the example tree in the traversals.

So;

- node
**a**is our*root*node. - node
**b**is the*left*child of node**a** - node
**c**is the*right*child of node**a** - node
**d**is the*right*child of node**b** - node
**e**is the*left*child of node**c** - node
**f**is the*right*child of node**c**

if we want to implement this structure in our code, it will look like this;

```
a_node = BinaryTree('a')
a_node.insert_left('b')
a_node.insert_right('c')
b_node = a_node.left
b_node.insert_right('d')
c_node = a_node.right
c_node.insert_left('e')
c_node.insert_right('f')
d_node = b_node.right
e_node = c_node.left
f_node = c_node.right
print(a_node.root) # a
print(b_node.root) # b
print(c_node.root) # c
print(d_node.root) # d
print(e_node.root) # e
print(f_node.root) # f
print(a_node.pre_order()) # abdcef
print(a_node.in_order()) # bdaecf
print(a_node.post_order()) # dbefca
```

## Summary

Binary Tree is a fundamental data structure in computer science and this article is just scratching the surface. I will continue writing more about trees in new articles about searching (Depth-First Search - DFS, Breadth-First Search - BFS), Binary Search Trees (BST), balanced trees, AVL trees, tree treversal etc.

## References

- Wikipedia,
*Binary Tree*