Day 19: Binary Trees Advanced

Day 19: Binary Trees Advanced

Welcome to Day 19 of my 100 Days of DSA challenge! Today, I tackled five problems focused on advanced binary tree concepts. From structural checks to transformations and various traversal techniques, these problems deepened my understanding of binary trees and how to manipulate them efficiently.

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

Let’s dive in and continue growing! 🚀


1. Check if a Binary Tree is Balanced

This program defines a binary tree and checks if it is balanced. It uses a recursive function to calculate the height of each subtree while simultaneously checking if the height difference between the left and right subtrees of each node is more than 1. If any subtree is unbalanced, the tree is marked as unbalanced.

Code:

#include <iostream>
#include <algorithm>
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 the height of the tree and check if it's balanced
int check_balance(Node* root, bool& isBalanced) {
    if (root == nullptr) {
        return 0; // Base case: height of an empty tree is 0
    }

    // Recursively get the height of left and right subtrees
    int leftHeight = check_balance(root->left, isBalanced);
    int rightHeight = check_balance(root->right, isBalanced);

    // If the left or right subtree is unbalanced, propagate false
    if (leftHeight == -1 || rightHeight == -1 || abs(leftHeight - rightHeight) > 1) {
        isBalanced = false;
        return -1; // Return -1 to indicate an unbalanced tree
    }

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

// Function to check if the tree is balanced
bool is_balanced_tree(Node* root) {
    bool isBalanced = true;
    check_balance(root, isBalanced);
    return isBalanced;
}

// 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);
    if (is_balanced_tree(root)) {
        cout << "The binary tree is balanced." << endl;
    } else {
        cout << "The binary tree is not balanced." << endl;
    }
    return 0;
}

Output:


2. Lowest Common Ancestor of Two Nodes in a Binary Tree

The program defines a binary tree and implements a function to find the lowest common ancestor (LCA) of two nodes. It starts by recursively checking if the current node is either of the target nodes. If not, it searches both the left and right subtrees. If both subtrees return non-null values, the current node is the LCA. Otherwise, it returns the node found in the non-null subtree.

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 find the lowest common ancestor of two nodes
Node* find_lca(Node* root, int n1, int n2) {
    // Base case: if the tree is empty or if we reach a leaf node
    if (root == nullptr) {
        return nullptr;
    }

    // If either n1 or n2 matches the root's data, return root
    if (root->data == n1 || root->data == n2) {
        return root;
    }

    // Recur for left and right subtrees
    Node* left_lca = find_lca(root->left, n1, n2);
    Node* right_lca = find_lca(root->right, n1, n2);

    // If both left and right calls return non-null, the current node is the LCA
    if (left_lca && right_lca) {
        return root;
    }

    // Otherwise, return the non-null child (either left or right)
    return (left_lca != nullptr) ? left_lca : right_lca;
}

// 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;
}

// Function to print the result
void print_lca(Node* lca) {
    if (lca != nullptr) {
        cout << "Lowest Common Ancestor: " << lca->data << endl;
    } else {
        cout << "Lowest Common Ancestor not found." << endl;
    }
}

int main() {
    // Create a binary tree
    Node* root = nullptr;
    root = insert(root, 20);
    insert(root, 10);
    insert(root, 30);
    insert(root, 5);
    insert(root, 15);
    insert(root, 25);
    insert(root, 35);
    int n1 = 5, n2 = 15; // Nodes to find the LCA for
    Node* lca = find_lca(root, n1, n2);
    print_lca(lca);
    return 0;
}

Output:


3. Convert a Binary Tree to its Mirror

This program defines a binary tree and implements a function to convert it into its mirror image. The mirror function recursively swaps the left and right children of each node in the tree. The insert function is used to build the binary tree by inserting nodes, while the inorder function prints the inorder traversal of the tree.

Code:

#include <iostream>
using namespace std;

// Structure for a node in the binary tree
struct Node {
    int data;
    Node* left;
    Node* right;

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

// Function to convert a binary tree to its mirror
void mirror(Node* root) {
    if (root == nullptr) {
        return;
    }

    // Swap the left and right child
    swap(root->left, root->right);

    // Recursively mirror the left and right subtrees
    mirror(root->left);
    mirror(root->right);
}

// Function to print inorder traversal of the tree
void inorder(Node* root) {
    if (root == nullptr) {
        return;
    }

    inorder(root->left);
    cout << root->data << " ";
    inorder(root->right);
}

// Function to insert a node in the binary tree (basic BST insertion for simplicity)
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, 20);
    insert(root, 10);
    insert(root, 30);
    insert(root, 5);
    insert(root, 15);
    insert(root, 25);
    insert(root, 35);
    cout << "Inorder traversal of original tree: ";
    inorder(root);
    cout << endl;
    mirror(root);
    cout << "Inorder traversal of mirrored tree: ";
    inorder(root);
    cout << endl;
    return 0;
}

Output:


4. Zig-Zag Level Order Traversal of a Binary Tree

This program implements a zigzag level order traversal of a binary tree. It uses a queue for level-order traversal and a stack to reverse the order of nodes at alternate levels. The direction of traversal alternates after each level: left to right for even levels and right to left for odd levels. At each level, the nodes are processed, and if the traversal is right to left, their values are pushed onto a stack. After processing a level, if the traversal was right to left, the stack is emptied to print the values in reverse order.

Code:

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

// Definition for a binary tree node.
struct Node {
    int data;
    Node* left;
    Node* right;

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

// Function to perform zigzag level order traversal
void zigzag_level_order(Node* root) {
    if (!root) return;

    queue<Node*> q;                 // Queue for level order traversal
    stack<int> levelStack;          // Stack to reverse the order on alternate levels
    bool leftToRight = true;        // Flag to check the direction of traversal

    q.push(root);                   // Start with the root node

    while (!q.empty()) {
        int levelSize = q.size();   // Number of nodes at current level

        for (int i = 0; i < levelSize; ++i) {
            Node* currentNode = q.front();
            q.pop();

            // If leftToRight is false, push the node data to stack to reverse order
            if (leftToRight) {
                cout << currentNode->data << " ";
            } else {
                levelStack.push(currentNode->data);
            }

            // Add left and right children to the queue for next level
            if (currentNode->left) q.push(currentNode->left);
            if (currentNode->right) q.push(currentNode->right);
        }

        // If leftToRight is false, print the elements from the stack
        if (!leftToRight) {
            while (!levelStack.empty()) {
                cout << levelStack.top() << " ";
                levelStack.pop();
            }
        }

        // Alternate the direction for the next level
        leftToRight = !leftToRight;
        cout << endl;
    }
}

// Function to insert nodes in the binary tree (for testing purposes)
Node* insert(Node* root, int value) {
    if (!root) 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, 1);
    insert(root, 2);
    insert(root, 3);
    insert(root, 4);
    insert(root, 5);
    insert(root, 6);
    insert(root, 7);
    cout << "Zigzag level order traversal: " << endl;
    zigzag_level_order(root);
    return 0;
}

Output:


5. Flatten a Binary Tree to a Linked List

This program flattens a binary tree into a linked list in-place by performing a preorder traversal. It recursively flattens the left and right subtrees, then moves the left child to the right, and connects the original right subtree to the end of the new right subtree. This transformation results in a linked list where each node’s right pointer points to the next node in the preorder traversal, and all left pointers are set to nullptr.

Code:

#include <iostream>
using namespace std;

// Definition for a binary tree node.
struct Node {
    int data;
    Node* left;
    Node* right;

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

// Function to flatten the binary tree
void flatten(Node* root) {
    if (!root) return;

    // Recursively flatten the left and right subtrees
    if (root->left) {
        flatten(root->left);

        // Save the right child before overwriting
        Node* temp = root->right;

        // Move the left child to the right
        root->right = root->left;
        root->left = nullptr;

        // Move to the last node in the new right subtree (which was the left subtree)
        Node* current = root->right;
        while (current->right) {
            current = current->right;
        }

        // Connect the original right subtree to the last node of the new right subtree
        current->right = temp;
    }

    // Flatten the right subtree
    flatten(root->right);
}

// Function to print the flattened binary tree (linked list)
void print_flattened(Node* root) {
    Node* current = root;
    while (current) {
        cout << current->data << " ";
        current = current->right;
    }
    cout << endl;
}

// Function to insert nodes in the binary tree (for testing purposes)
Node* insert(Node* root, int value) {
    if (!root) 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, 1);
    insert(root, 2);
    insert(root, 3);
    insert(root, 4);
    insert(root, 5);
    insert(root, 6);
    flatten(root);
    cout << "Flattened binary tree to linked list: ";
    print_flattened(root);
    return 0;
}

Output:


Day 19 expanded my understanding of binary trees by diving into more complex operations and traversal strategies. These problems highlighted the practical applications of binary trees and reinforced how recursion can be effectively used to solve intricate tasks. Excited to continue building on this knowledge in the upcoming days! 🚀