Trees

Master tree data structures in C++ with implementation, traversals, binary search trees, AVL trees, tries, and advanced problems like level order traversal, lowest common ancestor, and more.


Trees are hierarchical data structures with a root value and subtrees of children, represented as a set of linked nodes. Let's explore their implementations, algorithms, and common problems.

1. Basics & Structure

A tree node contains data and pointers to its children. The most common tree is a binary tree, where each node has at most two children.

struct TreeNode {
    int data;
    TreeNode* left;
    TreeNode* right;
 
    TreeNode(int val) {
        data = val;
        left = nullptr;
        right = nullptr;
    }
};

2. Tree Traversals

In-order Traversal

Visits left subtree, then root, then right subtree (produces sorted values for a BST).

void inOrderTraversal(TreeNode* root) {
    if (!root) return;           // Base case: empty tree
 
    inOrderTraversal(root->left);  // Visit left subtree
    cout << root->data << " ";     // Visit root
    inOrderTraversal(root->right); // Visit right subtree
}

Pre-order Traversal

Visits root, then left subtree, then right subtree (useful for copying trees).

void preOrderTraversal(TreeNode* root) {
    if (!root) return;           // Base case: empty tree
 
    cout << root->data << " ";     // Visit root
    preOrderTraversal(root->left);  // Visit left subtree
    preOrderTraversal(root->right); // Visit right subtree
}

Post-order Traversal

Visits left subtree, then right subtree, then root (useful for deletion).

void postOrderTraversal(TreeNode* root) {
    if (!root) return;           // Base case: empty tree
 
    postOrderTraversal(root->left);  // Visit left subtree
    postOrderTraversal(root->right); // Visit right subtree
    cout << root->data << " ";     // Visit root
}

3. Binary Search Tree (BST)

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.

Insert Node

Inserts a new node into the BST, maintaining the BST property.

TreeNode* insert(TreeNode* root, int val) {
    // If tree is empty, create a new node and return it as root
    if (!root) return new TreeNode(val);
 
    // Otherwise, recur down the tree
    if (val < root->data)
        root->left = insert(root->left, val);  // Insert in left subtree
    else if (val > root->data)
        root->right = insert(root->right, val); // Insert in right subtree
 
    // Equal values are not inserted (assuming no duplicates in BST)
 
    return root; // Return unchanged root pointer
}

Search Node

Searches for a value in the BST.

TreeNode* search(TreeNode* root, int val) {
    // Base cases: root is null or key is present at root
    if (!root || root->data == val)
        return root;
 
    // Key is greater than root's key
    if (val > root->data)
        return search(root->right, val);
 
    // Key is smaller than root's key
    return search(root->left, val);
}

Delete Node

Deletes a node from the BST, maintaining the BST property.

TreeNode* findMin(TreeNode* node) {
    TreeNode* current = node;
    while (current && current->left)
        current = current->left;
    return current;
}
 
TreeNode* deleteNode(TreeNode* root, int val) {
    // Base case: Empty tree
    if (!root) return nullptr;
 
    // Recur down the tree
    if (val < root->data)
        root->left = deleteNode(root->left, val);
    else if (val > root->data)
        root->right = deleteNode(root->right, val);
    else {
        // Node with only one child or no child
        if (!root->left) {
            TreeNode* temp = root->right;
            delete root;
            return temp;
        } else if (!root->right) {
            TreeNode* temp = root->left;
            delete root;
            return temp;
        }
 
        // Node with two children: Get the inorder successor (smallest in right subtree)
        TreeNode* temp = findMin(root->right);
 
        // Copy the inorder successor's content to this node
        root->data = temp->data;
 
        // Delete the inorder successor
        root->right = deleteNode(root->right, temp->data);
    }
    return root;
}

4. Level Order Traversal (BFS)

Traverses the tree level by level using a queue.

void levelOrderTraversal(TreeNode* root) {
    if (!root) return;
 
    queue<TreeNode*> q;
    q.push(root);
 
    while (!q.empty()) {
        int levelSize = q.size();  // Number of nodes at current level
 
        for (int i = 0; i < levelSize; i++) {
            TreeNode* node = q.front();
            q.pop();
 
            cout << node->data << " ";  // Process current node
 
            // Add children to queue for next level
            if (node->left) q.push(node->left);
            if (node->right) q.push(node->right);
        }
        cout << endl;  // New line after each level
    }
}

5. Height and Depth

Height of a Tree

The maximum distance from the root to any leaf node.

int heightOfTree(TreeNode* root) {
    if (!root) return 0;  // Height of empty tree is 0
 
    // Height of tree is max height of left or right subtree + 1
    int leftHeight = heightOfTree(root->left);
    int rightHeight = heightOfTree(root->right);
 
    return max(leftHeight, rightHeight) + 1;
}

Depth of a Node

The distance from the root to the node.

int depthOfNode(TreeNode* root, TreeNode* node, int depth = 0) {
    if (!root) return -1;  // Node not found
 
    if (root == node) return depth;  // Found the node
 
    // Check in left and right subtrees
    int leftDepth = depthOfNode(root->left, node, depth + 1);
    if (leftDepth != -1) return leftDepth;
 
    return depthOfNode(root->right, node, depth + 1);
}

6. Lowest Common Ancestor (LCA)

Finds the lowest common ancestor of two nodes in a binary tree.

TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    // Base case
    if (!root || root == p || root == q)
        return root;
 
    // Look for keys in left and right subtrees
    TreeNode* leftLCA = lowestCommonAncestor(root->left, p, q);
    TreeNode* rightLCA = lowestCommonAncestor(root->right, p, q);
 
    // If both keys are found in different subtrees, then root is LCA
    if (leftLCA && rightLCA)
        return root;
 
    // Otherwise, return the non-null value
    return (leftLCA != nullptr) ? leftLCA : rightLCA;
}

7. Diameter of Binary Tree

The longest path between any two nodes in a tree.

int diameterOfTree(TreeNode* root, int* height = nullptr) {
    // Height of left and right subtrees
    int lh = 0, rh = 0;
 
    // Left and right diameters
    int ld = 0, rd = 0;
 
    if (!root) {
        if (height) *height = 0;
        return 0;
    }
 
    // Get diameters and heights of left and right subtrees
    ld = diameterOfTree(root->left, &lh);
    rd = diameterOfTree(root->right, &rh);
 
    // Update height for current node
    if (height) *height = max(lh, rh) + 1;
 
    // Return max of: left diameter, right diameter, or path through root
    return max({ld, rd, lh + rh + 1});
}

8. Balanced Binary Tree

Checks if a binary tree is height-balanced (difference in height of left and right subtrees is not more than 1).

bool isBalanced(TreeNode* root, int* height = nullptr) {
    // Heights of left and right subtrees
    int lh = 0, rh = 0;
 
    // Base case: empty tree is balanced
    if (!root) {
        if (height) *height = 0;
        return true;
    }
 
    // Check if left and right subtrees are balanced
    bool leftBalanced = isBalanced(root->left, &lh);
    bool rightBalanced = isBalanced(root->right, &rh);
 
    // Update height
    if (height) *height = max(lh, rh) + 1;
 
    // Tree is balanced if:
    // 1. Left subtree is balanced
    // 2. Right subtree is balanced
    // 3. Difference in heights is not more than 1
    return leftBalanced && rightBalanced && abs(lh - rh) <= 1;
}

9. Advanced Tree Structures

AVL Tree

A self-balancing binary search tree where the heights of the two child subtrees differ by at most one.

struct AVLNode {
    int data;
    AVLNode* left;
    AVLNode* right;
    int height;  // Height of the node
 
    AVLNode(int val) {
        data = val;
        left = nullptr;
        right = nullptr;
        height = 1;  // New node is initially at height 1
    }
};
 
// Get height of node
int height(AVLNode* node) {
    if (!node) return 0;
    return node->height;
}
 
// Get balance factor
int getBalance(AVLNode* node) {
    if (!node) return 0;
    return height(node->left) - height(node->right);
}
 
// Right rotate subtree rooted with y
AVLNode* rightRotate(AVLNode* y) {
    AVLNode* x = y->left;
    AVLNode* T2 = x->right;
 
    // Perform rotation
    x->right = y;
    y->left = T2;
 
    // Update heights
    y->height = max(height(y->left), height(y->right)) + 1;
    x->height = max(height(x->left), height(x->right)) + 1;
 
    return x;  // New root
}
 
// Left rotate subtree rooted with x
AVLNode* leftRotate(AVLNode* x) {
    AVLNode* y = x->right;
    AVLNode* T2 = y->left;
 
    // Perform rotation
    y->left = x;
    x->right = T2;
 
    // Update heights
    x->height = max(height(x->left), height(x->right)) + 1;
    y->height = max(height(y->left), height(y->right)) + 1;
 
    return y;  // New root
}
 
// Insert a node
AVLNode* insert(AVLNode* node, int val) {
    // 1. Perform standard BST insertion
    if (!node) return new AVLNode(val);
 
    if (val < node->data)
        node->left = insert(node->left, val);
    else if (val > node->data)
        node->right = insert(node->right, val);
    else  // Equal values not allowed
        return node;
 
    // 2. Update height of this ancestor node
    node->height = 1 + max(height(node->left), height(node->right));
 
    // 3. Get the balance factor
    int balance = getBalance(node);
 
    // Left Left Case
    if (balance > 1 && val < node->left->data)
        return rightRotate(node);
 
    // Right Right Case
    if (balance < -1 && val > node->right->data)
        return leftRotate(node);
 
    // Left Right Case
    if (balance > 1 && val > node->left->data) {
        node->left = leftRotate(node->left);
        return rightRotate(node);
    }
 
    // Right Left Case
    if (balance < -1 && val < node->right->data) {
        node->right = rightRotate(node->right);
        return leftRotate(node);
    }
 
    // Return unchanged node pointer
    return node;
}

Trie (Prefix Tree)

A tree-like data structure used to store a dynamic set of strings, particularly useful for quick prefix searches.

struct TrieNode {
    TrieNode* children[26];  // Assuming only lowercase English letters
    bool isEndOfWord;
 
    TrieNode() {
        for (int i = 0; i < 26; i++)
            children[i] = nullptr;
        isEndOfWord = false;
    }
};
 
class Trie {
private:
    TrieNode* root;
 
public:
    Trie() {
        root = new TrieNode();
    }
 
    // Insert a word into the trie
    void insert(string word) {
        TrieNode* crawler = root;
        for (char c : word) {
            int index = c - 'a';
            if (!crawler->children[index])
                crawler->children[index] = new TrieNode();
            crawler = crawler->children[index];
        }
        crawler->isEndOfWord = true;
    }
 
    // Search for a word in the trie
    bool search(string word) {
        TrieNode* crawler = root;
        for (char c : word) {
            int index = c - 'a';
            if (!crawler->children[index])
                return false;
            crawler = crawler->children[index];
        }
        return crawler->isEndOfWord;
    }
 
    // Check if any word with given prefix exists
    bool startsWith(string prefix) {
        TrieNode* crawler = root;
        for (char c : prefix) {
            int index = c - 'a';
            if (!crawler->children[index])
                return false;
            crawler = crawler->children[index];
        }
        return true;
    }
};

10. Practice Problems and Examples

Serialize and Deserialize Binary Tree

Converts a binary tree to a string and back.

// Encodes a tree to a single string
string serialize(TreeNode* root) {
    if (!root) return "X,";  // X represents null
 
    string leftSerialized = serialize(root->left);
    string rightSerialized = serialize(root->right);
 
    return to_string(root->data) + "," + leftSerialized + rightSerialized;
}
 
// Helper function for deserialize
TreeNode* deserializeHelper(queue<string>& nodes) {
    string val = nodes.front();
    nodes.pop();
 
    if (val == "X") return nullptr;
 
    TreeNode* root = new TreeNode(stoi(val));
    root->left = deserializeHelper(nodes);
    root->right = deserializeHelper(nodes);
 
    return root;
}
 
// Decodes your encoded data to tree
TreeNode* deserialize(string data) {
    queue<string> nodes;
    string val;
 
    // Split the string by commas
    for (char c : data) {
        if (c == ',') {
            nodes.push(val);
            val.clear();
        } else {
            val.push_back(c);
        }
    }
    if (!val.empty()) nodes.push(val);
 
    return deserializeHelper(nodes);
}

Use Case: Storing trees in databases or transmitting them over networks.

Check if Binary Tree is a Binary Search Tree

Verifies if a binary tree follows the BST property.

bool isBST(TreeNode* root, TreeNode* min = nullptr, TreeNode* max = nullptr) {
    // An empty tree is a BST
    if (!root) return true;
 
    // Check if current node's value is within valid range
    if (min && root->data <= min->data) return false;
    if (max && root->data >= max->data) return false;
 
    // Check recursively for left and right subtrees
    // Left subtree: all nodes must be less than root
    // Right subtree: all nodes must be greater than root
    return isBST(root->left, min, root) && isBST(root->right, root, max);
}

Use Case: Validating BST property before performing BST-specific operations.

Path Sum

Checks if there is a root-to-leaf path that sums to a given target.

bool hasPathSum(TreeNode* root, int targetSum) {
    // Base case: empty tree
    if (!root) return false;
 
    // Subtract current node's value from target
    targetSum -= root->data;
 
    // If this is a leaf node and target became 0, we found a valid path
    if (!root->left && !root->right)
        return targetSum == 0;
 
    // Otherwise, check both subtrees
    return hasPathSum(root->left, targetSum) || hasPathSum(root->right, targetSum);
}

Use Case: Finding financial transaction paths or decision trees.

Construct Binary Tree from Inorder and Preorder

Reconstructs a binary tree from its inorder and preorder traversals.

TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
    unordered_map<int, int> inorderMap;
    for (int i = 0; i < inorder.size(); i++)
        inorderMap[inorder[i]] = i;
 
    return buildTreeHelper(preorder, 0, preorder.size() - 1, inorder, 0, inorder.size() - 1, inorderMap);
}
 
TreeNode* buildTreeHelper(vector<int>& preorder, int preStart, int preEnd,
                         vector<int>& inorder, int inStart, int inEnd,
                         unordered_map<int, int>& inorderMap) {
    if (preStart > preEnd || inStart > inEnd)
        return nullptr;
 
    // Root is first element in preorder traversal
    TreeNode* root = new TreeNode(preorder[preStart]);
 
    // Find position of root in inorder traversal
    int inRoot = inorderMap[root->data];
 
    // Calculate size of left subtree
    int leftSize = inRoot - inStart;
 
    // Build left and right subtrees recursively
    root->left = buildTreeHelper(preorder, preStart + 1, preStart + leftSize,
                                inorder, inStart, inRoot - 1, inorderMap);
    root->right = buildTreeHelper(preorder, preStart + leftSize + 1, preEnd,
                                 inorder, inRoot + 1, inEnd, inorderMap);
 
    return root;
}

Use Case: Tree reconstruction in databases, file systems, or expression parsing.

Vertical Order Traversal

Traverses a binary tree vertically.

vector<vector<int>> verticalOrder(TreeNode* root) {
    vector<vector<int>> result;
    if (!root) return result;
 
    // Map to store nodes at each horizontal distance
    map<int, vector<int>> columnTable;
 
    // Queue for level order traversal with horizontal distance
    queue<pair<TreeNode*, int>> q;
    q.push({root, 0});
 
    while (!q.empty()) {
        auto [node, hd] = q.front();
        q.pop();
 
        columnTable[hd].push_back(node->data);
 
        if (node->left) q.push({node->left, hd - 1});
        if (node->right) q.push({node->right, hd + 1});
    }
 
    // Extract result from the map
    for (auto& column : columnTable)
        result.push_back(column.second);
 
    return result;
}

Use Case: Visualizing trees, organizing hierarchical data by columns.

11. Summary of Techniques

TechniqueUse Case
In-order TraversalVisit nodes in sorted order in BST
Pre-order TraversalCreate a copy of the tree, prefix expression trees
Post-order TraversalDelete a tree, postfix expression trees
Level Order TraversalProcess tree by levels (breadth-first)
BST OperationsEfficient search, insert, delete in sorted data
Tree BalancingMaintain O(log n) height for operations (AVL, Red-Black)
Lowest Common AncestorFind relationships in hierarchies, taxonomies
Path SumFind specific paths in decision trees
TrieEfficient prefix search, autocomplete, word dictionaries
SerializationStore & transmit tree structures
Vertical OrderOrganize nodes by horizontal distance
Tree ConstructionBuild trees from traversals or sorted arrays

This summary equips you with the techniques needed to master classic and advanced tree problems!