
Data Structure
Networking
RDBMS
Operating System
Java
MS Excel
iOS
HTML
CSS
Android
Python
C Programming
C++
C#
MongoDB
MySQL
Javascript
PHP
- Selected Reading
- UPSC IAS Exams Notes
- Developer's Best Practices
- Questions and Answers
- Effective Resume Writing
- HR Interview Questions
- Computer Glossary
- Who is Who
2-3 Trees (Search and Insert) in C/C++?
What is 2-3 Trees?
A 2-3 tree is a tree data structure, where each internal node has either 2 or 3 children (also, we can say that 2-nodes and 3-nodes, respectively). It is a type of B-tree that ensures efficient search, insertion, and deletion operations with O(logn) time complexity.

Properties of 2-3 tree
- 2-node contains one data element and has two children (or none if it is a leaf).
- 3-node contains two data elements and has three children (or none if it is a leaf).
- Data is stored in the sorted order.
- It is a balanced tree.
- All the leaf node are at the same level.
- Each node can either be leaf, 2 node, or 3 node.
2-3 Tree: Search Operation
Searching for an element in a 2-3 tree is same as searching for an item in a binary search tree. To search for a key K in the given 2-3 tree T, we follow the following steps:
Base Case
- If T is empty, return false (K can not be found in the tree).
- If the current node contains the data value that is equal to K, return true.
- If we reached to the leaf-node and it doesn't contain the required key value K, return false.
Recursive Call
- If K < current_node.left_val, then we explore the left subtree of the current node.
- Else if current_node.left_val < K < current_node.right_val, then we explore the middle subtree of the current node.
- Else if K > current_node.right_val, then we explore the right subtree of the current node.
Let's see the following example: Search 5 in following 2-3 tree.




Following is the code of the search operation:
bool search(Node* node, int key) { if (!node) return false; int i = 0; while (i < node->nKeys && key > node->key[i]) ++i; if (i < node->nKeys && key == node->key[i]) return true; return search(node->child[i], key); }
2-3 Tree: Insertion Operation
To perform an insertion operation in a 2-3 tree, we need to find the proper location of the key 'k' and append it there.
- Find the proper leaf node where the new key should go.
- Insert the key:
- If the node has 1 key, add the new key.
- If it already has 2 keys, then:
- Temporarily store 3 keys
- Split the node into two nodes with 1 key each
- Promote the middle key to the parent
- If the root splits, create a new root with 1 key and two children.
There are three possible cases in insertion, which you can see below:
Case 1: Insert in a node with only one data element: Insert 4 in the following 2-3 Tree:

Case 2: Insert a node with two data element whose parent contains only one data element: Insert 10 in the following 2-3 Tree:


Case 3: Insert in a node with two data elements whose parent also contains two data elements: Insert 1 in the following 2-3 Tree:


Following is the code of the insertion operation:
void insertInternal(Node*& node, int key, int& upKey, Node*& newChild) { int tempKeys[3], i; Node* tempChildren[4]; // If the current node is a leaf if (node->isLeaf) { // Copy existing keys to a temporary array for (i = 0; i < node->nKeys; ++i) tempKeys[i] = node->key[i]; // Insert the new key in sorted order i = node->nKeys - 1; while (i >= 0 && key < tempKeys[i]) { tempKeys[i + 1] = tempKeys[i]; --i; } tempKeys[i + 1] = key; // If the leaf node has room (only 1 key), insert without splitting if (node->nKeys < 2) { for (int j = 0; j <= node->nKeys; ++j) node->key[j] = tempKeys[j]; node->nKeys++; newChild = nullptr; // No split occurred } else { // Leaf node is full, needs to be split node->key[0] = tempKeys[0]; // Left node keeps first key node->nKeys = 1; newChild = new Node(); // Create right node newChild->key[0] = tempKeys[2]; // Right node gets third key newChild->nKeys = 1; upKey = tempKeys[1]; // Promote middle key to parent } } else { // Internal node: find child to recurse into i = node->nKeys - 1; while (i >= 0 && key < node->key[i]) --i; int pos = i + 1; int tempUpKey; Node* tempNewChild = nullptr; // Recursively insert into appropriate child insertInternal(node->child[pos], key, tempUpKey, tempNewChild); // If no split happened in child, just return if (!tempNewChild) { newChild = nullptr; return; } // Child split occurred: insert promoted key into current node for (i = 0; i < node->nKeys; ++i) tempKeys[i] = node->key[i]; for (i = 0; i <= node->nKeys; ++i) tempChildren[i] = node->child[i]; // Shift keys and children to make space for new key and child i = node->nKeys - 1; while (i >= pos) { tempKeys[i + 1] = tempKeys[i]; tempChildren[i + 2] = tempChildren[i + 1]; --i; } // Insert the new promoted key and corresponding child tempKeys[pos] = tempUpKey; tempChildren[pos + 1] = tempNewChild; // If current node has room, insert without splitting if (node->nKeys < 2) { for (int j = 0; j <= node->nKeys + 1; ++j) node->child[j] = tempChildren[j]; for (int j = 0; j <= node->nKeys; ++j) node->key[j] = tempKeys[j]; node->nKeys++; newChild = nullptr; } else { // Current node is full, needs to be split node->key[0] = tempKeys[0]; node->child[0] = tempChildren[0]; node->child[1] = tempChildren[1]; node->nKeys = 1; newChild = new Node(); newChild->isLeaf = false; newChild->key[0] = tempKeys[2]; newChild->child[0] = tempChildren[2]; newChild->child[1] = tempChildren[3]; newChild->nKeys = 1; upKey = tempKeys[1]; // Promote middle key to parent } } } // Entry point for inserting a key into the 2-3 tree void insert(Node*& root, int key) { int upKey; Node* newChild = nullptr; // Start the recursive insertion process insertInternal(root, key, upKey, newChild); // If the root node split, create a new root if (newChild) { Node* newRoot = new Node(); newRoot->key[0] = upKey; newRoot->child[0] = root; newRoot->child[1] = newChild; newRoot->nKeys = 1; newRoot->isLeaf = false; root = newRoot; } }
Example: Search and Insert Operation in 2-3 Tree
In the following example, we implement search and insert operation in 2-3 tree using C/C++ Program:
#include <iostream> using namespace std; // Node definition for 2-3 Tree struct Node { int nKeys; // Number of keys (1 or 2) int key[3]; // Temporary array to hold 3 keys during split Node * child[4]; // Up to 4 children during split bool isLeaf; Node() { nKeys = 0; isLeaf = true; for (int i = 0; i < 4; ++i) child[i] = nullptr; } }; // Inorder traversal (sorted output) void traverse(Node * node) { if (!node) return; for (int i = 0; i < node -> nKeys; ++i) { traverse(node -> child[i]); cout << node -> key[i] << " "; } traverse(node -> child[node -> nKeys]); } // Search function bool search(Node * node, int key) { if (!node) return false; int i = 0; while (i < node -> nKeys && key > node -> key[i]) ++i; if (i < node -> nKeys && key == node -> key[i]) return true; return search(node -> child[i], key); } // Internal insert function that handles recursion and splitting void insertInternal(Node * & node, int key, int & upKey, Node * & newChild) { int tempKeys[3], i; Node * tempChildren[4]; if (node -> isLeaf) { // Leaf case: insert into tempKeys for (i = 0; i < node -> nKeys; ++i) tempKeys[i] = node -> key[i]; i = node -> nKeys - 1; while (i >= 0 && key < tempKeys[i]) { tempKeys[i + 1] = tempKeys[i]; --i; } tempKeys[i + 1] = key; if (node -> nKeys < 2) { // No split needed for (int j = 0; j <= node -> nKeys; ++j) node -> key[j] = tempKeys[j]; node -> nKeys++; newChild = nullptr; } else { // Split leaf node node -> key[0] = tempKeys[0]; node -> nKeys = 1; newChild = new Node(); newChild -> key[0] = tempKeys[2]; newChild -> nKeys = 1; upKey = tempKeys[1]; } } else { // Internal node case i = node -> nKeys - 1; while (i >= 0 && key < node -> key[i]) --i; int pos = i + 1; int tempUpKey; Node * tempNewChild = nullptr; insertInternal(node -> child[pos], key, tempUpKey, tempNewChild); if (!tempNewChild) { newChild = nullptr; return; } // Copy keys and children to temp arrays for (i = 0; i < node -> nKeys; ++i) tempKeys[i] = node -> key[i]; for (i = 0; i <= node -> nKeys; ++i) tempChildren[i] = node -> child[i]; // Insert new key and child in order i = node -> nKeys - 1; while (i >= pos) { tempKeys[i + 1] = tempKeys[i]; tempChildren[i + 2] = tempChildren[i + 1]; --i; } tempKeys[pos] = tempUpKey; tempChildren[pos + 1] = tempNewChild; if (node -> nKeys < 2) { // No split needed for (int j = 0; j <= node -> nKeys + 1; ++j) node -> child[j] = tempChildren[j]; for (int j = 0; j <= node -> nKeys; ++j) node -> key[j] = tempKeys[j]; node -> nKeys++; newChild = nullptr; } else { // Split internal node node -> key[0] = tempKeys[0]; node -> child[0] = tempChildren[0]; node -> child[1] = tempChildren[1]; node -> nKeys = 1; newChild = new Node(); newChild -> isLeaf = false; newChild -> key[0] = tempKeys[2]; newChild -> child[0] = tempChildren[2]; newChild -> child[1] = tempChildren[3]; newChild -> nKeys = 1; upKey = tempKeys[1]; } } } // Public insert function void insert(Node * & root, int key) { int upKey; Node * newChild = nullptr; insertInternal(root, key, upKey, newChild); if (newChild) { // Create new root if root is split Node * newRoot = new Node(); newRoot -> key[0] = upKey; newRoot -> child[0] = root; newRoot -> child[1] = newChild; newRoot -> nKeys = 1; newRoot -> isLeaf = false; root = newRoot; } } int main() { Node * root = new Node(); int keys[] = {2, 7, 1, 3, 6, 9, 11}; for (int key: keys) insert(root, key); cout << "Inorder Traversal of 2-3 Tree:\n"; traverse(root); cout << "\n"; int target = 3; cout << "Search " << target << ": " << (search(root, target) ? "Found" : "Not Found") << "\n"; target = 5; cout << "Search " << target << ": " << (search(root, target) ? "Found" : "Not Found") << "\n"; return 0; }
Following is the output:
Inorder Traversal of 2-3 Tree: 1 2 3 6 7 9 11 Search 3: Found Search 5: Not Found
#include <stdio.h> #include <stdlib.h> #include <stdbool.h> // Definition of the Node structure typedef struct Node { int nKeys; int key[3]; // Temporarily hold 3 keys during split struct Node * child[4]; // Can have up to 4 children bool isLeaf; } Node; // Function to create a new node Node * createNode() { Node * newNode = (Node * ) malloc(sizeof(Node)); newNode -> nKeys = 0; newNode -> isLeaf = true; for (int i = 0; i < 4; ++i) newNode -> child[i] = NULL; return newNode; } // Inorder traversal void traverse(Node * node) { if (!node) return; for (int i = 0; i < node -> nKeys; ++i) { traverse(node -> child[i]); printf("%d ", node -> key[i]); } traverse(node -> child[node -> nKeys]); } // Search key in 2-3 Tree bool search(Node * node, int key) { if (!node) return false; int i = 0; while (i < node -> nKeys && key > node -> key[i]) ++i; if (i < node -> nKeys && key == node -> key[i]) return true; return search(node -> child[i], key); } // Internal insert function void insertInternal(Node * node, int key, int * upKey, Node ** newChild) { int tempKeys[3]; Node * tempChildren[4]; int i; if (node -> isLeaf) { // Insert key in sorted order into tempKeys for (i = 0; i < node -> nKeys; ++i) tempKeys[i] = node -> key[i]; i = node -> nKeys - 1; while (i >= 0 && key < tempKeys[i]) { tempKeys[i + 1] = tempKeys[i]; --i; } tempKeys[i + 1] = key; if (node -> nKeys < 2) { for (int j = 0; j <= node -> nKeys; ++j) node -> key[j] = tempKeys[j]; node -> nKeys++; * newChild = NULL; } else { // Split leaf node node -> key[0] = tempKeys[0]; node -> nKeys = 1; Node * right = createNode(); right -> key[0] = tempKeys[2]; right -> nKeys = 1; * upKey = tempKeys[1]; * newChild = right; } } else { // Internal node i = node -> nKeys - 1; while (i >= 0 && key < node -> key[i]) --i; int pos = i + 1; int tempUpKey; Node * tempNewChild = NULL; insertInternal(node -> child[pos], key, & tempUpKey, & tempNewChild); if (!tempNewChild) { * newChild = NULL; return; } for (i = 0; i < node -> nKeys; ++i) tempKeys[i] = node -> key[i]; for (i = 0; i <= node -> nKeys; ++i) tempChildren[i] = node -> child[i]; // Insert new key and child into temp arrays i = node -> nKeys - 1; while (i >= pos) { tempKeys[i + 1] = tempKeys[i]; tempChildren[i + 2] = tempChildren[i + 1]; --i; } tempKeys[pos] = tempUpKey; tempChildren[pos + 1] = tempNewChild; if (node -> nKeys < 2) { for (int j = 0; j <= node -> nKeys + 1; ++j) node -> child[j] = tempChildren[j]; for (int j = 0; j <= node -> nKeys; ++j) node -> key[j] = tempKeys[j]; node -> nKeys++; * newChild = NULL; } else { // Split internal node node -> key[0] = tempKeys[0]; node -> child[0] = tempChildren[0]; node -> child[1] = tempChildren[1]; node -> nKeys = 1; Node * right = createNode(); right -> isLeaf = false; right -> key[0] = tempKeys[2]; right -> child[0] = tempChildren[2]; right -> child[1] = tempChildren[3]; right -> nKeys = 1; * upKey = tempKeys[1]; * newChild = right; } } } // Public insert function void insert(Node ** root, int key) { int upKey; Node * newChild = NULL; insertInternal( * root, key, & upKey, & newChild); if (newChild) { Node * newRoot = createNode(); newRoot -> key[0] = upKey; newRoot -> child[0] = * root; newRoot -> child[1] = newChild; newRoot -> nKeys = 1; newRoot -> isLeaf = false; * root = newRoot; } } int main() { Node * root = createNode(); int keys[] = { 2, 7, 1, 3, 6, 9, 11 }; int n = sizeof(keys) / sizeof(keys[0]); for (int i = 0; i < n; ++i) insert( & root, keys[i]); printf("Inorder Traversal of 2-3 Tree:\n"); traverse(root); printf("\n"); int target = 3; printf("Search %d: %s\n", target, search(root, target) ? "Found" : "Not Found"); target = 5; printf("Search %d: %s\n", target, search(root, target) ? "Found" : "Not Found"); return 0; }
Following is the output:
1 2 3 6 7 9 11 Search 3: Found Search 5: Not Found
Conclusion
We learned that a 2-3 tree is a balanced search tree that ensures efficient search and insertion operations in O(logn) time. It maintains balance by splitting nodes and enabling keys during insertion. With the help of diagrams, we understand how insertion and searching operations can take place.