Day 18: Binary Trees Basics

Day 18: Binary Trees Basics

Welcome to Day 18 of my 100 Days of DSA challenge! Today, I tackled five problems centered around the fundamentals of binary trees. These problems helped me dive deeper into constructing trees, understanding their properties, and applying tree traversal techniques. Binary trees are an essential building block for many advanced data structures and algorithms, and today’s challenges gave me a solid understanding of their core concepts.

Check out my GitHub repository for all the solutions and progress updates at: 100 Days of DSA

Let’s dive into the problems and how I approached them! 🚀


1. Implement a Binary Tree with Insertion and Traversal

Theis program implements a binary tree with insertion and three types of traversal methods: inorder, preorder, and postorder. Nodes are inserted into the tree based on their values, ensuring the left subtree contains smaller values and the right subtree contains larger values. Traversals visit the nodes in specific orders:

  • Inorder: Left subtree → Current node → Right subtree.

  • Preorder: Current node → Left subtree → Right subtree.

  • Postorder: Left subtree → Right subtree → Current node.

Code:

#include <iostream>
using namespace std;

// Node structure for the binary tree
struct Node {
    int data;
    Node* left;
    Node* right;

    Node(int value) {
        data = value;
        left = nullptr;
        right = nullptr;
    }
};

// Class for the binary tree
class binary_tree {
    public:
        Node* root;

        binary_tree () {
            root = nullptr;
        }

        // Function to insert a node into the binary tree
        Node* insert(Node* node, int value) {
            if (node == nullptr) {
                return new Node(value);                     // Create a new node if the current node is null
            }

            if (value < node->data) {
                node->left = insert(node->left, value);     // Recursively insert into the left subtree
            } else {
                node->right = insert(node->right, value);   // Recursively insert into the right subtree
            }

            return node;                                    // Return the current node
        }

        // Inorder traversal: Left -> Root -> Right
        void inorder(Node* node) {
            if (node == nullptr) return;
            inorder(node->left);
            cout << node->data << " ";
            inorder(node->right);
        }

        // Preorder traversal: Root -> Left -> Right
        void preorder(Node* node) {
            if (node == nullptr) return;
            cout << node->data << " ";
            preorder(node->left);
            preorder(node->right);
        }

        // Postorder traversal: Left -> Right -> Root
        void postorder(Node* node) {
            if (node == nullptr) return;
            postorder(node->left);
            postorder(node->right);
            cout << node->data << " ";
        }
};

int main() {
    binary_tree tree;
    tree.root = tree.insert(tree.root, 50);
    tree.insert(tree.root, 30);
    tree.insert(tree.root, 70);
    tree.insert(tree.root, 20);
    tree.insert(tree.root, 40);
    tree.insert(tree.root, 60);
    tree.insert(tree.root, 80);
    cout << "Inorder Traversal: ";
    tree.inorder(tree.root);
    cout << endl;
    cout << "Preorder Traversal: ";
    tree.preorder(tree.root);
    cout << endl;
    cout << "Postorder Traversal: ";
    tree.postorder(tree.root);
    cout << endl;
    return 0;
}

Output:


2. Height of a Binary Tree

This program defines a binary tree structure and calculates its height. Nodes are inserted into the tree such that smaller values go to the left and larger values to the right. The height of the tree is computed recursively by finding the maximum depth of the left and right subtrees and adding 1 for the current node.

Code:

#include <iostream>
using namespace std;

// Node structure for the binary tree
struct Node {
    int data;
    Node* left;
    Node* right;

    Node(int value) {
        data = value;
        left = nullptr;
        right = nullptr;
    }
};

// Class for the binary tree
class binary_tree {
    public:
        Node* root;

        binary_tree() {
            root = nullptr;
        }

        // Function to insert a node into the binary tree
        Node* insert(Node* node, int value) {
            if (node == nullptr) {
                return new Node(value);
            }

            if (value < node->data) {
                node->left = insert(node->left, value);
            } else {
                node->right = insert(node->right, value);
            }

            return node;
        }

        // Function to find the height of the binary tree
        int find_height(Node* node) {
            if (node == nullptr) {
                return 0;   // Base case: height of an empty tree is 0
            }

            // Recursively find the height of left and right subtrees
            int left_height = find_height(node->left);
            int right_height = find_height(node->right);

            // Height is 1 + the maximum of leftHeight and rightHeight
            return 1 + max(left_height, right_height);
        }
};

int main() {
    binary_tree tree;
    tree.root = tree.insert(tree.root, 10);
    tree.insert(tree.root, 5);
    tree.insert(tree.root, 15);
    tree.insert(tree.root, 3);
    tree.insert(tree.root, 7);
    tree.insert(tree.root, 18);
    int height = tree.find_height(tree.root);
    cout << "Height of the binary tree: " << height << endl;
    return 0;
}

Output:


3. Check if Two Binary Trees are Identical

This program defines a binary tree and checks if two binary trees are identical. Nodes are created and inserted into two separate trees. The function are_identical recursively compares the nodes of both trees to ensure their data, left subtrees, and right subtrees match.

Code:

#include <iostream>
using namespace std;

// Node structure for the binary tree
struct Node {
    int data;
    Node* left;
    Node* right;

    Node(int value) {
        data = value;
        left = nullptr;
        right = nullptr;
    }
};

// Function to check if two binary trees are identical
bool are_identical(Node* root1, Node* root2) {
    // Base cases
    if (root1 == nullptr && root2 == nullptr) {
        return true;    // Both trees are empty
    }
    if (root1 == nullptr || root2 == nullptr) {
        return false;   // One tree is empty, and the other is not
    }

    // Check if the data of the current nodes match and recurse for left and right subtrees
    return (root1->data == root2->data) &&
           are_identical(root1->left, root2->left) &&
           are_identical(root1->right, root2->right);
}

// Function to insert a node into the binary tree
Node* insert(Node* root, int value) {
    if (root == nullptr) {
        return new Node(value);
    }

    if (value < root->data) {
        root->left = insert(root->left, value);
    } else {
        root->right = insert(root->right, value);
    }

    return root;
}

int main() {
    Node* root1 = nullptr;
    root1 = insert(root1, 10);
    insert(root1, 5);
    insert(root1, 15);
    insert(root1, 3);
    insert(root1, 7);

    Node* root2 = nullptr;
    root2 = insert(root2, 10);
    insert(root2, 5);
    insert(root2, 15);
    insert(root2, 3);
    insert(root2, 7);

    if (are_identical(root1, root2)) {
        cout << "The two binary trees are identical." << endl;
    } else {
        cout << "The two binary trees are not identical." << endl;
    }
    return 0;
}

Output:


4. Diameter of a Binary Tree

This program calculates the diameter of a binary tree, which is the longest path between any two nodes. It recursively computes the height of the left and right subtrees for each node, updating the diameter as the sum of these heights. A helper function calculate_diameter tracks the height and diameter simultaneously, while find_diameter initiates the process.

Code:

#include <iostream>
using namespace std;

// Node structure for the binary tree
struct Node {
    int data;
    Node* left;
    Node* right;

    Node(int value) {
        data = value;
        left = nullptr;
        right = nullptr;
    }
};

// Function to calculate the height of the tree and update the diameter
int calculate_diameter(Node* root, int& diameter) {
    if (root == nullptr) {
        return 0; // Base case: height of an empty tree is 0
    }

    // Calculate the height of the left and right subtrees
    int leftHeight = calculate_diameter(root->left, diameter);
    int rightHeight = calculate_diameter(root->right, diameter);

    // Update the diameter
    diameter = max(diameter, leftHeight + rightHeight);

    // Return the height of the current tree
    return 1 + max(leftHeight, rightHeight);
}

// Function to find the diameter of the binary tree
int find_diameter(Node* root) {
    int diameter = 0;
    calculate_diameter(root, diameter);
    return diameter;
}

// Function to insert a node into the binary tree
Node* insert(Node* root, int value) {
    if (root == nullptr) {
        return new Node(value);
    }

    if (value < root->data) {
        root->left = insert(root->left, value);
    } else {
        root->right = insert(root->right, value);
    }

    return root;
}

int main() {
    Node* root = nullptr;
    root = insert(root, 10);
    insert(root, 5);
    insert(root, 15);
    insert(root, 3);
    insert(root, 7);
    insert(root, 12);
    insert(root, 17);
    int diameter = find_diameter(root);
    cout << "The diameter of the binary tree is: " << diameter << endl;
    return 0;
}

Output:


5. Level-Order Traversal of a Binary Tree

This program performs level-order traversal of a binary tree using a queue to visit nodes level by level. It starts with the root, enqueues its children, and processes nodes in the order they are dequeued. The insert function constructs a binary search tree, and the main function demonstrates traversal on a sample tree by printing the nodes in level-wise order.

Code:

#include <iostream>
#include <queue>
using namespace std;

// Node structure for the binary tree
struct Node {
    int data;
    Node* left;
    Node* right;

    Node(int value) {
        data = value;
        left = nullptr;
        right = nullptr;
    }
};

// Function to perform level-order traversal
void level_order_traversal(Node* root) {
    if (root == nullptr) {
        return;
    }

    // Create a queue to hold nodes at each level
    queue<Node*> q;
    q.push(root);

    while (!q.empty()) {
        // Get the current node
        Node* current = q.front();
        q.pop();

        // Print the current node's data
        cout << current->data << " ";

        // Enqueue the left child if it exists
        if (current->left != nullptr) {
            q.push(current->left);
        }

        // Enqueue the right child if it exists
        if (current->right != nullptr) {
            q.push(current->right);
        }
    }
    cout << endl;
}

// Function to insert a node into the binary tree
Node* insert(Node* root, int value) {
    if (root == nullptr) {
        return new Node(value);
    }

    if (value < root->data) {
        root->left = insert(root->left, value);
    } else {
        root->right = insert(root->right, value);
    }

    return root;
}

int main() {
    Node* root = nullptr;
    root = insert(root, 10);
    insert(root, 5);
    insert(root, 15);
    insert(root, 3);
    insert(root, 7);
    insert(root, 12);
    insert(root, 17);
    cout << "Level-order traversal of the binary tree: ";
    level_order_traversal(root);
    return 0;
}

Output:


Day 18 offered a strong foundation in binary trees, highlighting key concepts like construction and traversal. Understanding these basics is crucial for tackling more complex tree problems in the future. I'm excited to build on this knowledge as I move forward with more advanced challenges! 🚀