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

OperationAverageWorst (skewed)Space
SearchO(log n)O(n)O(1) iterative
InsertO(log n)O(n)O(1) iterative
DeleteO(log n)O(n)O(1) iterative
TraversalO(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

ProblemWhat it asks (simplified)Customization
701. Insert into a BSTInsert a value into a BST and return the rootUse 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);
}
ProblemWhat it asks (simplified)Customization
700. Search in a BSTReturn the subtree rooted at the node with the target valueUse either iterative or recursive search above.

Find Min / Max

OperationLogicCode
Find MinGo left until left == NULLwhile (root && root->left) root = root->left;
Find MaxGo right until right == NULLwhile (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:

CaseConditionActionExample
LeafNo childrenFree node, return NULLDelete 20 from tree
One childLeft or right is NULLBypass node with its childDelete node with only right child
Two childrenBoth children existReplace with inorder successor, delete successorDelete 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

ProblemWhat it asks (simplified)Customization
450. Delete Node in a BSTDelete a value and return the updated rootUse the three-case deletion above directly.
669. Trim a BSTRemove 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

TraversalOrderBST Use CaseOutput for example tree   
InorderLeft → Root → RightSorted sequence20 30 35 40 50 60 70 80   
PreorderRoot → Left → RightSerialize / copy tree50 30 20 40 35 70 60 80   
PostorderLeft → Right → RootSafe free / delete20 35 40 30 60 80 70 50   
Level OrderLevel by levelBFS analysis5030 7020 40 60 8035

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

ProblemWhat it asks (simplified)Customization
94. Binary Tree Inorder TraversalReturn inorder sequence as arrayUse inorderToArray helper, return the array.
144. Binary Tree Preorder TraversalReturn preorder sequence as arrayVisit root first, then left, then right into array.
145. Binary Tree Postorder TraversalReturn postorder sequence as arrayVisit left, right, then root into array.
102. Binary Tree Level Order TraversalReturn values grouped by levelUse BFS with queue, track level size.
107. Level Order Traversal IILevel order bottom to topSame 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

ProblemWhat it asks (simplified)Customization
98. Validate BSTCheck if tree is a valid BSTUse min/max range validation. Do NOT just check parent — check global range.
104. Maximum DepthReturn height of the treeRecursively compute 1 + max(left, right). Base case: NULL returns 0 (or -1 for edge count).
110. Balanced Binary TreeCheck if height difference ≤ 1 at every nodeModify height function to return -1 if unbalanced, propagate up.
222. Count Complete Tree NodesCount nodes in complete treeSimple recursion 1 + count(left) + count(right). Optimized: compare left/right heights.
230. Kth SmallestFind kth smallest value in BSTInorder traversal with counter. Stop early when count reaches k.
235. LCA of BSTFind lowest common ancestorExploit BST ordering: follow split point where p and q diverge.
530. Min Absolute DifferenceFind minimum difference between any two nodesInorder 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

#ProblemCore OperationDifficulty
94Inorder TraversalInorder traversalEasy
98Validate BSTRange validationMedium
100Same TreeRecursive comparisonEasy
101Symmetric TreeMirror checkEasy
102Level Order TraversalBFS / QueueMedium
104Maximum DepthHeight recursionEasy
108Sorted Array to BSTBuild balanced BSTEasy
110Balanced Binary TreeHeight + balance checkEasy
144Preorder TraversalPreorder traversalEasy
145Postorder TraversalPostorder traversalEasy
222Count Complete Tree NodesCount nodesMedium
226Invert Binary TreeSwap childrenEasy
230Kth Smallest in BSTInorder + counterMedium
235LCA of BSTBST split pointMedium
236LCA of Binary TreeGeneral DFSMedium
450Delete Node in BSTDeletion with successorMedium
530Min Absolute DifferenceInorder + diffEasy
653Two Sum IV - BSTInorder + two pointersEasy
669Trim a BSTRecursive trimMedium
700Search in BSTBST searchEasy
701Insert into BSTBST insertionMedium
783Min Distance Between BST NodesInorder + diffEasy

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