Binary Trees
Objectives
- Understand tree terminology
- Implement a binary search tree (BST)
- Master the different traversal orders
- Analyze the complexity of operations
Terminology
50 <- Root
/ \
30 70 <- Internal nodes
/ \ / \
20 40 60 80 <- Leaves
| Term | Definition |
|---|---|
| Root | Node with no parent (top of the tree) |
| Leaf | Node with no children |
| Internal node | Node with at least one child |
| Height | Maximum distance from root to a leaf |
| Depth | Distance from a node to the root |
| Subtree | Tree formed by a node and its descendants |
Binary Search Tree (BST)
A BST satisfies the property:
- For every node N: values in the left subtree < N < values in the right subtree
Fundamental property
The inorder traversal of a BST yields the elements in sorted order!
Structure
typedef struct TreeNode {
int data;
struct TreeNode* left;
struct TreeNode* right;
} TreeNode;
typedef TreeNode* Tree;
Basic Functions
// Is the tree empty?
int is_empty(Tree tree) {
return tree == NULL;
}
// Is it a leaf?
int is_leaf(Tree tree) {
return tree != NULL
&& tree->left == NULL
&& tree->right == NULL;
}
Insertion (recursive)
Tree insert(Tree tree, int value) {
if (tree == NULL) {
return create_node(value);
}
if (value < tree->data) {
tree->left = insert(tree->left, value);
} else if (value > tree->data) {
tree->right = insert(tree->right, value);
}
// If equal, ignore (no duplicates)
return tree;
}
Search
int search(Tree tree, int value) {
if (tree == NULL) {
return 0; // Not found
}
if (value == tree->data) {
return 1; // Found
}
if (value < tree->data) {
return search(tree->left, value);
}
return search(tree->right, value);
}
Tree Traversals
Depth-First Search (DFS)
Preorder: Root - Left - Right
void preorder(Tree tree) {
if (tree != NULL) {
printf("%d ", tree->data); // Root
preorder(tree->left); // Left
preorder(tree->right); // Right
}
}
Inorder: Left - Root - Right
void inorder(Tree tree) {
if (tree != NULL) {
inorder(tree->left); // Left
printf("%d ", tree->data); // Root
inorder(tree->right); // Right
}
}
Postorder: Left - Right - Root
void postorder(Tree tree) {
if (tree != NULL) {
postorder(tree->left); // Left
postorder(tree->right); // Right
printf("%d ", tree->data); // Root
}
}
Traversal Example
50
/ \
30 70
/ \ / \
20 40 60 80
| Traversal | Result |
|---|---|
| Preorder | 50, 30, 20, 40, 70, 60, 80 |
| Inorder | 20, 30, 40, 50, 60, 70, 80 |
| Postorder | 20, 40, 30, 60, 80, 70, 50 |
Breadth-First Search (BFS)
Level by level, from left to right.
Result: 50, 30, 70, 20, 40, 60, 80
Implementation using a queue:
void level_order(Tree tree) {
if (tree == NULL) return;
Queue* q = create_queue();
enqueue(q, tree);
while (!is_queue_empty(q)) {
Tree current = dequeue(q);
printf("%d ", current->data);
if (current->left != NULL)
enqueue(q, current->left);
if (current->right != NULL)
enqueue(q, current->right);
}
}
Tree Measurements
// Height
int height(Tree tree) {
if (tree == NULL) return -1;
int h_left = height(tree->left);
int h_right = height(tree->right);
return 1 + (h_left > h_right ? h_left : h_right);
}
// Number of nodes
int count_nodes(Tree tree) {
if (tree == NULL) return 0;
return 1 + count_nodes(tree->left) + count_nodes(tree->right);
}
// Minimum (leftmost node)
int find_min(Tree tree) {
while (tree->left != NULL) {
tree = tree->left;
}
return tree->data;
}
Complexity
| Operation | Average case | Worst case (degenerate) |
|---|---|---|
| Search | O(log n) | O(n) |
| Insertion | O(log n) | O(n) |
| Deletion | O(log n) | O(n) |
| Min/Max | O(log n) | O(n) |
Degenerate trees
If insertions are made in ascending/descending order, the tree degenerates into a linked list and loses its advantages. Solutions: AVL trees, red-black trees.
Deletion in a BST
Three cases:
- Leaf: simply delete it
- One child: replace with the child
- Two children: replace with the successor (minimum of the right subtree) or the predecessor (maximum of the left subtree)
Applications
- Dictionaries and symbol tables
- File systems (directories)
- Syntax trees (compilers)
- Decision trees (AI)
- QuickSort (partitions like a BST)