Trees

Data Structures & Algorithms

Trees

Hierarchical Data

A tree is a connected acyclic graph where one node is designated as the root. It models hierarchical relationships — things that have parents and children.

Real-world analogies: - File system: folders containing files and subfolders — a tree - Company org chart: CEO at root, departments as children, employees as leaves - HTML DOM: <html> at root, <head> and <body> as children - Decision making: each node is a question, each edge is an answer


Part 1 — Binary Tree

Node Structure

struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

Tree Terminology

          1          ← root
        /   \
       2     3       ← internal nodes
      / \     \
     4   5     6     ← 4,5,6 are leaves

Height: 3 (longest path from root to leaf)
Depth of node 5: 2 (distance from root)
Level: root is level 1 (or 0, depending on convention)
Degree: number of children (node 1 has degree 2)

Part 2 — Tree Traversals

Traversals are how you visit every node. The order matters for different applications.

Depth-First Traversals

Inorder (Left → Root → Right):

void inorder(TreeNode* root) {
    if (!root) return;
    inorder(root->left);
    cout << root->val << " ";
    inorder(root->right);
}
// Output for BST: sorted order!
// Tree above: 4 2 5 1 3 6

Preorder (Root → Left → Right):

void preorder(TreeNode* root) {
    if (!root) return;
    cout << root->val << " ";
    preorder(root->left);
    preorder(root->right);
}
// Used to: serialize tree, create copy
// Tree above: 1 2 4 5 3 6

Postorder (Left → Right → Root):

void postorder(TreeNode* root) {
    if (!root) return;
    postorder(root->left);
    postorder(root->right);
    cout << root->val << " ";
}
// Used to: delete tree, evaluate expression trees
// Tree above: 4 5 2 6 3 1

Iterative Inorder (using stack):

vector<int> inorderIterative(TreeNode* root) {
    vector<int> result;
    stack<TreeNode*> st;
    TreeNode* curr = root;

    while (curr || !st.empty()) {
        while (curr) {
            st.push(curr);
            curr = curr->left;
        }
        curr = st.top(); st.pop();
        result.push_back(curr->val);
        curr = curr->right;
    }
    return result;
}

Breadth-First / Level Order Traversal

vector<vector<int>> levelOrder(TreeNode* root) {
    if (!root) return {};
    vector<vector<int>> result;
    queue<TreeNode*> q;
    q.push(root);

    while (!q.empty()) {
        int levelSize = q.size();
        vector<int> level;
        for (int i = 0; i < levelSize; i++) {
            TreeNode* node = q.front(); q.pop();
            level.push_back(node->val);
            if (node->left) q.push(node->left);
            if (node->right) q.push(node->right);
        }
        result.push_back(level);
    }
    return result;
}
// [[1], [2,3], [4,5,6]]

Part 3 — Binary Search Tree (BST)

A BST maintains the BST property: for every node, all values in the left subtree are smaller, all values in the right subtree are larger.

Analogy: A sorted filing system. To find a file, look at the current drawer label — too high, go left; too low, go right. You never need to check most drawers.

       8
      / \
     3   10
    / \    \
   1   6    14
      / \   /
     4   7 13

BST Operations

class BST {
    TreeNode* root = nullptr;

    TreeNode* insert(TreeNode* node, int val) {
        if (!node) return new TreeNode(val);
        if (val < node->val) node->left = insert(node->left, val);
        else if (val > node->val) node->right = insert(node->right, val);
        return node;
    }

    TreeNode* search(TreeNode* node, int val) {
        if (!node || node->val == val) return node;
        if (val < node->val) return search(node->left, val);
        return search(node->right, val);
    }

    TreeNode* findMin(TreeNode* node) {
        while (node->left) node = node->left;
        return node;
    }

    TreeNode* deleteNode(TreeNode* node, int val) {
        if (!node) return nullptr;
        if (val < node->val) node->left = deleteNode(node->left, val);
        else if (val > node->val) node->right = deleteNode(node->right, val);
        else {
            // Case 1: leaf node
            if (!node->left && !node->right) { delete node; return nullptr; }
            // Case 2: one child
            if (!node->left) { TreeNode* t = node->right; delete node; return t; }
            if (!node->right) { TreeNode* t = node->left; delete node; return t; }
            // Case 3: two children — replace with inorder successor
            TreeNode* successor = findMin(node->right);
            node->val = successor->val;
            node->right = deleteNode(node->right, successor->val);
        }
        return node;
    }

public:
    void insert(int val) { root = insert(root, val); }
    bool search(int val) { return search(root, val) != nullptr; }
    void remove(int val) { root = deleteNode(root, val); }
};
// All operations: O(h) where h = height
// Balanced BST: O(log n), Degenerate (linked list): O(n)

BST Validation

bool isValidBST(TreeNode* root, long min = LLONG_MIN, long max = LLONG_MAX) {
    if (!root) return true;
    if (root->val <= min || root->val >= max) return false;
    return isValidBST(root->left, min, root->val) &&
           isValidBST(root->right, root->val, max);
}

Part 4 — Common Tree Problems

Height of Tree

int height(TreeNode* root) {
    if (!root) return 0;
    return 1 + max(height(root->left), height(root->right));
}

Diameter of Binary Tree

Longest path between any two nodes (may not pass through root).

int diameter(TreeNode* root) {
    int maxDiam = 0;
    function<int(TreeNode*)> height = [&](TreeNode* node) -> int {
        if (!node) return 0;
        int left = height(node->left);
        int right = height(node->right);
        maxDiam = max(maxDiam, left + right);
        return 1 + max(left, right);
    };
    height(root);
    return maxDiam;
}

Lowest Common Ancestor (LCA)

The deepest node that is an ancestor of both p and q.

TreeNode* LCA(TreeNode* root, TreeNode* p, TreeNode* q) {
    if (!root || root == p || root == q) return root;
    TreeNode* left = LCA(root->left, p, q);
    TreeNode* right = LCA(root->right, p, q);
    if (left && right) return root;  // p and q in different subtrees
    return left ? left : right;
}

Path Sum

Check if any root-to-leaf path has a given sum.

bool hasPathSum(TreeNode* root, int target) {
    if (!root) return false;
    if (!root->left && !root->right) return root->val == target;
    return hasPathSum(root->left, target - root->val) ||
           hasPathSum(root->right, target - root->val);
}

Maximum Path Sum

Path between any two nodes.

int maxPathSum(TreeNode* root) {
    int maxSum = INT_MIN;
    function<int(TreeNode*)> dfs = [&](TreeNode* node) -> int {
        if (!node) return 0;
        int left = max(0, dfs(node->left));   // ignore negative paths
        int right = max(0, dfs(node->right));
        maxSum = max(maxSum, node->val + left + right);
        return node->val + max(left, right);  // can only extend in one direction
    };
    dfs(root);
    return maxSum;
}

Serialize and Deserialize

Convert tree to/from string representation.

string serialize(TreeNode* root) {
    if (!root) return "null,";
    return to_string(root->val) + "," +
           serialize(root->left) + serialize(root->right);
}

TreeNode* deserialize(string data) {
    queue<string> tokens;
    stringstream ss(data);
    string token;
    while (getline(ss, token, ',')) tokens.push(token);

    function<TreeNode*()> build = [&]() -> TreeNode* {
        string val = tokens.front(); tokens.pop();
        if (val == "null") return nullptr;
        TreeNode* node = new TreeNode(stoi(val));
        node->left = build();
        node->right = build();
        return node;
    };
    return build();
}

Part 5 — Balanced Trees

AVL Tree (Self-Balancing BST)

Maintains height balance: for every node, |height(left) - height(right)| ≤ 1.

Balance factor = height(left) - height(right)

When balance factor becomes ±2 after an insert/delete, rotations restore balance.

LL Rotation (single right rotate):
    z                 y
   /                 / \
  y         →       x   z
 /
x

LR Rotation (left-right double rotate):
    z                 x
   /                 / \
  y         →       y   z
   \
    x
int getHeight(TreeNode* node) { return node ? node->height : 0; }
int getBalance(TreeNode* node) {
    return node ? getHeight(node->left) - getHeight(node->right) : 0;
}

TreeNode* rightRotate(TreeNode* z) {
    TreeNode* y = z->left;
    TreeNode* T3 = y->right;
    y->right = z;
    z->left = T3;
    z->height = 1 + max(getHeight(z->left), getHeight(z->right));
    y->height = 1 + max(getHeight(y->left), getHeight(y->right));
    return y;
}

All BST operations remain O(log n) because height is always O(log n).


Practice Problems

Easy: 1. Find the maximum element in a binary tree. 2. Count the number of nodes in a binary tree. 3. Check if two binary trees are identical.

Medium: 4. Find all root-to-leaf paths in a binary tree. 5. Convert a sorted array to a height-balanced BST. 6. Zigzag level order traversal. 7. Right side view of a binary tree.

Hard: 8. Recover a BST where two nodes are swapped. 9. Flatten a binary tree to linked list in-place. 10. Binary tree maximum path sum.


Answers to Selected Problems

Problem 5 (Sorted array to BST):

TreeNode* sortedArrayToBST(vector<int>& nums, int left, int right) {
    if (left > right) return nullptr;
    int mid = left + (right - left) / 2;
    TreeNode* node = new TreeNode(nums[mid]);
    node->left = sortedArrayToBST(nums, left, mid - 1);
    node->right = sortedArrayToBST(nums, mid + 1, right);
    return node;
}
// Pick middle as root → guarantees height balance

Problem 7 (Right side view):

vector<int> rightSideView(TreeNode* root) {
    vector<int> result;
    queue<TreeNode*> q;
    if (root) q.push(root);

    while (!q.empty()) {
        int size = q.size();
        for (int i = 0; i < size; i++) {
            TreeNode* node = q.front(); q.pop();
            if (i == size - 1) result.push_back(node->val);  // last node in level
            if (node->left) q.push(node->left);
            if (node->right) q.push(node->right);
        }
    }
    return result;
}

References

  • Cormen et al. — CLRS — Chapters 12, 13 (BST and Red-Black Trees)
  • Sedgewick & Wayne — Algorithms — Chapter 3 (Symbol Tables)
  • Visualgo — BST visualization
  • LeetCode — Tree problems
start typing to search