Binary Search Tree in C

Binary Search Tree in C

A complete guide to Solve the Binary Search Trees(BST) Problems in C & popular LeetCode BST problems

A guide to BST operations in C with LeetCode problem mappings

Table of Contents


1. What is a Binary Search Tree?

BST Property

graph TD
    A((50)) --> B((30))
    A --> C((70))
    B --> D((20))
    B --> E((40))
    C --> F((60))
    C --> G((80))

    style A fill:#6c5ce7,color:#fff
    style B fill:#00b894,color:#fff
    style C fill:#e17055,color:#fff
    style D fill:#00b894,color:#fff
    style E fill:#00b894,color:#fff
    style F fill:#e17055,color:#fff
    style G fill:#e17055,color:#fff

Every node follows one rule: left children are smaller and right children are larger. For example, 30 and 20 sit left of 50 (both smaller), while 70, 60, 80 sit right (all larger).

A Binary Search Tree (BST) is a binary tree where for every node:

  • All values in the left subtree are less than the node’s value
  • All values in the right subtree are greater than the node’s value
  • Both left and right subtrees are also BSTs

Example BST:

        50
       /  \
      30    70
     / \   / \
    20  40 60  80

Time Complexity

Operation Average Worst (skewed) Space
Search O(log n) O(n) O(1) iterative
Insert O(log n) O(n) O(1) iterative
Delete O(log n) O(n) O(1) iterative
Traversal O(n) O(n) O(n)

This table shows BST operation complexities: average case is O(log n) for search, insert, and delete, but degrades to O(n) when the tree becomes skewed (like a linked list).


2. Node Structure

Memory Layout

 ┌──────────────────────────┐
 │       TreeNode           │
 ├──────────┬───────────────┤
 │ int val  │      50       │  ← data
 ├──────────┼───────────────┤
 │ left  ●──┼──→ [Node 30]  │  ← pointer to left child
 ├──────────┼───────────────┤
 │ right ●──┼──→ [Node 70]  │  ← pointer to right child
 └──────────┴───────────────┘
               │
        ┌──────┴──────┐
        ▼             ▼
   ┌─────────┐   ┌─────────┐
   │ val: 30 │   │ val: 70 │
   │ left: ∅ │   │ left: ∅ │
   │ right: ∅│   │ right: ∅│
   └─────────┘   └─────────┘

Each node is a C struct with three fields: int val stores the data, left and right are pointers to child nodes (or NULL if no child exists). Node 50 points left to 30 (smaller) and right to 70 (larger).

Required headers:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <limits.h>

Node definition and constructor:

typedef struct TreeNode {
    int val;
    struct TreeNode* left;
    struct TreeNode* right;
} TreeNode;

TreeNode* createNode(int val) {
    TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
    node->val = val;
    node->left = NULL;
    node->right = NULL;
    return node;
}

3. Core BST Operations

Insertion

Insertion Flow

Inserting 35 into this BST:

graph TD
    A((50)) -.->|"35 < 50 → go left"| B((30))
    B -.->|"35 > 30 → go right"| C((40))
    C -.->|"35 < 40 → go left"| D["✅ NULL → insert 35 here"]

    style A fill:#636e72,color:#fff
    style B fill:#636e72,color:#fff
    style C fill:#636e72,color:#fff
    style D fill:#00b894,color:#fff

Starting at the root, each comparison tells us exactly which direction to go. We keep moving down until we hit a NULL spot — that’s where the new node goes.

Iterative insertion:

#include <stdio.h>
#include <stdlib.h>

typedef struct TreeNode {
    int val;
    struct TreeNode* left;
    struct TreeNode* right;
} TreeNode;

TreeNode* createNode(int val) {
    TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
    node->val = val;
    node->left = NULL;
    node->right = NULL;
    return node;
}

TreeNode* insert(TreeNode* root, int val) {
    TreeNode* newNode = createNode(val);
    if (root == NULL) return newNode;

    TreeNode* curr = root;
    TreeNode* parent = NULL;

    while (curr != NULL) {
        parent = curr;
        if (val < curr->val)
            curr = curr->left;
        else if (val > curr->val)
            curr = curr->right;
        else
            return root;  // duplicate — no insertion
    }

    if (val < parent->val)
        parent->left = newNode;
    else
        parent->right = newNode;

    return root;
}

int main() {
    TreeNode* root = NULL;
    root = insert(root, 50);
    insert(root, 30);
    insert(root, 70);
    insert(root, 20);
    printf("Tree built with 4 nodes\n");
    return 0;
}

Iterative insertion avoids recursion overhead and is preferred in performance-critical embedded systems and low-level C code.

Recursive insertion:

TreeNode* insertRecursive(TreeNode* root, int val) {
    if (root == NULL) return createNode(val);

    if (val < root->val)
        root->left = insertRecursive(root->left, val);
    else if (val > root->val)
        root->right = insertRecursive(root->right, val);

    return root;  // duplicate — unchanged
}

LeetCode Problems Using Insertion

Problem What it asks (simplified) Customization
701. Insert into a BST Insert a value into a BST and return the root Use the recursive insertion above directly.

Search Flow

graph LR
    A["search(root, val)"] --> B{root == NULL?}
    B -->|Yes| C[Return NULL]
    B -->|No| D{"val == root->val?"}
    D -->|Yes| E[Found — return node]
    D -->|No| F{"val < root->val?"}
    F -->|Yes| G[Search left]
    F -->|No| H[Search right]

The search flow exploits BST ordering: at each node, the value tells us exactly which subtree to explore, eliminating half the tree at each step.

Iterative search:

TreeNode* search(TreeNode* root, int val) {
    while (root != NULL && root->val != val) {
        if (val < root->val)
            root = root->left;
        else
            root = root->right;
    }
    return root;  // NULL if not found
}

Recursive search:

TreeNode* searchRecursive(TreeNode* root, int val) {
    if (root == NULL || root->val == val)
        return root;

    if (val < root->val)
        return searchRecursive(root->left, val);
    else
        return searchRecursive(root->right, val);
}
Problem What it asks (simplified) Customization
700. Search in a BST Return the subtree rooted at the node with the target value Use either iterative or recursive search above.

Find Min / Max

Operation Logic Code
Find Min Go left until left == NULL while (root && root->left) root = root->left;
Find Max Go right until right == NULL while (root && root->right) root = root->right;

The minimum value in a BST is always the leftmost node, and the maximum is always the rightmost. Both traverse a single path from root to leaf.

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

TreeNode* findMax(TreeNode* root) {
    while (root && root->right)
        root = root->right;
    return root;
}

Deletion

Deletion Decision Flow

graph TD
    A["deleteNode(root, key)"] --> B{Found node?}
    B -->|No| C[Return NULL]
    B -->|Yes| D{How many children?}
    D -->|0 - Leaf| E[Free node, return NULL]
    D -->|1 child| F[Replace with child]
    D -->|2 children| G[Find inorder successor]
    G --> H["Copy successor's value"]
    H --> I["Delete successor from right subtree"]

Deletion has three cases: leaf nodes are simply removed, single-child nodes are replaced by their child, and two-child nodes use the inorder successor (smallest value in right subtree) as a replacement.

Three cases explained:

Case Condition Action Example
Leaf No children Free node, return NULL Delete 20 from tree
One child Left or right is NULL Bypass node with its child Delete node with only right child
Two children Both children exist Replace with inorder successor, delete successor Delete 30 (replace with 35)

This table summarizes the three deletion cases: each requires different handling based on the node’s child count.

TreeNode* deleteNode(TreeNode* root, int key) {
    if (root == NULL) return NULL;

    if (key < root->val) {
        root->left = deleteNode(root->left, key);
    } else if (key > root->val) {
        root->right = deleteNode(root->right, key);
    } else {
        // Case 1 & 2: No child or one child
        if (root->left == NULL) {
            TreeNode* temp = root->right;
            free(root);
            return temp;
        } else if (root->right == NULL) {
            TreeNode* temp = root->left;
            free(root);
            return temp;
        }

        // Case 3: Two children
        TreeNode* successor = findMin(root->right);
        root->val = successor->val;
        root->right = deleteNode(root->right, successor->val);
    }
    return root;
}

Deletion visualized (deleting 30):

  Case 3: Two children          After replacing with
        50                      inorder successor (35)
       /  \                          50
      30    70         →            /  \
     / \   / \                    35    70
    20  40 60  80                /  \  / \
       /                        20  40 60 80
      35

LeetCode Problems Using Deletion

Problem What it asks (simplified) Customization
450. Delete Node in a BST Delete a value and return the updated root Use the three-case deletion above directly.
669. Trim a BST Remove all nodes outside range [low, high] If val < low, return trimmed right subtree. If val > high, return trimmed left subtree.

4. Tree Traversals

Traversal Overview

graph TD
    A[Tree Traversals] --> B[DFS - Depth First]
    A --> C[BFS - Breadth First]
    B --> D["Inorder: L → Root → R"]
    B --> E["Preorder: Root → L → R"]
    B --> F["Postorder: L → R → Root"]
    C --> G["Level Order: Level by level"]
    D --> H["Sorted output for BST"]
    E --> I["Tree serialization"]
    F --> J["Safe deletion"]
    G --> K["Uses a Queue"]

Tree traversals are either depth-first (going deep before wide) or breadth-first (going wide before deep). Each DFS variant has specific use cases: inorder for sorting, preorder for copying, postorder for deletion.

Traversal Order Comparison

Traversal Order BST Use Case Output for example tree      
Inorder Left → Root → Right Sorted sequence 20 30 35 40 50 60 70 80      
Preorder Root → Left → Right Serialize / copy tree 50 30 20 40 35 70 60 80      
Postorder Left → Right → Root Safe free / delete 20 35 40 30 60 80 70 50      
Level Order Level by level BFS analysis 50 30 70 20 40 60 80 35

This table compares the four traversal orders, their visit sequence, practical use cases, and the output produced when applied to the example BST.


Inorder (Left → Root → Right)

Produces a sorted sequence of values from a BST.

#include <stdio.h>
#include <stdlib.h>

typedef struct TreeNode {
    int val;
    struct TreeNode* left;
    struct TreeNode* right;
} TreeNode;

TreeNode* createNode(int val) {
    TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
    node->val = val;
    node->left = NULL;
    node->right = NULL;
    return node;
}

void inorder(TreeNode* root) {
    if (root == NULL) return;
    inorder(root->left);
    printf("%d ", root->val);
    inorder(root->right);
}

// Store inorder result in array (useful for many LeetCode problems)
void inorderToArray(TreeNode* root, int* arr, int* idx) {
    if (root == NULL) return;
    inorderToArray(root->left, arr, idx);
    arr[(*idx)++] = root->val;
    inorderToArray(root->right, arr, idx);
}

int main() {
    TreeNode* root = createNode(50);
    root->left = createNode(30);
    root->right = createNode(70);
    root->left->left = createNode(20);
    root->left->right = createNode(40);

    printf("Inorder: ");
    inorder(root);  // 20 30 40 50 70
    printf("\n");
    return 0;
}

Inorder traversal is the most important BST traversal — it produces a sorted sequence, which is the basis for problems like Kth Smallest, Validate BST, and finding minimum differences.


Preorder (Root → Left → Right)

Used to serialize or copy a tree. The root is visited first, making it ideal for reconstruction.

void preorder(TreeNode* root) {
    if (root == NULL) return;
    printf("%d ", root->val);
    preorder(root->left);
    preorder(root->right);
}

Postorder (Left → Right → Root)

Used to safely delete/free a tree — children are freed before the parent.

void postorder(TreeNode* root) {
    if (root == NULL) return;
    postorder(root->left);
    postorder(root->right);
    printf("%d ", root->val);
}

// Free entire tree using postorder
void freeTree(TreeNode* root) {
    if (root == NULL) return;
    freeTree(root->left);
    freeTree(root->right);
    free(root);
}

Always use postorder to free a tree in C. Freeing a parent before its children creates dangling pointers and memory leaks — a common mistake in manual memory management.


Level Order (BFS)

BFS Process

graph LR
    A[Enqueue root] --> B[Dequeue node]
    B --> C[Process node]
    C --> D{Has left child?}
    D -->|Yes| E[Enqueue left]
    D -->|No| F{Has right child?}
    E --> F
    F -->|Yes| G[Enqueue right]
    F -->|No| H{Queue empty?}
    G --> H
    H -->|No| B
    H -->|Yes| I[Done]

Level order traversal uses a queue: enqueue the root, then repeatedly dequeue a node, process it, and enqueue its children. This visits all nodes level by level from top to bottom.

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef struct TreeNode {
    int val;
    struct TreeNode* left;
    struct TreeNode* right;
} TreeNode;

// Simple queue for BFS
typedef struct {
    TreeNode* data[10000];
    int front, rear;
} Queue;

void initQueue(Queue* q) { q->front = 0; q->rear = 0; }
void enqueue(Queue* q, TreeNode* node) { q->data[q->rear++] = node; }
TreeNode* dequeue(Queue* q) { return q->data[q->front++]; }
bool isEmpty(Queue* q) { return q->front == q->rear; }

void levelOrder(TreeNode* root) {
    if (root == NULL) return;

    Queue q;
    initQueue(&q);
    enqueue(&q, root);

    while (!isEmpty(&q)) {
        int size = q.rear - q.front;  // nodes at current level

        for (int i = 0; i < size; i++) {
            TreeNode* node = dequeue(&q);
            printf("%d ", node->val);

            if (node->left)  enqueue(&q, node->left);
            if (node->right) enqueue(&q, node->right);
        }
        printf("\n");  // new line per level
    }
}

Level order traversal is essential for problems that require processing nodes level-by-level, such as finding the width of a tree, right-side view, or zigzag traversal.

LeetCode Problems Using Traversals

Problem What it asks (simplified) Customization
94. Binary Tree Inorder Traversal Return inorder sequence as array Use inorderToArray helper, return the array.
144. Binary Tree Preorder Traversal Return preorder sequence as array Visit root first, then left, then right into array.
145. Binary Tree Postorder Traversal Return postorder sequence as array Visit left, right, then root into array.
102. Binary Tree Level Order Traversal Return values grouped by level Use BFS with queue, track level size.
107. Level Order Traversal II Level order bottom to top Same as 102, then reverse the result array.

5. BST Properties & Utilities

Height of Tree

graph TD
    A["height(root)"] --> B{root == NULL?}
    B -->|Yes| C["Return -1"]
    B -->|No| D["leftH = height(left)"]
    D --> E["rightH = height(right)"]
    E --> F["Return 1 + max(leftH, rightH)"]

Height is computed recursively: the height of an empty tree is -1, and each node adds 1 to the maximum height of its subtrees.

int height(TreeNode* root) {
    if (root == NULL) return -1;  // -1 for edges, 0 for nodes

    int leftH  = height(root->left);
    int rightH = height(root->right);

    return 1 + (leftH > rightH ? leftH : rightH);
}

Example:

        50          height = 3
       /  \
      30    70      height = 2
     / \   / \
    20  40 60  80   height = 1
       /
      35            height = 0

Count Nodes

int countNodes(TreeNode* root) {
    if (root == NULL) return 0;
    return 1 + countNodes(root->left) + countNodes(root->right);
}

Validate BST

Validation Strategy

graph TD
    A["isValidBST(root, min, max)"] --> B{root == NULL?}
    B -->|Yes| C["Return true"]
    B -->|No| D{"val <= min OR val >= max?"}
    D -->|Yes| E["Return false — violates BST"]
    D -->|No| F["Check left with (min, root->val)"]
    F --> G["Check right with (root->val, max)"]
    G --> H["Return left AND right"]

Validation uses a min/max range that narrows at each level: left children must be less than the parent, right children must be greater. This catches the common mistake of only checking immediate parent.

#include <stdbool.h>
#include <limits.h>

bool isValidBSTHelper(TreeNode* root, long min, long max) {
    if (root == NULL) return true;

    if (root->val <= min || root->val >= max)
        return false;

    return isValidBSTHelper(root->left, min, root->val) &&
           isValidBSTHelper(root->right, root->val, max);
}

bool isValidBST(TreeNode* root) {
    return isValidBSTHelper(root, LONG_MIN, LONG_MAX);
}

Why just checking parent is wrong:

        5
       / \
      1    6      ← Looks valid at each parent
          / \
         3   7    ← 3 < 5 — violates BST property!

The range-based approach catches this: node 3 would need to be in range (5, 6), but 3 < 5, so it fails.


Lowest Common Ancestor

LCA Decision Flow

graph TD
    A["LCA(root, p, q)"] --> B{"p < root AND q < root?"}
    B -->|Yes| C["Both in left → go left"]
    B -->|No| D{"p > root AND q > root?"}
    D -->|Yes| E["Both in right → go right"]
    D -->|No| F["Split point = LCA!"]

In a BST, the LCA is the first node where the two target values diverge into different subtrees. This makes it O(log n) instead of the O(n) general tree approach.

TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    while (root != NULL) {
        if (p->val < root->val && q->val < root->val)
            root = root->left;       // both in left subtree
        else if (p->val > root->val && q->val > root->val)
            root = root->right;      // both in right subtree
        else
            return root;             // split point = LCA
    }
    return NULL;
}

Kth Smallest Element

Use inorder traversal (sorted order) and count down to k.

void kthSmallestHelper(TreeNode* root, int k, int* count, int* result) {
    if (root == NULL || *count >= k) return;

    kthSmallestHelper(root->left, k, count, result);

    (*count)++;
    if (*count == k) {
        *result = root->val;
        return;
    }

    kthSmallestHelper(root->right, k, count, result);
}

int kthSmallest(TreeNode* root, int k) {
    int count = 0, result = -1;
    kthSmallestHelper(root, k, &count, &result);
    return result;
}

Example (k=3):

Inorder: 20, 30, [35], 40, 50, 60, 70, 80
                   ↑
              3rd smallest = 35

LeetCode Problems Using BST Properties

Problem What it asks (simplified) Customization
98. Validate BST Check if tree is a valid BST Use min/max range validation. Do NOT just check parent — check global range.
104. Maximum Depth Return height of the tree Recursively compute 1 + max(left, right). Base case: NULL returns 0 (or -1 for edge count).
110. Balanced Binary Tree Check if height difference ≤ 1 at every node Modify height function to return -1 if unbalanced, propagate up.
222. Count Complete Tree Nodes Count nodes in complete tree Simple recursion 1 + count(left) + count(right). Optimized: compare left/right heights.
230. Kth Smallest Find kth smallest value in BST Inorder traversal with counter. Stop early when count reaches k.
235. LCA of BST Find lowest common ancestor Exploit BST ordering: follow split point where p and q diverge.
530. Min Absolute Difference Find minimum difference between any two nodes Inorder traversal, track previous value, compute min diff at each step.

6. LeetCode Practice Problems

Problem 1: Convert Sorted Array to BST

Build a height-balanced BST from a sorted array by always picking the middle element as root.

#include <stdio.h>
#include <stdlib.h>

typedef struct TreeNode {
    int val;
    struct TreeNode* left;
    struct TreeNode* right;
} TreeNode;

TreeNode* createNode(int val) {
    TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
    node->val = val;
    node->left = NULL;
    node->right = NULL;
    return node;
}

TreeNode* sortedArrayToBST(int* nums, int left, int right) {
    if (left > right) return NULL;

    int mid = left + (right - left) / 2;
    TreeNode* root = createNode(nums[mid]);
    root->left  = sortedArrayToBST(nums, left, mid - 1);
    root->right = sortedArrayToBST(nums, mid + 1, right);
    return root;
}

void inorder(TreeNode* root) {
    if (!root) return;
    inorder(root->left);
    printf("%d ", root->val);
    inorder(root->right);
}

int main() {
    int nums[] = {-10, -3, 0, 5, 9};
    int n = 5;
    TreeNode* root = sortedArrayToBST(nums, 0, n - 1);
    printf("Inorder: ");
    inorder(root);  // -10 -3 0 5 9
    printf("\n");
    return 0;
}

Process:

Array: [-10, -3, 0, 5, 9]
                ↓
Mid = 0 (index 2)         →  Root = 0
Left half:  [-10, -3]     →  Left subtree
Right half: [5, 9]        →  Right subtree

Result:
        0
       / \
     -3    9
     /    /
   -10   5

This technique guarantees a balanced tree (height difference ≤ 1). It’s a divide-and-conquer approach used in database index construction and balanced tree initialization.


Problem 2: Invert Binary Tree

Swap left and right children at every node recursively.

TreeNode* invertTree(TreeNode* root) {
    if (root == NULL) return NULL;

    TreeNode* temp = root->left;
    root->left  = invertTree(root->right);
    root->right = invertTree(temp);
    return root;
}

Process:

Before:         After:
    4              4
   / \            / \
  2    7   →    7    2
 / \  / \      / \  / \
1   3 6   9   9   6 3   1

Tree inversion is a classic recursion problem famously associated with Homebrew creator Max Howell’s Google interview. It tests understanding of tree structure manipulation and recursive thinking.


Problem 3: Two Sum IV (BST)

Flatten BST to sorted array via inorder, then use two pointers.

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef struct TreeNode {
    int val;
    struct TreeNode* left;
    struct TreeNode* right;
} TreeNode;

void inorderToArray(TreeNode* root, int* arr, int* idx) {
    if (root == NULL) return;
    inorderToArray(root->left, arr, idx);
    arr[(*idx)++] = root->val;
    inorderToArray(root->right, arr, idx);
}

bool findTarget(TreeNode* root, int k) {
    int arr[10001];
    int idx = 0;
    inorderToArray(root, arr, &idx);

    int lo = 0, hi = idx - 1;
    while (lo < hi) {
        int sum = arr[lo] + arr[hi];
        if (sum == k) return true;
        else if (sum < k) lo++;
        else hi--;
    }
    return false;
}

Example (k=9):

Inorder: [2, 3, 4, 6, 7]
          ↑            ↑     2+7=9 ✓ Found!
         lo           hi

This combines BST inorder traversal with the classic two-pointer technique. The sorted property of inorder output makes two-pointer search optimal at O(n) time and O(n) space.


Problem 4: Trim a BST

Remove all nodes outside the range [low, high].

TreeNode* trimBST(TreeNode* root, int low, int high) {
    if (root == NULL) return NULL;

    if (root->val < low)
        return trimBST(root->right, low, high);
    if (root->val > high)
        return trimBST(root->left, low, high);

    root->left  = trimBST(root->left, low, high);
    root->right = trimBST(root->right, low, high);
    return root;
}

Process (trim [1, 3]):

Before:         After:
    3              3
   / \            /
  0    4   →    2
   \           /
    2         1
   /
  1

Trimming uses BST ordering: if a node is below the range, all its left children are too, so we only keep the right subtree (and vice versa). This makes it efficient — no need to check every node twice.


Problem 5: Minimum Absolute Difference

Find the minimum difference between any two node values in a BST.

#include <stdio.h>
#include <limits.h>

void minDiffHelper(TreeNode* root, int* prev, int* minDiff) {
    if (root == NULL) return;

    minDiffHelper(root->left, prev, minDiff);

    if (*prev != -1) {
        int diff = root->val - *prev;
        if (diff < *minDiff) *minDiff = diff;
    }
    *prev = root->val;

    minDiffHelper(root->right, prev, minDiff);
}

int getMinimumDifference(TreeNode* root) {
    int prev = -1, minDiff = INT_MAX;
    minDiffHelper(root, &prev, &minDiff);
    return minDiff;
}

Example:

Inorder: 1, 2, 3, 4, 6
Diffs:     1  1  1  2
Min diff = 1

Since inorder traversal of a BST is sorted, the minimum difference must be between consecutive elements. This avoids the O(n²) brute-force approach.

Complete LeetCode Reference Table

# Problem Core Operation Difficulty
94 Inorder Traversal Inorder traversal Easy
98 Validate BST Range validation Medium
100 Same Tree Recursive comparison Easy
101 Symmetric Tree Mirror check Easy
102 Level Order Traversal BFS / Queue Medium
104 Maximum Depth Height recursion Easy
108 Sorted Array to BST Build balanced BST Easy
110 Balanced Binary Tree Height + balance check Easy
144 Preorder Traversal Preorder traversal Easy
145 Postorder Traversal Postorder traversal Easy
222 Count Complete Tree Nodes Count nodes Medium
226 Invert Binary Tree Swap children Easy
230 Kth Smallest in BST Inorder + counter Medium
235 LCA of BST BST split point Medium
236 LCA of Binary Tree General DFS Medium
450 Delete Node in BST Deletion with successor Medium
530 Min Absolute Difference Inorder + diff Easy
653 Two Sum IV - BST Inorder + two pointers Easy
669 Trim a BST Recursive trim Medium
700 Search in BST BST search Easy
701 Insert into BST BST insertion Medium
783 Min Distance Between BST Nodes Inorder + diff Easy

7. Full Working Code

A complete program demonstrating all operations:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <limits.h>

typedef struct TreeNode {
    int val;
    struct TreeNode* left;
    struct TreeNode* right;
} TreeNode;

TreeNode* createNode(int val) {
    TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
    node->val   = val;
    node->left  = NULL;
    node->right = NULL;
    return node;
}

TreeNode* insert(TreeNode* root, int val) {
    if (root == NULL) return createNode(val);
    if (val < root->val)
        root->left = insert(root->left, val);
    else if (val > root->val)
        root->right = insert(root->right, val);
    return root;
}

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

TreeNode* deleteNode(TreeNode* root, int key) {
    if (!root) return NULL;
    if (key < root->val)
        root->left = deleteNode(root->left, key);
    else if (key > root->val)
        root->right = deleteNode(root->right, key);
    else {
        if (!root->left)  { TreeNode* t = root->right; free(root); return t; }
        if (!root->right) { TreeNode* t = root->left;  free(root); return t; }
        TreeNode* s = findMin(root->right);
        root->val = s->val;
        root->right = deleteNode(root->right, s->val);
    }
    return root;
}

TreeNode* search(TreeNode* root, int val) {
    while (root && root->val != val) {
        if (val < root->val) root = root->left;
        else root = root->right;
    }
    return root;
}

void inorder(TreeNode* root) {
    if (!root) return;
    inorder(root->left);
    printf("%d ", root->val);
    inorder(root->right);
}

void preorder(TreeNode* root) {
    if (!root) return;
    printf("%d ", root->val);
    preorder(root->left);
    preorder(root->right);
}

void postorder(TreeNode* root) {
    if (!root) return;
    postorder(root->left);
    postorder(root->right);
    printf("%d ", root->val);
}

int height(TreeNode* root) {
    if (!root) return -1;
    int l = height(root->left);
    int r = height(root->right);
    return 1 + (l > r ? l : r);
}

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

void freeTree(TreeNode* root) {
    if (!root) return;
    freeTree(root->left);
    freeTree(root->right);
    free(root);
}

int main() {
    TreeNode* root = NULL;
    int values[] = {50, 30, 70, 20, 40, 60, 80, 35};
    int n = sizeof(values) / sizeof(values[0]);

    // Build BST
    for (int i = 0; i < n; i++)
        root = insert(root, values[i]);

    // Traversals
    printf("Inorder:   "); inorder(root);   printf("\n");
    printf("Preorder:  "); preorder(root);   printf("\n");
    printf("Postorder: "); postorder(root);  printf("\n");

    // Properties
    printf("Height:    %d\n", height(root));
    printf("Count:     %d\n", countNodes(root));

    // Search
    TreeNode* found = search(root, 40);
    printf("Search 40: %s\n", found ? "Found" : "Not found");

    // Delete
    printf("\nDeleting 30...\n");
    root = deleteNode(root, 30);
    printf("Inorder:   "); inorder(root); printf("\n");

    // Min / Max
    printf("Min value: %d\n", findMin(root)->val);

    freeTree(root);
    return 0;
}

Output:

Inorder:   20 30 35 40 50 60 70 80
Preorder:  50 30 20 40 35 70 60 80
Postorder: 20 35 40 30 60 80 70 50
Height:    3
Count:     8
Search 40: Found

Deleting 30...
Inorder:   20 35 40 50 60 70 80
Min value: 20

8. Reference

Operations Quick Reference

// Create
TreeNode* node = createNode(val);

// Insert
root = insert(root, val);

// Search
TreeNode* found = search(root, val);

// Delete
root = deleteNode(root, key);

// Min / Max
TreeNode* min = findMin(root);
TreeNode* max = findMax(root);

// Traversals
inorder(root);    // Sorted output
preorder(root);   // Serialize tree
postorder(root);  // Safe deletion

// Properties
int h = height(root);
int n = countNodes(root);
bool valid = isValidBST(root);

// Cleanup
freeTree(root);

Final Tips

Do:

  • Use inorder traversal for sorted-order problems
  • Use min/max range for BST validation (not just parent check)
  • Free trees with postorder traversal
  • Consider iterative versions for production C code
  • Handle NULL pointers at every function entry

Don’t:

  • Forget to free() deleted nodes — memory leaks are silent bugs
  • Only check immediate parent when validating BST
  • Use recursion on extremely deep trees (stack overflow risk)
  • Ignore the skewed tree worst case — use AVL/Red-Black for guarantees
  • Assume BST operations are always O(log n) — they’re O(n) when skewed