Binary Search Trees · AVL Trees · B-Trees · B+ Trees
Data Structures
and Algorithms
5.4 Search Trees
2.5 – 3 Hours
By the end of this lecture, you will be able to:
We will build from simple to complex structures. Keep your calculators ready to compute Balance Factors during AVL rotations!
Searching is the single most frequent action executed on computers.
| Data Structure | Average Search Time | Sorted? |
|---|---|---|
| Array | O(n) | No |
| Linked List | O(n) | No |
| Binary Tree (unordered) | O(n) | No |
| Search Tree | O(log n) | Yes |
Where are Search Trees used in production systems?
B-Tree indexes in MySQL & PostgreSQL.
ext4 and NTFS directory maps.
Fast prefix search maps.
Prefix tables for packet forwarding.
"An organized tree architecture ensuring ordered lookup paths."
A Search Tree is a node-based binary or multi-way tree structure where:
A binary search tree has at most 2 children per node. An m-way tree can have up to $m$ children.
| Operation | Sorted Array | Linked List | Hash Table | BST (Average) |
|---|---|---|---|---|
| Search | O(log n) | O(n) | O(1) | O(log n) |
| Insert | O(n) | O(1) | O(1) | O(log n) |
| Delete | O(n) | O(n) | O(1) | O(log n) |
| Range Queries | O(log n + k) | O(n) | O(n) | O(log n + k) |
"Formal definition, step-by-step constructions, and implementation details."
For every node $X$ in the tree:
We will construct a BST from scratch using this sequence of inputs:
[50, 30, 70, 20, 40, 60, 80]
Each insertion requires starting at the root and recursively choosing left or right edges.
Start from the root node. If input key is smaller than node key, go left. If greater, go right. Repeat until a NULL child pointer is found.
First value inserted into an empty tree becomes the root node.
Node 50 is created. Left and right subtrees are NULL.
BST Construction is now complete! The resulting tree is fully balanced.
Node* search(Node* root, int key) {
if (root == NULL || root->key == key)
return root;
if (key < root->key)
return search(root->left, key);
return search(root->right, key);
}
Node* insert(Node* node, int key) {
if (node == NULL)
return newNode(key);
if (key < node->key)
node->left = insert(node->left, key);
else if (key > node->key)
node->right = insert(node->right, key);
return node;
}
Like search, we traverse recursively:
Deletion is more complex than insertion because we must maintain the BST property after removal.
Node to delete is a Leaf node.
Node has One Child.
Node has Two Children.
Node 20 is a leaf. We can remove it directly without moving any other nodes.
Result: Set left child pointer of parent (30) to NULL and free memory of node 20.
Assume we deleted 20, now we delete 30. Node 30 has only one child (40).
Bypassing node 30: Connect the parent of 30 (which is 50) directly to 30's child (40).
Node 50 has two children. We cannot delete it directly.
Instead, replace its value with its Inorder Successor (smallest key in the right subtree) or Inorder Predecessor (largest key in the left subtree).
Successor of 50 is 60. We copy 60 to the root, and then recursively delete 60 from the right subtree.
Node* deleteNode(Node* root, int key) {
if (root == NULL) return root;
if (key < root->key) root->left = deleteNode(root->left, key);
else if (key > root->key) root->right = deleteNode(root->right, key);
else {
if (root->left == NULL) {
Node* temp = root->right; free(root); return temp;
} else if (root->right == NULL) {
Node* temp = root->left; free(root); return temp;
}
Node* temp = minValueNode(root->right); // Successor
root->key = temp->key;
root->right = deleteNode(root->right, temp->key);
}
return root;
}
| Operation | Best Case | Average Case | Worst Case |
|---|---|---|---|
| Search | O(1) | O(log n) | O(n) |
| Insertion | O(log n) | O(log n) | O(n) |
| Deletion | O(log n) | O(log n) | O(n) |
| Space | O(n) | O(n) | O(n) |
BSTs provide the foundational logic for more complex structures like AVL and B-Trees, which power these modern systems.
"Self-balancing Binary Search Trees, balance calculations, and LL/RR/LR/RL rotations."
If we insert keys in sorted order: [10, 20, 30, 40], a standard BST becomes skewed.
A skewed BST is structurally identical to a singly Linked List but consumes double the memory for pointers.
An AVL Tree maintains balance by computing the Balance Factor (BF) for every node.
For every node, the Balance Factor must be:
If \( \text{BF} > 1 \) or \( \text{BF} < -1 \), the node becomes imbalanced and requires one or more tree rotations to restore balance.
Height(Left) = 1
Height(Right) = 1
BF = 1 − 1 = 0
Let's trace how heights propagate upwards:
Depending on the direction of imbalance, we apply one of four rotations:
Single Right Rotation. Occurs when left child of left child overflows.
Single Left Rotation. Occurs when right child of right child overflows.
Double Rotation (Left-Right). Left child's right child overflows.
Double Rotation (Right-Left). Right child's left child overflows.
RightRotate(z)
1. y = z->left
2. T3 = y->right
3. y->right = z
4. z->left = T3
5. Update Height(z)
6. Update Height(y)
7. return y
LeftRotate(z)
1. y = z->right;
2. T2 = y->left;
3. y->left = z;
4. z->right = T2;
5. UpdateHeight(z);
6. UpdateHeight(y);
7. return y;
LRRotate(z)
1. z->left = LeftRotate(z->left);
2. return RightRotate(z);
RLRotate(z)
1. z->right = RightRotate(z->right);
2. return LeftRotate(z);
Node* rightRotate(Node* y) {
Node* x = y->left;
Node* T2 = x->right;
// Perform rotation
x->right = y;
y->left = T2;
// Update heights
y->height = max(height(y->left), height(y->right)) + 1;
x->height = max(height(x->left), height(x->right)) + 1;
// Return new root
return x;
}
Insert: [10, 20, 30] into AVL.
Subtree is balanced automatically. Height remains 2 instead of 3.
Because the tree guarantees balance, heights are bound by $\log n$.
| Operation | Average Case | Worst Case |
|---|---|---|
| Search | O(log n) | O(log n) |
| Insertion | O(log n) | O(log n) |
| Deletion | O(log n) | O(log n) |
"Extending search trees to hold multiple keys per node — optimizing database and disk page storage limits."
Why do binary search trees fail for large database tables containing millions of rows?
Hardware reads data in blocks (pages), typically 4KB or 8KB. Multi-way trees pack multiple keys inside a single block to minimize block loads.
An M-Way Search Tree node contains:
Subtree $P_i$ contains keys between $K_{i-1}$ and $K_i$.
To find a key $X$ in a node with $k$ keys ($K_1 \dots K_k$):
In node [20 | 50 | 80]:
1. 40 > 20
2. 40 < 50
Result: Follow pointer $P_2$ (middle child).
Just like standard BSTs, basic M-way trees don't balance themselves. They can become skewed, leading to $O(n)$ search times.
"Self-balancing, multi-way search tree. Definition, properties, split promotions, and complexities."
A B-Tree of order $m$ satisfies:
Leaves being at the same depth guarantees that the tree height remains strictly balanced $O(\log n)$. It grows and shrinks from the root, not the leaves.
Insert: [10, 20, 30] in a B-Tree of Order 4.
Insert: [40]
Insert: [50, 60, 70]
Deletion requires maintaining minimum keys (1 for Order 4 internal, 1 for leaves here).
If a node underflows, borrow a key from a rich sibling through the parent.
Sibling Key → Parent → Underflowed Node.
What if siblings are also at minimum capacity?
Parent 20 merged down with 10.
Deleting a key that is not in a leaf.
To delete root 40, find predecessor (e.g., 30 in leaf). Replace 40 with 30. Now delete 30 from the leaf.
| Operation | Average Case | Worst Case | Disk Reads |
|---|---|---|---|
| Search | O(log n) | O(log n) | O(log_m n) |
| Insertion | O(log n) | O(log n) | O(log_m n) |
| Deletion | O(log n) | O(log n) | O(log_m n) |
"Optimizing B-Trees for database indexing. Leaf node chains, range queries, and structural layout."
How does a B+ Tree differ from a B-Tree?
We can perform full range queries ($O(\log n)$ to find start, then linear scan along leaves) without visiting parent nodes again.
Insert: [10, 20, 30, 40] in B+ Tree (Order 4)
What happens when an internal (index) node overflows?
Leaves → Split & Copy up.
Internals → Split & Push up.
Internal overflow during large bulk inserts:
Data leaves not shown.
Deleting a key always happens at the leaf level.
In B+ Trees, an index key can exist even if its corresponding data record was deleted, because it still correctly routes $X < K$ and $X \ge K$.
Query: Find all keys in range 15 to 35.
Requires zero backtracking to parent nodes. Standard B-Trees would require multiple upward/downward traversals to fetch ordered ranges.
| Tree Type | Max Child | Height Balance | Search Time | Ideal Application |
|---|---|---|---|---|
| BST | 2 | None (can skew) | $O(n)$ Worst Case | General in-memory maps |
| AVL | 2 | Strict height $\le 1$ | $O(\log n)$ Guaranteed | Lookup-heavy in-memory maps |
| B-Tree | $m$ | Perfect leaf level | $O(\log_m n)$ | Storage/Database systems |
| B+ Tree | $m$ | Perfect leaf level | $O(\log_m n)$ | DB indexing (MySQL, SQLite) |
NTFS uses B-Trees. ext4 uses HTrees (similar structure).
MySQL InnoDB, SQLite, and PostgreSQL use B+ Trees.
Linux kernel uses red-black trees (similar to AVL) for VM space.
Double-check balance factor signs. Draw out parent-child boundaries during rotations to keep tracks clear.
Work on these problems during this workshop segment:
We will solve these live on the board in 10 minutes. Discuss your solutions with your partner!
Choose AVL for fast memory lookups, B+ Trees for large disk storage indexes, and BST for simple prototype implementations.
Any Questions?
contact@university.edu
cs.university.edu/dsa