🌳 Trees
Understand hierarchical data, learn essential Tree terminology, study Binary Tree structure, build a small Binary Tree, and trace the exact C program step by step.
📖 What is a Tree?
A tree is a non-linear hierarchical data structure made of nodes connected by edges. Unlike an array, linked list, stack, or queue, Tree data is organized in levels.
💡 Hierarchical Structure
A Tree begins with one top node called the root. A node can connect downward to child nodes, and those children can have their own children.
🧠 Real-Life Examples
Folder structures, organization charts, HTML DOM, family hierarchies, decision trees, expression trees, and database indexes can all be modeled using Trees.
🧩 Important Tree Terms
📌 Convention Used on CodeBhavya
The root is at depth 0. Tree height is measured in edges, so a single-node Tree has height 0.
🌿 Binary Tree Basics
A Binary Tree is a Tree in which every node has at most two children. These are called the left child and right child.
left pointer.
right pointer.
🧠 In Our Example
Node 10 is the root. Nodes 20 and 30 are its children. Nodes 40 and 50 are children of 20. Nodes 60 and 70 are children of 30.
🧱 Binary Tree Node in C
A Binary Tree node normally stores one data field and two pointer fields.
struct Node
{
int data;
struct Node *left;
struct Node *right;
};
💡 Meaning of NULL
If left == NULL, the node has no left child.
If right == NULL, the node has no right child.
A leaf node has both pointers equal to NULL.
⚙️ Building a Small Binary Tree
For this first Tree lesson, we create nodes individually and connect them using left and right pointers.
CREATE_NODE(value)
1. Allocate memory for a new node
2. newNode->data = value
3. newNode->left = NULL
4. newNode->right = NULL
5. Return newNode
BUILD EXAMPLE
root = createNode(10)
root->left = createNode(20)
root->right = createNode(30)
root->left->left = createNode(40)
root->left->right = createNode(50)
root->right->left = createNode(60)
root->right->right = createNode(70)
🔗 Pointer View
The values do not have to be stored next to each other in memory. The Tree structure is created by the left and right pointer connections.
🎬 Build a Binary Tree Visually
null or - for an empty position.
💻 Create a Binary Tree in C
Visible Learning Program
#include <stdio.h>
#include <stdlib.h>
struct Node
{
int data;
struct Node *left;
struct Node *right;
};
struct Node *createNode(int value)
{
struct Node *newNode =
(struct Node *)malloc(sizeof(struct Node));
newNode->data = value;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}
int main()
{
struct Node *root;
root = createNode(10);
root->left = createNode(20);
root->right = createNode(30);
root->left->left = createNode(40);
root->left->right = createNode(50);
root->right->left = createNode(60);
root->right->right = createNode(70);
printf("Root: %d\n", root->data);
printf("Left Child: %d\n", root->left->data);
printf("Right Child: %d\n", root->right->data);
return 0;
}
Program Output
Root: 10
Left Child: 20
Right Child: 30
Final Tree
10
/ \
20 30
/ \ / \
40 50 60 70
📘 Learning Note
This short program focuses on node creation and pointer connections. In long-running programs, dynamically allocated Tree nodes should also be released when no longer needed.
🔎 Trace the Binary Tree Construction
💻 Program
🧠 What is happening?
📊 Live Variables
🌳 Live Binary Tree Memory
—
⚡ Binary Tree Basics — Complexity
🎯 Interview Note
If the root is at depth 0, the maximum number of nodes at depth d is
2^d. A perfect Binary Tree of height h can contain
2^(h+1) - 1 nodes.
🔵 Preorder Traversal
A Tree traversal visits every node of a Tree in a systematic order. In Preorder Traversal, we process the current node before its subtrees.
💡 Preorder Rule
Remember it as ROOT → LEFT → RIGHT. Because the root is processed first, Preorder is useful when parent information must appear before child information.
🧠 Example Tree
10
/ \
20 30
/ \ / \
40 50 60 70
⚙️ Recursive Preorder Algorithm
PREORDER(root)
1. If root == NULL
return
2. Visit root
3. PREORDER(root->left)
4. PREORDER(root->right)
🔁 Why Recursion Fits Trees
Every left or right child is itself the root of a smaller subtree.
So the same Preorder logic can be applied recursively until a NULL pointer is reached.
🎬 Preorder Traversal Visualizer
💻 Recursive Preorder Traversal in C
Visible Learning Program
#include <stdio.h>
#include <stdlib.h>
struct Node
{
int data;
struct Node *left;
struct Node *right;
};
struct Node *createNode(int value)
{
struct Node *newNode =
(struct Node *)malloc(sizeof(struct Node));
newNode->data = value;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}
void preorder(struct Node *root)
{
if(root == NULL)
return;
printf("%d ", root->data);
preorder(root->left);
preorder(root->right);
}
int main()
{
struct Node *root;
root = createNode(10);
root->left = createNode(20);
root->right = createNode(30);
root->left->left = createNode(40);
root->left->right = createNode(50);
root->right->left = createNode(60);
root->right->right = createNode(70);
preorder(root);
return 0;
}
Program Output
10 20 40 50 30 60 70
Traversal Rule
ROOT
LEFT subtree
RIGHT subtree
🔎 Trace Recursive Preorder Execution
💻 Program
🧠 What is happening?
📊 Live Variables
📚 Call Stack
🌳 Live Binary Tree
—
⚡ Preorder Traversal Complexity
🎯 Interview Note
Preorder naturally produces a parent-before-children order. It is commonly used for copying/serializing Tree structure, prefix expression generation, and hierarchical output.
🟡 Inorder Traversal
In Inorder Traversal, we completely process the left subtree first, then visit the current node, and finally process the right subtree.
💡 Inorder Rule
Remember it as LEFT → ROOT → RIGHT. The root is processed only after the entire left subtree has been handled.
🧠 Example Tree
10
/ \
20 30
/ \ / \
40 50 60 70
⚙️ Recursive Inorder Algorithm
INORDER(root)
1. If root == NULL
return
2. INORDER(root->left)
3. Visit root
4. INORDER(root->right)
🔑 Special BST Property
For a Binary Search Tree, Inorder Traversal produces the keys in sorted ascending order. We will use this property again when we study BST.
🎬 Inorder Traversal Visualizer
💻 Recursive Inorder Traversal in C
Visible Learning Program
#include <stdio.h>
#include <stdlib.h>
struct Node
{
int data;
struct Node *left;
struct Node *right;
};
struct Node *createNode(int value)
{
struct Node *newNode =
(struct Node *)malloc(sizeof(struct Node));
newNode->data = value;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}
void inorder(struct Node *root)
{
if(root == NULL)
return;
inorder(root->left);
printf("%d ", root->data);
inorder(root->right);
}
int main()
{
struct Node *root;
root = createNode(10);
root->left = createNode(20);
root->right = createNode(30);
root->left->left = createNode(40);
root->left->right = createNode(50);
root->right->left = createNode(60);
root->right->right = createNode(70);
inorder(root);
return 0;
}
Program Output
40 20 50 10 60 30 70
Traversal Rule
LEFT subtree
ROOT
RIGHT subtree
🔎 Trace Recursive Inorder Execution
💻 Program
🧠 What is happening?
📊 Live Variables
📚 Call Stack
🌳 Live Binary Tree
—
⚡ Inorder Traversal Complexity
🎯 Interview Note
Inorder is especially important for Binary Search Trees because it visits BST keys in sorted order. For an ordinary Binary Tree, Inorder does not automatically produce sorted values.
🟣 Postorder Traversal
In Postorder Traversal, we process both subtrees before visiting the current node. That means the parent is handled only after its children are completely processed.
💡 Postorder Rule
Remember it as LEFT → RIGHT → ROOT. Since children are handled before their parent, Postorder is useful for deleting/freeing a Tree and for evaluating expression trees.
🧠 Example Tree
10
/ \
20 30
/ \ / \
40 50 60 70
⚙️ Recursive Postorder Algorithm
POSTORDER(root)
1. If root == NULL
return
2. POSTORDER(root->left)
3. POSTORDER(root->right)
4. Visit root
🧹 Why Postorder is Good for Deleting a Tree
Before freeing a parent node, its left and right subtrees should already be processed. Postorder naturally gives exactly that children-before-parent order.
🎬 Postorder Traversal Visualizer
💻 Recursive Postorder Traversal in C
Visible Learning Program
#include <stdio.h>
#include <stdlib.h>
struct Node
{
int data;
struct Node *left;
struct Node *right;
};
struct Node *createNode(int value)
{
struct Node *newNode =
(struct Node *)malloc(sizeof(struct Node));
newNode->data = value;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}
void postorder(struct Node *root)
{
if(root == NULL)
return;
postorder(root->left);
postorder(root->right);
printf("%d ", root->data);
}
int main()
{
struct Node *root;
root = createNode(10);
root->left = createNode(20);
root->right = createNode(30);
root->left->left = createNode(40);
root->left->right = createNode(50);
root->right->left = createNode(60);
root->right->right = createNode(70);
postorder(root);
return 0;
}
Program Output
40 50 20 60 70 30 10
Traversal Rule
LEFT subtree
RIGHT subtree
ROOT
🔎 Trace Recursive Postorder Execution
💻 Program
🧠 What is happening?
📊 Live Variables
📚 Call Stack
🌳 Live Binary Tree
—
⚡ Postorder Traversal Complexity
🎯 Interview Note
Postorder is the natural traversal for freeing an entire dynamically allocated Tree: free the left subtree, free the right subtree, then free the current node.
🟢 Level Order Traversal
Level Order Traversal visits a Tree level by level from top to bottom, and from left to right within each level. It is the Tree form of Breadth-First Search (BFS).
💡 Why a Queue?
A FIFO Queue keeps nodes in discovery order. Nodes discovered earlier are processed earlier, which naturally gives level-by-level traversal.
🧠 Example Tree
10
/ \
20 30
/ \ / \
40 50 60 70
⚙️ Level Order Algorithm using Queue
LEVEL_ORDER(root)
1. If root == NULL
return
2. Create an empty Queue
3. Enqueue root
4. While Queue is not empty
current = Dequeue()
Visit current
If current->left != NULL
Enqueue current->left
If current->right != NULL
Enqueue current->right
🔁 BFS Connection
The same idea appears in graph BFS: discover nodes, place them in a Queue, and process them in FIFO order.
🎬 Level Order Traversal Visualizer
💻 Level Order Traversal in C
Visible Learning Program
#include <stdio.h>
#include <stdlib.h>
struct Node
{
int data;
struct Node *left;
struct Node *right;
};
struct Node *createNode(int value)
{
struct Node *newNode =
(struct Node *)malloc(sizeof(struct Node));
newNode->data = value;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}
void levelOrder(struct Node *root)
{
struct Node *queue[100];
int front = 0;
int rear = 0;
if(root == NULL)
return;
queue[rear++] = root;
while(front < rear)
{
struct Node *current = queue[front++];
printf("%d ", current->data);
if(current->left != NULL)
queue[rear++] = current->left;
if(current->right != NULL)
queue[rear++] = current->right;
}
}
int main()
{
struct Node *root;
root = createNode(10);
root->left = createNode(20);
root->right = createNode(30);
root->left->left = createNode(40);
root->left->right = createNode(50);
root->right->left = createNode(60);
root->right->right = createNode(70);
levelOrder(root);
return 0;
}
Program Output
10 20 30 40 50 60 70
Core Idea
Queue: FIFO
Dequeue node
Visit node
Enqueue LEFT
Enqueue RIGHT
🔎 Trace Level Order Execution
💻 Program
🧠 What is happening?
📊 Live Variables
🚶 Live Queue
🌳 Live Binary Tree
—
⚡ Level Order Traversal Complexity
🎯 Interview Note
Level Order Traversal is BFS on a Tree. If an interviewer asks for nodes level by level, shortest level distance from the root, or level-wise processing, think of a Queue.
🌲 Binary Search Tree (BST)
A Binary Search Tree is a Binary Tree that maintains an ordering rule. For every node, smaller values are stored in the left subtree and larger values are stored in the right subtree.
🧠 Example BST
50
/ \
30 70
/ \ / \
20 40 60 80
📌 Duplicate Policy Used Here
This learning implementation does not insert duplicate values.
When value == root->data, the existing node is kept unchanged.
| Binary Tree | Binary Search Tree |
|---|---|
| No general ordering rule between node values. | Maintains left-smaller and right-larger ordering. |
| Searching may require checking many or all nodes. | Ordering lets Search choose only one subtree at each comparison. |
| Inorder is not necessarily sorted. | Inorder produces ascending values for a valid BST. |
🔍 Searching in a BST
BST Search compares the key with the current node and discards one entire subtree after each comparison.
SEARCH(root, key)
1. If root == NULL
return NOT FOUND
2. If root->data == key
return root
3. If key < root->data
SEARCH(root->left, key)
4. Otherwise
SEARCH(root->right, key)
🧠 Search for 60
Start at 50. Since 60 > 50, move RIGHT to 70. Since 60 < 70, move LEFT to 60. The key is found.
➕ Inserting into a BST
Insertion follows the same comparison rule as Search until an empty NULL position is found.
A new node is created at that position.
INSERT(root, value)
1. If root == NULL
return new Node(value)
2. If value < root->data
root->left =
INSERT(root->left, value)
3. Else if value > root->data
root->right =
INSERT(root->right, value)
4. Return root
💡 Example: Insert 60
50 → go RIGHT to 70 → go LEFT because 60 < 70 → the left position is empty → insert 60 there.
🎬 BST Search & Insert Visualizer
💻 BST Insert and Search in C
Visible Learning Program
#include <stdio.h>
#include <stdlib.h>
struct Node
{
int data;
struct Node *left;
struct Node *right;
};
struct Node *createNode(int value)
{
struct Node *newNode =
(struct Node *)malloc(sizeof(struct Node));
newNode->data = value;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}
struct Node *insert(struct Node *root, int value)
{
if(root == NULL)
return createNode(value);
if(value < root->data)
root->left = insert(root->left, value);
else if(value > root->data)
root->right = insert(root->right, value);
return root;
}
struct Node *search(struct Node *root, int key)
{
if(root == NULL || root->data == key)
return root;
if(key < root->data)
return search(root->left, key);
return search(root->right, key);
}
int main()
{
struct Node *root = NULL;
struct Node *result;
root = insert(root, 50);
root = insert(root, 30);
root = insert(root, 70);
root = insert(root, 20);
root = insert(root, 40);
root = insert(root, 60);
root = insert(root, 80);
result = search(root, 60);
if(result != NULL)
printf("60 Found\n");
else
printf("60 Not Found\n");
return 0;
}
Program Output
60 Found
Final BST
50
/ \
30 70
/ \ / \
20 40 60 80
🔎 Trace BST Insert + Search
💻 Program
🧠 What is happening?
📊 Live Variables
📚 Call Stack
🌲 Live BST Memory
—
⚡ BST Search & Insert Complexity
🎯 Interview Note
BST performance depends on height, not merely on the number of nodes.
A balanced BST keeps height near log n; a skewed BST can have height near n.
🗑️ Deleting a Node from a BST
BST deletion must remove the required key while still preserving the rule LEFT < ROOT < RIGHT. The exact action depends on how many children the node has.
💡 First Step is Always Search
Compare the key with the current node. Move LEFT for a smaller key and RIGHT for a larger key until the target node is found.
🧩 The 3 Cases of BST Deletion
NULL to its parent.
🧠 One Sequence Demonstrates All Three Cases
Initial BST
50
/ \
30 70
/ \ / \
20 40 60 80
Delete 30 → one-child case (only child 40 remains)
Delete 50 → two-child case (inorder successor = 60)
⚙️ BST Deletion Algorithm
DELETE(root, key)
1. If root == NULL
return root
2. If key < root->data
root->left =
DELETE(root->left, key)
3. Else if key > root->data
root->right =
DELETE(root->right, key)
4. Else
target node is found
A. If left == NULL
temp = right
free(root)
return temp
B. If right == NULL
temp = left
free(root)
return temp
C. Otherwise
temp =
minimum node in right subtree
root->data = temp->data
root->right =
DELETE(root->right,
temp->data)
5. Return root
🔑 Why the Inorder Successor?
The minimum value in the right subtree is greater than every value in the left subtree but is the smallest valid replacement larger than the current node. After copying it, we remove the original successor node from the right subtree.
🎬 BST Deletion Visualizer
💻 BST Deletion in C
Visible Learning Program
#include <stdio.h>
#include <stdlib.h>
struct Node
{
int data;
struct Node *left;
struct Node *right;
};
struct Node *createNode(int value)
{
struct Node *newNode =
(struct Node *)malloc(sizeof(struct Node));
newNode->data = value;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}
struct Node *insert(struct Node *root, int value)
{
if(root == NULL)
return createNode(value);
if(value < root->data)
root->left = insert(root->left, value);
else if(value > root->data)
root->right = insert(root->right, value);
return root;
}
struct Node *minValueNode(struct Node *root)
{
struct Node *current = root;
while(current != NULL && current->left != NULL)
current = current->left;
return current;
}
struct Node *deleteNode(struct Node *root, int key)
{
struct Node *temp;
if(root == NULL)
return root;
if(key < root->data)
{
root->left = deleteNode(root->left, key);
}
else if(key > root->data)
{
root->right = deleteNode(root->right, key);
}
else
{
if(root->left == NULL)
{
temp = root->right;
free(root);
return temp;
}
if(root->right == NULL)
{
temp = root->left;
free(root);
return temp;
}
temp = minValueNode(root->right);
root->data = temp->data;
root->right =
deleteNode(root->right, temp->data);
}
return root;
}
void inorder(struct Node *root)
{
if(root == NULL)
return;
inorder(root->left);
printf("%d ", root->data);
inorder(root->right);
}
int main()
{
struct Node *root = NULL;
root = insert(root, 50);
root = insert(root, 30);
root = insert(root, 70);
root = insert(root, 20);
root = insert(root, 40);
root = insert(root, 60);
root = insert(root, 80);
root = deleteNode(root, 20);
printf("After deleting 20: ");
inorder(root);
printf("\n");
root = deleteNode(root, 30);
printf("After deleting 30: ");
inorder(root);
printf("\n");
root = deleteNode(root, 50);
printf("After deleting 50: ");
inorder(root);
printf("\n");
return 0;
}
Program Output
After deleting 20: 30 40 50 60 70 80
After deleting 30: 40 50 60 70 80
After deleting 50: 40 60 70 80
Deletion Cases
20 → Leaf
30 → One Child
50 → Two Children
Successor of 50 → 60
🔎 Trace All Three BST Deletion Cases
💻 Program
🧠 What is happening?
📊 Live Variables
📚 Call Stack
🌲 Live BST Memory
—
⚡ BST Deletion Complexity
🎯 Interview Note
The difficult case is deleting a node with two children. A common solution is to replace its value with the inorder successor (minimum node in the right subtree), then recursively delete that successor.
⛰️ Binary Heap
A Binary Heap is a Complete Binary Tree that also follows a heap-order property. Because it is complete, it can be stored very efficiently in an array without explicit left/right pointers.
📌 Heap vs BST
A Heap guarantees only a parent-child ordering. It does not guarantee that every value in the left subtree is smaller than every value in the right subtree, so Heap Search is not the same as BST Search.
↕️ Two Basic Binary Heaps
🔵 Max Heap
For every node: Parent ≥ Children. The maximum value is always at the root.
50
/ \
30 40
/ \ / \
10 20 35 37
🟡 Min Heap
For every node: Parent ≤ Children. The minimum value is always at the root.
10
/ \
20 30
/ \ / \
40 50 35 37
🎯 Important
The root can be accessed in O(1), but inserting or deleting the root may require moving along the Tree height, which takes O(log n).
🧮 Heap Index Formulas
With 0-based indexing, a node stored at index i has:
(i - 1) / 2
2 * i + 1
2 * i + 2
➕ Insert into a Max Heap
Insert the new value at the next free array position to preserve the complete-tree shape. Then compare it with its parent and swap upward until the Max Heap property is restored. This upward correction is often called heapify-up or sift-up.
INSERT_MAX(heap, size, value)
1. Put value at heap[size]
2. size = size + 1
3. i = size - 1
4. While i > 0
parent = (i - 1) / 2
If heap[parent] >= heap[i]
stop
Swap heap[parent], heap[i]
i = parent
🧠 Insert 45
Before: 50 30 40 10 20 35 37
After : 50 45 40 30 20 35 37 10
➖ Delete the Root of a Max Heap
The root contains the maximum element. To delete it while preserving the complete-tree shape, move the last heap element to the root, reduce the size, then restore heap order by moving downward.
DELETE_MAX(heap, size)
1. Save heap[0]
2. Move heap[size - 1] to heap[0]
3. size = size - 1
4. HEAPIFY_DOWN(heap, size, 0)
5. Return saved maximum
💡 Why Move the Last Element?
Removing the last array element preserves the complete Binary Tree shape. Only the heap-order property may be broken, so we repair that with heapify-down.
🔧 Heapify Down
For a Max Heap, compare the current value with its children. If one child is larger, swap with the largest child and continue from that child index.
HEAPIFY_DOWN(heap, size, i)
1. largest = i
2. left = 2*i + 1
3. right = 2*i + 2
4. If left exists and
heap[left] > heap[largest]
largest = left
5. If right exists and
heap[right] > heap[largest]
largest = right
6. If largest == i
stop
7. Swap heap[i], heap[largest]
8. i = largest
9. Repeat
🎬 Binary Heap Visualizer
💻 Max Heap Insert + Delete in C
Visible Learning Program
#include <stdio.h>
#define MAX 100
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
void insertMaxHeap(int heap[], int *size, int value)
{
int i = *size;
int parent;
heap[i] = value;
(*size)++;
while(i > 0)
{
parent = (i - 1) / 2;
if(heap[parent] >= heap[i])
break;
swap(&heap[parent], &heap[i]);
i = parent;
}
}
void heapifyDown(int heap[], int size, int i)
{
int largest;
int left;
int right;
while(1)
{
largest = i;
left = 2 * i + 1;
right = 2 * i + 2;
if(left < size && heap[left] > heap[largest])
largest = left;
if(right < size && heap[right] > heap[largest])
largest = right;
if(largest == i)
break;
swap(&heap[i], &heap[largest]);
i = largest;
}
}
int deleteMax(int heap[], int *size)
{
int root;
if(*size == 0)
return -1;
root = heap[0];
heap[0] = heap[*size - 1];
(*size)--;
if(*size > 0)
heapifyDown(heap, *size, 0);
return root;
}
void printHeap(int heap[], int size)
{
int i;
for(i = 0; i < size; i++)
printf("%d ", heap[i]);
printf("\n");
}
int main()
{
int heap[MAX];
int size = 0;
int deleted;
insertMaxHeap(heap, &size, 50);
insertMaxHeap(heap, &size, 30);
insertMaxHeap(heap, &size, 40);
insertMaxHeap(heap, &size, 10);
insertMaxHeap(heap, &size, 20);
insertMaxHeap(heap, &size, 35);
insertMaxHeap(heap, &size, 37);
printf("Initial Max Heap: ");
printHeap(heap, size);
insertMaxHeap(heap, &size, 45);
printf("After inserting 45: ");
printHeap(heap, size);
deleted = deleteMax(heap, &size);
printf("Deleted root: %d\n", deleted);
printf("After deletion: ");
printHeap(heap, size);
return 0;
}
Program Output
Initial Max Heap: 50 30 40 10 20 35 37
After inserting 45: 50 45 40 30 20 35 37 10
Deleted root: 50
After deletion: 45 30 40 10 20 35 37
Core Operations
Insert:
append → heapify up
Delete root:
last → root
size--
heapify down
🔎 Trace Max Heap Insert + Delete
💻 Program
🧠 What is happening?
📊 Live Variables
🧮 Live Heap Array
⛰️ Live Heap Tree
—
⚡ Binary Heap Complexity
🎯 Interview Note
A Binary Heap is excellent when you repeatedly need the highest- or lowest-priority element. That is why it is a standard implementation of a Priority Queue. Advanced Heap families such as Binomial and Fibonacci Heaps belong in the later advanced Heap module.
🧠 Core Binary Tree & BST Problems
After learning traversal, BST operations, and Heap fundamentals, the next step is to solve recurring Tree interview problems. These problems strengthen recursion, subtree reasoning, height calculation, and BST ordering.
🎯 How to Think About Tree Problems
For many recursive Tree questions, first solve the same problem for the LEFT subtree, then for the RIGHT subtree, and finally combine those two answers at the current node.
🔢 Count Nodes, Leaves, Internal Nodes & Height
Total Nodes
COUNT(root)
if root == NULL
return 0
return 1
+ COUNT(root->left)
+ COUNT(root->right)
Leaf Nodes
LEAVES(root)
if root == NULL
return 0
if left == NULL
and right == NULL
return 1
return LEAVES(left)
+ LEAVES(right)
Internal Nodes
internal =
totalNodes - leafNodes
For a non-empty Tree, internal nodes are the nodes having at least one child.
Tree Height
HEIGHT(root)
if root == NULL
return 0
return 1 +
max(HEIGHT(left),
HEIGHT(right))
🧠 Fixed Example
10
/ \
20 30
/ \ / \
40 50 60 70
Leaf Nodes = 4
Internal Nodes = 3
Height = 3 levels = 2 edges
📌 Height Convention
Our C program returns height as the number of levels / nodes on the longest path. Some textbooks define height as the number of edges instead. Under that convention, this Tree has height 2.
🔍 Search an Ordinary Binary Tree
Unlike a BST, an ordinary Binary Tree has no ordering rule. So in the worst case we may need to examine every node.
SEARCH(root, key)
1. If root == NULL
return false
2. If root->data == key
return true
3. If SEARCH(root->left, key)
return true
4. Return
SEARCH(root->right, key)
🪞 Mirror a Binary Tree
To mirror a Binary Tree, swap the LEFT and RIGHT child pointers at every node.
void mirror(struct Node *root)
{
struct Node *temp;
if(root == NULL)
return;
temp = root->left;
root->left = root->right;
root->right = temp;
mirror(root->left);
mirror(root->right);
}
Before
10
/ \
20 30
After Mirror
10
/ \
30 20
👯 Check Whether Two Trees are Identical
Two Trees are identical when they have the same structure and the same data at corresponding nodes.
int identical(struct Node *a, struct Node *b)
{
if(a == NULL && b == NULL)
return 1;
if(a == NULL || b == NULL)
return 0;
return a->data == b->data &&
identical(a->left, b->left) &&
identical(a->right, b->right);
}
💡 Recursive Condition
Current values must match, LEFT subtrees must be identical, and RIGHT subtrees must be identical.
⚖️ Check Whether a Binary Tree is Height-Balanced
A Binary Tree is height-balanced when, at every node, the height difference between the LEFT and RIGHT subtrees is at most 1.
int checkHeight(struct Node *root)
{
int leftHeight;
int rightHeight;
if(root == NULL)
return 0;
leftHeight = checkHeight(root->left);
if(leftHeight == -1)
return -1;
rightHeight = checkHeight(root->right);
if(rightHeight == -1)
return -1;
if(abs(leftHeight - rightHeight) > 1)
return -1;
return 1 + (leftHeight > rightHeight
? leftHeight : rightHeight);
}
/* Balanced when checkHeight(root) != -1 */
🎯 Interview Improvement
Computing height separately at every node can become O(n²). Returning -1 immediately when
an unbalanced subtree is detected allows an O(n) solution.
📏 Diameter of a Binary Tree
The diameter is the number of nodes (or sometimes edges, depending on convention) on the longest path between any two nodes. That longest path may or may not pass through the root.
int diameterHeight(struct Node *root, int *diameter)
{
int leftHeight;
int rightHeight;
if(root == NULL)
return 0;
leftHeight = diameterHeight(root->left, diameter);
rightHeight = diameterHeight(root->right, diameter);
if(leftHeight + rightHeight + 1 > *diameter)
*diameter = leftHeight + rightHeight + 1;
return 1 + (leftHeight > rightHeight
? leftHeight : rightHeight);
}
🌿 Lowest Common Ancestor (LCA)
The Lowest Common Ancestor of two nodes is the lowest node in the Tree that has both target nodes in its subtree, allowing a node to be an ancestor of itself.
struct Node *lca(struct Node *root, int a, int b)
{
struct Node *left;
struct Node *right;
if(root == NULL ||
root->data == a ||
root->data == b)
return root;
left = lca(root->left, a, b);
right = lca(root->right, a, b);
if(left != NULL && right != NULL)
return root;
return left != NULL ? left : right;
}
Example
In the fixed Tree, LCA(40, 50) = 20 and LCA(40, 60) = 10.
↔️ Find Minimum and Maximum in a BST
BST ordering makes these operations very simple: keep moving LEFT for the minimum and RIGHT for the maximum.
struct Node *bstMin(struct Node *root)
{
if(root == NULL)
return NULL;
while(root->left != NULL)
root = root->left;
return root;
}
struct Node *bstMax(struct Node *root)
{
if(root == NULL)
return NULL;
while(root->right != NULL)
root = root->right;
return root;
}
⏮️ Predecessor & Successor in a BST
For a key in sorted BST order, the predecessor is the largest smaller key and the successor is the smallest larger key.
void predecessorSuccessor(
struct Node *root,
int key,
struct Node **pred,
struct Node **succ)
{
while(root != NULL)
{
if(key < root->data)
{
*succ = root;
root = root->left;
}
else if(key > root->data)
{
*pred = root;
root = root->right;
}
else
{
if(root->left != NULL)
*pred = bstMax(root->left);
if(root->right != NULL)
*succ = bstMin(root->right);
return;
}
}
}
📌 Core Rule
If the target has a LEFT subtree, its predecessor is the maximum of that subtree. If it has a RIGHT subtree, its successor is the minimum of that subtree. Ancestors encountered during search handle the remaining cases.
🎬 Important Tree Problems Visualizer
💻 Core Recursive Tree Problems in C
This learning program combines four core problems in one place: count total nodes, count leaf nodes, find height, and search an ordinary Binary Tree.
Visible Learning Program
#include <stdio.h>
#include <stdlib.h>
struct Node
{
int data;
struct Node *left;
struct Node *right;
};
struct Node *createNode(int value)
{
struct Node *newNode =
(struct Node *)malloc(sizeof(struct Node));
newNode->data = value;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}
int countNodes(struct Node *root)
{
int leftCount;
int rightCount;
if(root == NULL)
return 0;
leftCount = countNodes(root->left);
rightCount = countNodes(root->right);
return 1 + leftCount + rightCount;
}
int countLeaves(struct Node *root)
{
int leftLeaves;
int rightLeaves;
if(root == NULL)
return 0;
if(root->left == NULL && root->right == NULL)
return 1;
leftLeaves = countLeaves(root->left);
rightLeaves = countLeaves(root->right);
return leftLeaves + rightLeaves;
}
int treeHeight(struct Node *root)
{
int leftHeight;
int rightHeight;
if(root == NULL)
return 0;
leftHeight = treeHeight(root->left);
rightHeight = treeHeight(root->right);
if(leftHeight > rightHeight)
return leftHeight + 1;
return rightHeight + 1;
}
int searchTree(struct Node *root, int key)
{
if(root == NULL)
return 0;
if(root->data == key)
return 1;
if(searchTree(root->left, key))
return 1;
return searchTree(root->right, key);
}
int main()
{
struct Node *root;
int total;
int leaves;
int h;
root = createNode(10);
root->left = createNode(20);
root->right = createNode(30);
root->left->left = createNode(40);
root->left->right = createNode(50);
root->right->left = createNode(60);
root->right->right = createNode(70);
total = countNodes(root);
leaves = countLeaves(root);
h = treeHeight(root);
printf("Total Nodes: %d\n", total);
printf("Leaf Nodes: %d\n", leaves);
printf("Internal Nodes: %d\n", total - leaves);
printf("Height (levels): %d\n", h);
if(searchTree(root, 60))
printf("60 Found\n");
else
printf("60 Not Found\n");
return 0;
}
Program Output
Total Nodes: 7
Leaf Nodes: 4
Internal Nodes: 3
Height (levels): 3
60 Found
Fixed Tree
10
/ \
20 30
/ \ / \
40 50 60 70
🔎 Trace Core Recursive Tree Problems
💻 Program
🧠 What is happening?
📊 Live Variables
📚 Call Stack
🌳 Live Binary Tree
—
⚡ Important Tree Problems — Complexity
| Problem | Time | Auxiliary Space | Reason |
|---|---|---|---|
| Count Nodes / Leaves | O(n) | O(h) | Visit each node once; recursion follows Tree height. |
| Tree Height | O(n) | O(h) | Both subtrees may be examined. |
| Binary Tree Search | O(n) | O(h) | No ordering lets us discard a subtree safely. |
| Mirror / Identical | O(n) | O(h) | Each corresponding node is processed once. |
| Balanced Check | O(n) | O(h) | Optimized postorder computes height while checking balance. |
| Diameter | O(n) | O(h) | Optimized version computes height and diameter together. |
| LCA in Binary Tree | O(n) | O(h) | May need to search both subtrees. |
| BST Min / Max | O(h) | O(1) iterative | Follow only one extreme path. |
| BST Predecessor / Successor | O(h) | O(1) iterative | Follow one search path plus an extreme subtree path. |
🎯 Interview Note
The most common optimization pattern in Tree interviews is to avoid recomputing subtree information. For balance and diameter, calculate height while returning from the same recursion instead of running a separate height function again and again.
🌳 Binary Tree vs BST vs Binary Heap
These three structures are all tree-based, but they solve different problems. Choosing the correct structure is more important than simply knowing their definitions.
| Property | Binary Tree | Binary Search Tree | Binary Heap |
|---|---|---|---|
| Shape | Each node has at most two children. No general shape restriction. | Binary Tree shape; may become balanced or skewed. | Must be a Complete Binary Tree. |
| Ordering | No general key-ordering rule. | LEFT < ROOT < RIGHT under the duplicate policy. | Parent-child priority only: Max Heap or Min Heap. |
| Search arbitrary key | O(n) worst case. | O(h): O(log n) balanced, O(n) skewed. | O(n) worst case because Heap order is not a BST order. |
| Insert | Depends on the required Tree rule. | O(h). | O(log n) using heapify-up. |
| Delete | Depends on the application. | O(h), with 0/1/2-child cases. | Delete root in O(log n) using heapify-down. |
| Important traversal | Preorder, Inorder, Postorder, Level Order. | Inorder produces sorted keys. | Array / Level Order represents the complete shape naturally. |
| Root significance | Represents the top of the hierarchy. | Splits smaller and larger search regions. | Stores the maximum or minimum priority. |
| Typical use | Hierarchies, expression trees, recursive structure problems. | Ordered search, sets/maps, predecessor/successor. | Priority Queues, Heap Sort, scheduling, graph algorithms. |
🎯 Placement Shortcut
Need ordered search? Think BST. Need highest/lowest priority quickly? Think Heap. Need a general hierarchical structure? Think Binary Tree.
❓ Tree Interview Questions — Final Revision (40 Questions)
🎯 20 Tree Practice Problems
Try every problem yourself first. Use Solve It Yourself to write and test your C program. Open Hint only when necessary, and use Show Program when you want to study the complete official solution.
🏆 Scoring
Each problem contains 5 tests × 20 points = 100 points. Passing all tests without help can earn the full score. Opening a Hint caps the competitive score for that problem at 90. After opening Show Program, you can still complete the problem, but it is recorded as Completed rather than a new competitive solve.
🌳 Practice Coverage
The 20 problems cover traversals, node counting, height, Binary Tree search, mirror/identical/balance/diameter/LCA, BST insert/search/min-max/deletion/predecessor-successor, and Binary Max Heap operations.