Open In App

Binary Tree in C++

Last Updated : 03 Jul, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

A binary tree is a hierarchical data structure in which each node has at most two children, referred to as the left and right child. The topmost node is the root node, and the nodes at the last level having no children are the leaf nodes.

In this article, we will learn about the basics of a binary tree in C++. We will learn how to represent a binary tree, basic operations performed on a binary tree, it's various types and practical applications of binary tree in C.

Implementation of Binary Tree in C++

A binary tree is a tree-based hierarchical data structure where each node can have at most two children. The topmost node is called the root, and nodes with no children are called leaves. To implement a binary tree in C++, we'll use a node-based approach. Each node of the binary tree will contain data and pointers to its left and right children.

Representation of Binary Tree

BinaryTreeC
Binary Tree in C++

To represent a binary tree in C++, we will declare a class Node that will consist of data and pointers to left and right children. We will use a template to keep the binary tree generic so that it can store multiple data types. We will also declare a class name BinaryTree to encapsulate the binary tree data members and all its methods.

template <typename T>
class Node {
public:
T data;
Node* left;
Node* right;

Here, each node of a binary tree has the following 3 parts:

  • data represents the value stored in the node of the binary tree.
  • left is the pointer to the left child of the node.
  • right is the pointer to the right child of the node.

To learn more about binary tree refer to this article.

Basic Operations on Binary Tree in C++

Following are some of the basic operations of binary tree that are required to manipulate its elements:

Operation Name

Description

Time Complexity

Space Complexity

Insertion

Inserts a new node into the binary tree.

O(N)

O(N)

Deletion

Deletes a specific node from the binary tree.

O(N)

O(N)

Searching

Searches for a specific value in the binary tree.

O(N)

O(N)

Traversal

In-order, Pre-order, Post-order traversals

O(N)

O(N)

1. Insertion in Binary Tree

In a binary tree, we can insert a node anywhere as right child or left child of the node. Following is the algorithm to insert a new node into the binary tree:

Approach:

  1. Check if the tree is empty:
    • If the tree is empty, create a new node and set it as the root.
  2. Use level order traversal to find the insertion point:
    • Initialize a queue and enqueue the root node.
    • While the queue is not empty:
      • Dequeue a node from the front of the queue.
      • If the dequeued node's left child is NULL:
        • Insert the new node as the left child of the dequeued node.
        • Break the loop.
      • If the dequeued node's right child is NULL:
        • Insert the new node as the right child of the dequeued node.
        • Break the loop.
      • If both left and right children are not NULL, enqueue them.

2. Deletion in Binary Tree

In binary, after deletion of a node we need to rearrange the tree to maintain the property of binary tree. Leaf node can be deleted without any rearrangements. Following is the algorithm to delete a node from the binary tree:

Approach:

  1. Find the node to be deleted:
    • Use level order traversal to find the node with the value to be deleted and the deepest node in the tree.
  2. Replace the node to be deleted with the deepest node:
    • Replace the value of the node to be deleted with the value of the deepest node.
  3. Delete the deepest node:
    • Use level order traversal to find and remove the deepest node in the tree.

3. Searching in Binary Tree

To search a given node in a binary tree, we need to traverse and compare each node with target node (node to be searched). Following is the algorithm to search a node in the binary tree:

Approach:

  • Check if the tree is empty:
    • If the tree is empty, return false.
  • Use level order traversal to search for the node:
    • Initialize a queue and enqueue the root node.
    • While the queue is not empty:
      • Dequeue a node from the front of the queue.
      • If the dequeued node's value matches the value being searched for, return true.
      • Enqueue the left and right children of the dequeued node if they are not NULL.
    • If the loop ends and the value was not found, return false.

4. Traversals in Binary Tree

Following are the binary tree traversal techniques:

C++ Program for Implementing Binary Tree

The following program illustrates how we can implement a binary tree in C++.

C++
// C++ Program for Implementing Binary Tree
#include <iostream>
#include <queue>
using namespace std;

// Template class for the Node of a Binary Tree
template <typename T>
class Node {
public:
    // Data held by the node
    T data;  
    // Pointer to the left child
    Node* left;  
    // Pointer to the right child
    Node* right;  

    // Constructor to initialize the node with a value
    Node(T value) : data(value), left(nullptr), right(nullptr) {}
};

// Template class for a Binary Tree
template <typename T>
class BinaryTree {
private:
    // Pointer to the root of the tree
    Node<T>* root;  

    // Recursive Function to delete a node from the tree
    Node<T>* deleteRecursive(Node<T>* current, T value) {
        if (current == nullptr) return nullptr;

        if (current->data == value) {
            if (current->left == nullptr && current->right == nullptr) {
                delete current;
                return nullptr;
            }
            if (current->left == nullptr) {
                Node<T>* temp = current->right;
                delete current;
                return temp;
            }
            if (current->right == nullptr) {
                Node<T>* temp = current->left;
                delete current;
                return temp;
            }

            Node<T>* successor = findMin(current->right);
            current->data = successor->data;
            current->right = deleteRecursive(current->right, successor->data);
        } else {
            current->left = deleteRecursive(current->left, value);
            current->right = deleteRecursive(current->right, value);
        }
        return current;
    }

    // Helper Function to find the minimum value node
    Node<T>* findMin(Node<T>* node) {
        while (node->left != nullptr) node = node->left;
        return node;
    }

    // Recursive Function to search for a value in the tree
    bool searchRecursive(Node<T>* current, T value) {
        if (current == nullptr) return false;
        if (current->data == value) return true;
        return searchRecursive(current->left, value) || searchRecursive(current->right, value);
    }

    // Function for Recursive inorder traversal of the tree
    void inorderRecursive(Node<T>* node) {
        if (node != nullptr) {
            inorderRecursive(node->left);
            cout << node->data << " ";
            inorderRecursive(node->right);
        }
    }

    // Function for Recursive preorder traversal of the tree
    void preorderRecursive(Node<T>* node) {
        if (node != nullptr) {
            cout << node->data << " ";
            preorderRecursive(node->left);
            preorderRecursive(node->right);
        }
    }

    // Function for Recursive postorder traversal of the tree
    void postorderRecursive(Node<T>* node) {
        if (node != nullptr) {
            postorderRecursive(node->left);
            postorderRecursive(node->right);
            cout << node->data << " ";
        }
    }

public:
    // Constructor to initialize the tree
    BinaryTree() : root(nullptr) {}

    // Function to insert a node in the binary tree 
    void insertNode(T value) {
        Node<T>* newNode = new Node<T>(value);

        if (root == nullptr) {
            root = newNode;
            return;
        }

        queue<Node<T>*> q;
        q.push(root);

        while (!q.empty()) {
            Node<T>* current = q.front();
            q.pop();

            if (current->left == nullptr) {
                current->left = newNode;
                return;
            } else {
                q.push(current->left);
            }

            if (current->right == nullptr) {
                current->right = newNode;
                return;
            } else {
                q.push(current->right);
            }
        }
    }

    // Function to delete a node from the tree
    void deleteNode(T value) {
        root = deleteRecursive(root, value);
    }

    // Function to search for a value in the tree
    bool search(T value) {
        return searchRecursive(root, value);
    }

    // Function to perform inorder traversal of the tree
    void inorder() {
        inorderRecursive(root);
        cout << endl;
    }

    // Function to perform preorder traversal of the tree
    void preorder() {
        preorderRecursive(root);
        cout << endl;
    }

    // Function to perform postorder traversal of the tree
    void postorder() {
        postorderRecursive(root);
        cout << endl;
    }

    // Function  to perform level order traversal of the tree
    void levelOrder() {
        if (root == nullptr) return;

        queue<Node<T>*> q;
        q.push(root);

        while (!q.empty()) {
            Node<T>* current = q.front();
            q.pop();

            cout << current->data << " ";

            if (current->left != nullptr) q.push(current->left);
            if (current->right != nullptr) q.push(current->right);
        }
        cout << endl;
    }
};

int main() {
    BinaryTree<int> tree;
    
    // Insert the nodes into the tree
    tree.insertNode(1);
    tree.insertNode(2);
    tree.insertNode(3);
    tree.insertNode(4);
    tree.insertNode(5);
    tree.insertNode(6);

    cout << "Inorder traversal: ";
    tree.inorder();

    cout << "Preorder traversal: ";
    tree.preorder();

    cout << "Postorder traversal: ";
    tree.postorder();

    cout << "Level order traversal: ";
    tree.levelOrder();

    cout << "Searching for 7: " << (tree.search(7) ? "Found" : "Not Found") << endl;
    cout << "Searching for 6: " << (tree.search(6) ? "Found" : "Not Found") << endl;

    tree.deleteNode(3);
    cout << "Inorder traversal after removing 3: ";
    tree.inorder();

    return 0;
}


Output

Inorder traversal: 4 2 5 1 6 3 
Preorder traversal: 1 2 4 5 3 6
Postorder traversal: 4 5 2 6 3 1
Level order traversal: 1 2 3 4 5 6
Searching for 7: Not Found
Searching for 6: Found
Inorder traversal after removing 3: 4 2 5 1 6

Types of Binary Tree in C++

Following are the types of Binary Trees according to various factors:

According to Number of Children

  • Full Binary Tree: A full binary tree is a tree in which every node has either 0 or 2 children. In other words, all nodes except leaf nodes have two children.
  • Degenerate Binary Tree: A degenerate binary tree is similar to a skewed binary tree. It's a tree where each parent node has only one child node.
  • Skewed Binary Tree: A skewed binary tree is a tree in which all nodes have only one child, either left or right. There are two types: left-skewed and right-skewed.

According to Completion of Levels

  • Complete Binary Tree: A complete binary tree is a binary tree in which all levels are completely filled except possibly the last level, which is filled from left to right.
  • Perfect Binary Tree: A perfect binary tree is a binary tree in which all interior nodes have two children and all leaves are at the same level.
  • Balanced Binary Tree: A balanced binary tree is a binary tree in which the height of the left and right subtrees of every node differs by at most one.

According to Node Values

  • Binary Search Tree: A Binary Search Tree is a binary tree where for each node, all elements in the left subtree are less than the node, and all elements in the right subtree are greater than the node.
  • AVL Tree: An AVL tree is a self-balancing BST where the height of the left and right subtrees of any node differ by at most one.
  • Red Black Tree: A Red-Black tree is a self-balancing BST where each node has an extra bit for denoting the color of the node, either red or black.
  • B Tree: B-tree is a self-balancing tree data structure that maintains sorted data and allows searches, sequential access, insertions, and deletions in logarithmic time.
  • B+ Tree: A B+ tree is a variant of the B-tree that has all data stored in the leaf nodes, while internal nodes only store keys.
  • Segment Tree: A Segment Tree is a tree data structure used for storing information about intervals, or segments. It allows querying which of the stored segments contain a given point.
  • Fenwick Tree: A Fenwick Tree is a data structure that can efficiently update elements and calculate prefix sums in a table of numbers.

Applications of Binary Tree

Following are some common applications of Binary Trees:

  • File System Organization: Binary trees are used to represent hierarchical file structures.Each node in the tree represents a directory or a file, with the root node typically representing the main directory. Child nodes represent subdirectories or files within a directory.
  • Expression Trees: In compilers and interpreters, expression trees are used to represent and evaluate arithmetic or logical expressions. Each internal node represents an operator (+, -, *, /, etc.), while leaf nodes represent operands (numbers or variables). This structure allows for easy parsing, evaluation, and optimization of mathematical expressions.
  • Huffman Coding Trees: Huffman coding is a data compression algorithm that uses a binary tree structure. The tree is constructed based on the frequency of characters in the data. More frequent characters are assigned shorter codes, while less frequent ones get longer codes. This results in efficient data compression, especially for text files.
  • Binary Search Trees: BSTs are a special type of binary tree where for each node, all elements in the left subtree are smaller, and all elements in the right subtree are larger. This property makes BSTs extremely efficient for searching, insertion, and deletion operations, typically with O(log n) time complexity in balanced cases.
  • Decision Trees: In machine learning, decision trees are used for classification and regression tasks. Each internal node represents a "test" on an attribute, each branch represents the outcome of the test, and each leaf node represents a class label or a numerical output. Decision trees are popular due to their interpretability and ability to handle both numerical and categorical data.
  • Game Trees: In artificial intelligence, particularly for game strategy planning, game trees represent all possible moves and their outcomes in a game. Each level of the tree represents a player's turn, and leaf nodes represent final game states
  • Syntax Trees: Compilers use syntax trees (also known as abstract syntax trees or ASTs) to represent the structure of program code. Each node in the tree represents a construct in the source code, such as functions, loops, or conditional statements. This representation helps in code analysis, optimization, and generation of machine code.

Next Article
Article Tags :
Practice Tags :

Similar Reads