Binary Search Tree
One key divides the number line into two ranges.
CodeBhavya
Store several sorted keys in every node using B-Trees, B+ Trees, 2โ3 Trees and 2โ3โ4 Treesโthe foundation of database and file-system indexes.
After completing this topic, you should be able to:
A binary search tree stores one key and at most two children per node. A multiway tree stores several sorted keys, so one node can direct the search into many child ranges.
One key divides the number line into two ranges.
k sorted keys divide the number line into k + 1 ranges.
Load one disk-sized node containing several keys.
Locate the first key greater than or equal to the target.
Return on equality or choose the corresponding child range.
Split overflow and repair underflow so all leaves remain level.
A node has at most m children and m โ 1 keys.
A node with k keys has k + 1 children.
Keys inside every node remain strictly ordered.
All leaves occur at the same depth.
| Operation | Main action | Balancing event | Typical time |
|---|---|---|---|
| Search | Search keys inside a node, then choose one child interval | None | O(log n) |
| Insertion | Insert in a leaf or descend through non-full nodes | Split a full node and promote a separator | O(log n) |
| Deletion | Remove from leaf or replace an internal key | Borrow, rotate, merge and possibly contract root | O(log n) |
| Traversal | Visit children and keys in sorted order | None | O(n) |
| Range search | Find the first key, then scan consecutive leaves | Uses B+ leaf links | O(log n + k) |
| Bulk loading | Build leaves from sorted data, then build upper levels | Occupancy planned bottom-up | O(n) |
Remove the key directly if the node keeps the minimum number of keys.
Replace with predecessor or successor when the corresponding child has spare keys.
Move a parent separator down and a sibling key up.
Combine two minimum children with their parent separator.
Never descend into a minimum child; borrow or merge first.
If an internal root becomes empty, its only child becomes the new root.
Find the first key โฅ target. Return on equality; otherwise descend through child i.
Visit child 0, key 0, child 1, key 1, โฆ, then the final child.
Use internal separators only for routing; confirm the record in a leaf.
Find the first qualifying leaf, then follow next-leaf links until the upper bound is passed.
A B-Tree stores keys and records in both internal and leaf nodes. With minimum degree t, every non-root node has t โ 1 to 2t โ 1 keys, and every internal non-root node has t to 2t children.
2t โ 1
t โ 1 for every non-root node
2t
At the same depth
#include <stdio.h>
#include <stdlib.h>
#define T 2
#define MAX_KEYS (2 * T - 1)
#define MAX_CHILDREN (2 * T)
typedef struct BTreeNode {
int keys[MAX_KEYS];
struct BTreeNode *child[MAX_CHILDREN];
int count;
int leaf;
} BTreeNode;
BTreeNode *createBTreeNode(int leaf) {
BTreeNode *node = malloc(sizeof(BTreeNode));
if (node == NULL) exit(EXIT_FAILURE);
node->count = 0;
node->leaf = leaf;
for (int i = 0; i < MAX_CHILDREN; i++) node->child[i] = NULL;
return node;
}
void traverseBTree(BTreeNode *node) {
int i;
for (i = 0; i < node->count; i++) {
if (!node->leaf) traverseBTree(node->child[i]);
printf("%d ", node->keys[i]); /* btree traverse */
}
if (!node->leaf) traverseBTree(node->child[i]);
}
BTreeNode *searchBTree(BTreeNode *node, int key) {
int i = 0;
while (i < node->count && key > node->keys[i]) i++; /* btree search loop */
if (i < node->count && key == node->keys[i]) return node; /* btree search result */
if (node->leaf) return NULL;
return searchBTree(node->child[i], key);
}
void splitBTreeChild(BTreeNode *parent, int index) {
BTreeNode *full = parent->child[index];
BTreeNode *right = createBTreeNode(full->leaf);
right->count = T - 1;
for (int j = 0; j < T - 1; j++) right->keys[j] = full->keys[j + T];
if (!full->leaf)
for (int j = 0; j < T; j++) right->child[j] = full->child[j + T];
full->count = T - 1;
for (int j = parent->count; j >= index + 1; j--)
parent->child[j + 1] = parent->child[j];
parent->child[index + 1] = right;
for (int j = parent->count - 1; j >= index; j--)
parent->keys[j + 1] = parent->keys[j];
parent->keys[index] = full->keys[T - 1]; /* btree promote median */
parent->count++; /* btree split child */
}
void insertBTreeNonFull(BTreeNode *node, int key) {
int i = node->count - 1;
if (node->leaf) {
while (i >= 0 && key < node->keys[i]) {
node->keys[i + 1] = node->keys[i];
i--;
}
node->keys[i + 1] = key;
node->count++; /* btree insert leaf */
return;
}
while (i >= 0 && key < node->keys[i]) i--;
i++; /* btree choose child */
if (node->child[i]->count == MAX_KEYS) {
splitBTreeChild(node, i);
if (key > node->keys[i]) i++;
}
insertBTreeNonFull(node->child[i], key);
}
void insertBTree(BTreeNode **root, int key) {
if (searchBTree(*root, key) != NULL) return;
if ((*root)->count == MAX_KEYS) {
BTreeNode *newRoot = createBTreeNode(0);
newRoot->child[0] = *root;
splitBTreeChild(newRoot, 0); /* btree split root */
*root = newRoot;
}
insertBTreeNonFull(*root, key); /* btree insert call */
}
int findKey(BTreeNode *node, int key) {
int index = 0;
while (index < node->count && node->keys[index] < key) index++;
return index;
}
int predecessor(BTreeNode *node, int index) {
BTreeNode *current = node->child[index];
while (!current->leaf) current = current->child[current->count];
return current->keys[current->count - 1];
}
int successor(BTreeNode *node, int index) {
BTreeNode *current = node->child[index + 1];
while (!current->leaf) current = current->child[0];
return current->keys[0];
}
void removeBTreeKey(BTreeNode *node, int key);
void mergeBTreeChildren(BTreeNode *node, int index) {
BTreeNode *left = node->child[index];
BTreeNode *right = node->child[index + 1];
left->keys[T - 1] = node->keys[index];
for (int i = 0; i < right->count; i++) left->keys[i + T] = right->keys[i];
if (!left->leaf)
for (int i = 0; i <= right->count; i++) left->child[i + T] = right->child[i];
for (int i = index + 1; i < node->count; i++) node->keys[i - 1] = node->keys[i];
for (int i = index + 2; i <= node->count; i++) node->child[i - 1] = node->child[i];
left->count += right->count + 1;
node->count--;
free(right); /* btree merge children */
}
void borrowBTreeLeft(BTreeNode *node, int index) {
BTreeNode *child = node->child[index];
BTreeNode *sibling = node->child[index - 1];
for (int i = child->count - 1; i >= 0; i--) child->keys[i + 1] = child->keys[i];
if (!child->leaf)
for (int i = child->count; i >= 0; i--) child->child[i + 1] = child->child[i];
child->keys[0] = node->keys[index - 1];
if (!child->leaf) child->child[0] = sibling->child[sibling->count];
node->keys[index - 1] = sibling->keys[sibling->count - 1];
child->count++;
sibling->count--; /* btree borrow left */
}
void borrowBTreeRight(BTreeNode *node, int index) {
BTreeNode *child = node->child[index];
BTreeNode *sibling = node->child[index + 1];
child->keys[child->count] = node->keys[index];
if (!child->leaf) child->child[child->count + 1] = sibling->child[0];
node->keys[index] = sibling->keys[0];
for (int i = 1; i < sibling->count; i++) sibling->keys[i - 1] = sibling->keys[i];
if (!sibling->leaf)
for (int i = 1; i <= sibling->count; i++) sibling->child[i - 1] = sibling->child[i];
child->count++;
sibling->count--; /* btree borrow right */
}
void prepareBTreeChild(BTreeNode *node, int index) {
if (index > 0 && node->child[index - 1]->count >= T)
borrowBTreeLeft(node, index);
else if (index < node->count && node->child[index + 1]->count >= T)
borrowBTreeRight(node, index);
else if (index < node->count)
mergeBTreeChildren(node, index);
else
mergeBTreeChildren(node, index - 1);
}
void removeBTreeKey(BTreeNode *node, int key) {
int index = findKey(node, key); /* btree delete locate */
if (index < node->count && node->keys[index] == key) {
if (node->leaf) {
for (int i = index + 1; i < node->count; i++) node->keys[i - 1] = node->keys[i];
node->count--; /* btree delete leaf */
} else if (node->child[index]->count >= T) {
int value = predecessor(node, index);
node->keys[index] = value; /* btree delete predecessor */
removeBTreeKey(node->child[index], value);
} else if (node->child[index + 1]->count >= T) {
int value = successor(node, index);
node->keys[index] = value; /* btree delete successor */
removeBTreeKey(node->child[index + 1], value);
} else {
mergeBTreeChildren(node, index);
removeBTreeKey(node->child[index], key);
}
return;
}
if (node->leaf) return;
int last = (index == node->count);
if (node->child[index]->count < T) prepareBTreeChild(node, index);
if (last && index > node->count) removeBTreeKey(node->child[index - 1], key);
else removeBTreeKey(node->child[index], key);
}
void deleteBTree(BTreeNode **root, int key) {
removeBTreeKey(*root, key); /* btree delete call */
if ((*root)->count == 0 && !(*root)->leaf) {
BTreeNode *old = *root;
*root = (*root)->child[0];
free(old); /* btree contract root */
}
}
int main(void) {
int n, key, query, removeKey;
BTreeNode *root = createBTreeNode(1); /* btree create root */
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", &key);
insertBTree(&root, key);
}
scanf("%d%d", &query, &removeKey);
printf("Search: %s\n", searchBTree(root, query) ? "Found" : "Not Found"); /* btree search call */
deleteBTree(&root, removeKey);
printf("After deletion: ");
traverseBTree(root);
printf("\n"); /* btree complete */ /* multiway complete */
return 0;
}10
10 20 5 6 12 30 7 17 3 25
17 6Search: Found
After deletion: 3 5 7 10 12 17 20 25 30A B+ Tree stores separator copies in internal nodes and keeps every actual record in leaves. Leaves are linked from left to right, making sequential and range access exceptionally efficient.
Contain only routing keys and child pointers, so fan-out is high.
Contain all searchable records and a pointer to the next leaf.
Always finishes at a leaf.
O(log n + k) using leaf links.
Copy the first key of the right leaf into the parent.
Promote the middle separator and remove it from the children.
#include <stdio.h>
#include <stdlib.h>
#define ORDER 4
#define MAX_KEYS (ORDER - 1)
typedef struct BPlusNode {
int keys[ORDER];
struct BPlusNode *child[ORDER + 1];
struct BPlusNode *next;
int count;
int leaf;
} BPlusNode;
typedef struct SplitResult {
int split;
int separator;
BPlusNode *right;
} SplitResult;
BPlusNode *createBPlusNode(int leaf) {
BPlusNode *node = malloc(sizeof(BPlusNode));
if (node == NULL) exit(EXIT_FAILURE);
node->count = 0;
node->leaf = leaf;
node->next = NULL;
for (int i = 0; i <= ORDER; i++) node->child[i] = NULL;
return node;
}
SplitResult insertBPlusRecursive(BPlusNode *node, int key) {
SplitResult result = {0, 0, NULL};
if (node->leaf) {
int index = 0;
while (index < node->count && node->keys[index] < key) index++;
if (index < node->count && node->keys[index] == key) return result;
for (int i = node->count; i > index; i--) node->keys[i] = node->keys[i - 1];
node->keys[index] = key;
node->count++; /* bplus insert leaf */
if (node->count <= MAX_KEYS) return result;
BPlusNode *right = createBPlusNode(1);
int leftCount = node->count / 2;
right->count = node->count - leftCount;
for (int i = 0; i < right->count; i++) right->keys[i] = node->keys[leftCount + i];
node->count = leftCount;
right->next = node->next;
node->next = right;
result.split = 1;
result.separator = right->keys[0];
result.right = right; /* bplus split leaf */
return result;
}
int index = 0;
while (index < node->count && key >= node->keys[index]) index++; /* bplus choose child */
SplitResult childResult = insertBPlusRecursive(node->child[index], key);
if (!childResult.split) return result;
for (int i = node->count; i > index; i--) node->keys[i] = node->keys[i - 1];
for (int i = node->count + 1; i > index + 1; i--) node->child[i] = node->child[i - 1];
node->keys[index] = childResult.separator;
node->child[index + 1] = childResult.right;
node->count++;
if (node->count <= MAX_KEYS) return result;
int middle = node->count / 2;
BPlusNode *right = createBPlusNode(0);
result.separator = node->keys[middle];
right->count = node->count - middle - 1;
for (int i = 0; i < right->count; i++) right->keys[i] = node->keys[middle + 1 + i];
for (int i = 0; i <= right->count; i++) right->child[i] = node->child[middle + 1 + i];
node->count = middle;
result.split = 1;
result.right = right; /* bplus split internal */
return result;
}
void insertBPlus(BPlusNode **root, int key) {
SplitResult result = insertBPlusRecursive(*root, key); /* bplus insert call */
if (result.split) {
BPlusNode *newRoot = createBPlusNode(0);
newRoot->keys[0] = result.separator;
newRoot->child[0] = *root;
newRoot->child[1] = result.right;
newRoot->count = 1;
*root = newRoot; /* bplus new root */
}
}
BPlusNode *findBPlusLeaf(BPlusNode *root, int key) {
BPlusNode *node = root;
while (!node->leaf) {
int index = 0;
while (index < node->count && key >= node->keys[index]) index++; /* bplus search loop */
node = node->child[index];
}
return node;
}
int searchBPlus(BPlusNode *root, int key) {
BPlusNode *leaf = findBPlusLeaf(root, key);
for (int i = 0; i < leaf->count; i++)
if (leaf->keys[i] == key) return 1; /* bplus search result */
return 0;
}
void rangeBPlus(BPlusNode *root, int low, int high) {
BPlusNode *leaf = findBPlusLeaf(root, low); /* bplus range call */
while (leaf != NULL) {
for (int i = 0; i < leaf->count; i++) {
if (leaf->keys[i] > high) return;
if (leaf->keys[i] >= low) printf("%d ", leaf->keys[i]); /* bplus range output */
}
leaf = leaf->next;
}
}
int main(void) {
int n, key, query, low, high;
BPlusNode *root = createBPlusNode(1); /* bplus create root */
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", &key);
insertBPlus(&root, key);
}
scanf("%d%d%d", &query, &low, &high);
printf("Search: %s\n", searchBPlus(root, query) ? "Found" : "Not Found"); /* bplus search call */
printf("Range: ");
rangeBPlus(root, low, high);
printf("\n"); /* bplus complete */ /* multiway complete */
return 0;
}10
5 10 15 20 25 30 35 40 45 50
25 18 42Search: Found
Range: 20 25 30 35 40Every internal node is either a 2-node containing one key and two children, or a 3-node containing two keys and three children. All leaves remain at the same level.
One key and two child ranges.
Two ordered keys and three child ranges.
A temporary three-key node splits and promotes its middle key.
Redistribute through the parent or merge with a sibling.
#include <stdio.h>
#include <stdlib.h>
typedef struct TTNode {
int keys[3];
struct TTNode *child[4];
struct TTNode *parent;
int count;
int leaf;
} TTNode;
TTNode *createTTNode(int leaf) {
TTNode *node = malloc(sizeof(TTNode));
if (node == NULL) exit(EXIT_FAILURE);
node->count = 0;
node->leaf = leaf;
node->parent = NULL;
for (int i = 0; i < 4; i++) node->child[i] = NULL;
return node;
}
TTNode *searchTT(TTNode *node, int key) {
while (node != NULL) {
int index = 0;
while (index < node->count && key > node->keys[index]) index++; /* tt search loop */
if (index < node->count && key == node->keys[index]) return node; /* tt search result */
if (node->leaf) return NULL;
node = node->child[index];
}
return NULL;
}
void insertKeySorted(TTNode *node, int key) {
int index = node->count;
while (index > 0 && key < node->keys[index - 1]) {
node->keys[index] = node->keys[index - 1];
index--;
}
node->keys[index] = key;
node->count++;
}
void insertPromoted(TTNode *parent, TTNode *left, int key, TTNode *right) {
int childIndex = 0;
while (childIndex <= parent->count && parent->child[childIndex] != left) childIndex++;
for (int i = parent->count; i > childIndex; i--) parent->keys[i] = parent->keys[i - 1];
for (int i = parent->count + 1; i > childIndex + 1; i--) parent->child[i] = parent->child[i - 1];
parent->keys[childIndex] = key;
parent->child[childIndex + 1] = right;
right->parent = parent;
parent->count++;
}
void splitTT(TTNode **root, TTNode *node) {
while (node->count == 3) {
int promoted = node->keys[1];
TTNode *right = createTTNode(node->leaf);
right->keys[0] = node->keys[2];
right->count = 1;
node->count = 1;
if (!node->leaf) {
right->child[0] = node->child[2];
right->child[1] = node->child[3];
right->child[0]->parent = right;
right->child[1]->parent = right;
}
if (node->parent == NULL) {
TTNode *newRoot = createTTNode(0);
newRoot->keys[0] = promoted;
newRoot->count = 1;
newRoot->child[0] = node;
newRoot->child[1] = right;
node->parent = newRoot;
right->parent = newRoot;
*root = newRoot; /* tt new root */
return;
}
TTNode *parent = node->parent;
insertPromoted(parent, node, promoted, right); /* tt promote middle */
node = parent;
}
}
void insertTT(TTNode **root, int key) {
if (searchTT(*root, key) != NULL) return;
TTNode *node = *root;
while (!node->leaf) {
int index = 0;
while (index < node->count && key > node->keys[index]) index++;
node = node->child[index]; /* tt choose child */
}
insertKeySorted(node, key); /* tt insert leaf */
splitTT(root, node); /* tt insert call */
}
void traverseTT(TTNode *node) {
int i;
for (i = 0; i < node->count; i++) {
if (!node->leaf) traverseTT(node->child[i]);
printf("%d ", node->keys[i]); /* tt traverse */
}
if (!node->leaf) traverseTT(node->child[i]);
}
int main(void) {
int n, key, query;
TTNode *root = createTTNode(1); /* tt create root */
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", &key);
insertTT(&root, key);
}
scanf("%d", &query);
printf("Search: %s\n", searchTT(root, query) ? "Found" : "Not Found"); /* tt search call */
printf("Sorted: ");
traverseTT(root);
printf("\n"); /* tt complete */ /* multiway complete */
return 0;
}9
20 10 30 5 15 25 35 12 18
25Search: Found
Sorted: 5 10 12 15 18 20 25 30 35A 2โ3โ4 Tree permits 2-nodes, 3-nodes and 4-nodes. It is exactly a B-Tree with t = 2, so a node stores one, two or three keys and has two, three or four children.
1 key, 2 children
2 keys, 3 children
3 keys, 4 children
Each 3/4-node can be represented by linked red nodes.
The complete B-Tree C program above uses T = 2; therefore, it is already a complete 2โ3โ4 Tree program supporting insertion, search, traversal and deletion. The visualizer provides a separate 2โ3โ4 selection so you can study the same rules using the familiar node names.
| Structure | Keys per node | Where records live | Best strength | Common use |
|---|---|---|---|---|
| B-Tree | Many | Internal and leaves | General balanced disk index | File systems, storage engines |
| B+ Tree | Many | Leaves only | Range and sequential access | Database indexes |
| 2โ3 Tree | 1 or 2 | All nodes | Simple multiway balancing theory | Teaching and proofs |
| 2โ3โ4 Tree | 1, 2 or 3 | All nodes | Connection to B-Trees and RedโBlack Trees | Teaching balanced search trees |
Choose a structure and operation, then follow node scans, child selection, median promotion, splitting, searching, deletion repair and range output step by step.
CodeBhavyaStep 0 of 0
Select a program. The matching complete C source appears with line numbers, live variables and the evolving multiway tree.
โ
Step 0 of 0
Use a B-Tree when records may be stored throughout the tree.
Use a B+ Tree because all records are in linked leaves.
Use a 2โ3 Tree for the smallest clean split and underflow cases.
Use a 2โ3โ4 Tree to understand how red links encode multi-key nodes.
Child i contains keys between separator i โ 1 and separator i.
An internal split must redistribute both keys and child pointers.
B+ leaf split copies a separator; a B-Tree split moves the median.
Top-down deletion repairs the child before descent.
An empty internal root must be replaced by its only child.
Internal separators route the search; records are confirmed in leaves.