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
C++ Program to Implement B+ Tree
A B+ tree is an m-tree that consists of a root, internal nodes, and leaves. The root may be a leaf or a node with two or more children. A B+ tree is an advanced data structure that extends the B-tree by adding a linked list of leaf nodes.
A B+ tree can be a B-tree where each node contains only keys (not key-value pairs).
What is B+ Tree?
A B+ tree is a self-balancing tree data structure that maintains sorted data and allows for efficient insertion, deletion, and search operations. It differs from a B-tree in the following ways:
- All values are at the leaf level.
- Leaf nodes are linked, making range queries and sequential access more efficient.
Following is the diagram of B+ tree:
Properties of B+ Tree
A B+ tree of order m has the following properties:
- All leaf node at the same level.
- All internal nodes except the root have at least [m/2] Children. But the root has at least two children it is not a leaf node.
- All internal nodes have at most m children.
- Each internal node with k children contains k-1 keys.
- All leaf nodes contain between [m/2] and m keys.
- The keys in leaf nodes are sorted, and each leaf node has a pointer to the next leaf node.
Implement B+ Tree in C++
A B+ tree contains internal nodes and leaf nodes. The internal node contains a key and a pointer to the child node, while the leaf node stores keys and pointers to the actual data values.
To construct a B+ tree in C++, we will use a class that contains the required definition: A struct to represent each node and a member function to provide basic functionality.
template <typename T>
class B_plus_tree {
public:
struct Node {
bool isLeaf;
vector<T> keys;
vector<Node*> children;
Node* next;
};
Operations of B+ Tree
The following are some of the basic operations of the B+ tree that are needed to manipulate its elements.
| Operation | Description | Time Complexity |
|---|---|---|
| Insert | Insert a new element into the B+ tree. | O(logn) |
| Delete | Remove an element from the B+ tree. | O(logn) |
| Search | Searches for the element in the B+ tree. | O(logn) |
| Range Query | Retrieve all elements within the given range from the B+ Tree. | O(logn+k) |
| Split Child | It split a full child node during insertion. | O(1) |
Example
In the following example, we demonstrate the implementation of a B+ tree in C++ -
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
template < typename T > class B_plus_tree {
public:
// structure to create a node
struct Node {
bool isLeaf;
vector < T > keys;
vector < Node * > children;
Node * next;
Node(bool leaf = false): isLeaf(leaf), next(nullptr) {}
};
Node * root;
int t;
// Function for split
void splitChild(Node * parent, int index, Node * child);
// Function for insert
void insertNonFull(Node * node, T key);
// Function for remove
void remove(Node * node, T key);
// Function for borrow
void borrowFromPrev(Node * node, int index);
// Function for borrow a key from the next sibling
void borrowFromNext(Node * node, int index);
// Function for merge two nodes
void merge(Node * node, int index);
void printTree(Node * node, int level);
public: B_plus_tree(int degree): root(nullptr),
t(degree) {}
void insert(T key);
bool search(T key);
void remove(T key);
vector < T > rangeQuery(T lower, T upper);
void printTree();
};
// Implementation of splitChild function
template < typename T >
void B_plus_tree < T > ::splitChild(Node * parent, int index,
Node * child) {
Node * newChild = new Node(child -> isLeaf);
parent -> children.insert(
parent -> children.begin() + index + 1, newChild);
parent -> keys.insert(parent -> keys.begin() + index,
child -> keys[t - 1]);
newChild -> keys.assign(child -> keys.begin() + t,
child -> keys.end());
child -> keys.resize(t - 1);
if (!child -> isLeaf) {
newChild -> children.assign(child -> children.begin() +
t,
child -> children.end());
child -> children.resize(t);
}
if (child -> isLeaf) {
newChild -> next = child -> next;
child -> next = newChild;
}
}
// Implementation of insertNonFull function
template < typename T >
void B_plus_tree < T > ::insertNonFull(Node * node, T key) {
if (node -> isLeaf) {
node -> keys.insert(upper_bound(node -> keys.begin(),
node -> keys.end(),
key),
key);
} else {
int i = node -> keys.size() - 1;
while (i >= 0 && key < node -> keys[i]) {
i--;
}
i++;
if (node -> children[i] -> keys.size() == 2 * t - 1) {
splitChild(node, i, node -> children[i]);
if (key > node -> keys[i]) {
i++;
}
}
insertNonFull(node -> children[i], key);
}
}
// Implementation of remove function
template < typename T >
void B_plus_tree < T > ::remove(Node * node, T key) {
// If node is a leaf
if (node -> isLeaf) {
auto it = find(node -> keys.begin(), node -> keys.end(),
key);
if (it != node -> keys.end()) {
node -> keys.erase(it);
}
} else {
int idx = lower_bound(node -> keys.begin(),
node -> keys.end(), key) -
node -> keys.begin();
if (idx < node -> keys.size() &&
node -> keys[idx] == key) {
if (node -> children[idx] -> keys.size() >= t) {
Node * predNode = node -> children[idx];
while (!predNode -> isLeaf) {
predNode = predNode -> children.back();
}
T pred = predNode -> keys.back();
node -> keys[idx] = pred;
remove(node -> children[idx], pred);
} else if (node -> children[idx + 1] -> keys.size() >=
t) {
Node * succNode = node -> children[idx + 1];
while (!succNode -> isLeaf) {
succNode = succNode -> children.front();
}
T succ = succNode -> keys.front();
node -> keys[idx] = succ;
remove(node -> children[idx + 1], succ);
} else {
merge(node, idx);
remove(node -> children[idx], key);
}
} else {
if (node -> children[idx] -> keys.size() < t) {
if (idx > 0 &&
node -> children[idx - 1] -> keys.size() >= t) {
borrowFromPrev(node, idx);
} else if (idx < node -> children.size() - 1 &&
node -> children[idx + 1] -> keys.size() >= t) {
borrowFromNext(node, idx);
} else {
if (idx < node -> children.size() - 1) {
merge(node, idx);
} else {
merge(node, idx - 1);
}
}
}
remove(node -> children[idx], key);
}
}
}
// Implementation of borrowFromPrev function
template < typename T >
void B_plus_tree < T > ::borrowFromPrev(Node * node, int index) {
Node * child = node -> children[index];
Node * sibling = node -> children[index - 1];
child -> keys.insert(child -> keys.begin(),
node -> keys[index - 1]);
node -> keys[index - 1] = sibling -> keys.back();
sibling -> keys.pop_back();
if (!child -> isLeaf) {
child -> children.insert(child -> children.begin(),
sibling -> children.back());
sibling -> children.pop_back();
}
}
// Implementation of borrowFromNext function
template < typename T >
void B_plus_tree < T > ::borrowFromNext(Node * node, int index) {
Node * child = node -> children[index];
Node * sibling = node -> children[index + 1];
child -> keys.push_back(node -> keys[index]);
node -> keys[index] = sibling -> keys.front();
sibling -> keys.erase(sibling -> keys.begin());
if (!child -> isLeaf) {
child -> children.push_back(
sibling -> children.front());
sibling -> children.erase(sibling -> children.begin());
}
}
// Implementation of merge function
template < typename T >
void B_plus_tree < T > ::merge(Node * node, int index) {
Node * child = node -> children[index];
Node * sibling = node -> children[index + 1];
child -> keys.push_back(node -> keys[index]);
child -> keys.insert(child -> keys.end(),
sibling -> keys.begin(),
sibling -> keys.end());
if (!child -> isLeaf) {
child -> children.insert(child -> children.end(),
sibling -> children.begin(),
sibling -> children.end());
}
node -> keys.erase(node -> keys.begin() + index);
node -> children.erase(node -> children.begin() + index +
1);
delete sibling;
}
// Implementation of printTree function
template < typename T >
void B_plus_tree < T > ::printTree(Node * node, int level) {
if (node != nullptr) {
for (int i = 0; i < level; ++i) {
cout << " ";
}
for (const T & key: node -> keys) {
cout << key << " ";
}
cout << endl;
for (Node * child: node -> children) {
printTree(child, level + 1);
}
}
}
// Implementation of printTree wrapper function
template < typename T > void B_plus_tree < T > ::printTree() {
printTree(root, 0);
}
// Implementation of search function
template < typename T > bool B_plus_tree < T > ::search(T key) {
Node * current = root;
while (current != nullptr) {
int i = 0;
while (i < current -> keys.size() &&
key > current -> keys[i]) {
i++;
}
if (i < current -> keys.size() &&
key == current -> keys[i]) {
return true;
}
if (current -> isLeaf) {
return false;
}
current = current -> children[i];
}
return false;
}
// Implementation of range query function
template < typename T >
vector < T > B_plus_tree < T > ::rangeQuery(T lower, T upper) {
vector < T > result;
Node * current = root;
while (!current -> isLeaf) {
int i = 0;
while (i < current -> keys.size() &&
lower > current -> keys[i]) {
i++;
}
current = current -> children[i];
}
while (current != nullptr) {
for (const T & key: current -> keys) {
if (key >= lower && key <= upper) {
result.push_back(key);
}
if (key > upper) {
return result;
}
}
current = current -> next;
}
return result;
}
// Implementation of insert function
template < typename T > void B_plus_tree < T > ::insert(T key) {
if (root == nullptr) {
root = new Node(true);
root -> keys.push_back(key);
} else {
if (root -> keys.size() == 2 * t - 1) {
Node * newRoot = new Node();
newRoot -> children.push_back(root);
splitChild(newRoot, 0, root);
root = newRoot;
}
insertNonFull(root, key);
}
}
// Implementation of remove function
template < typename T > void B_plus_tree < T > ::remove(T key) {
if (root == nullptr) {
return;
}
remove(root, key);
if (root -> keys.empty() && !root -> isLeaf) {
Node * tmp = root;
root = root -> children[0];
delete tmp;
}
}
// Main function to test the B+ Tree implementation
int main() {
B_plus_tree < int > tree(3);
// Insert elements
tree.insert(1);
tree.insert(2);
tree.insert(5);
tree.insert(7);
tree.insert(9);
tree.insert(11);
cout << "B+ Tree after insertions:" << endl;
tree.printTree();
// Search for a key
int searchKey = 9;
cout << "\nSearching for key " << searchKey << ": " <<
(tree.search(searchKey) ? "Found" : "Not Found") <<
endl;
// Perform a range query
int lower = 1, upper = 11;
vector < int > rangeResult = tree.rangeQuery(lower, upper);
cout << "\nRange query [" << lower << ", " << upper <<
"]: ";
for (int key: rangeResult) {
cout << key << " ";
}
cout << endl;
// Remove a key
int removeKey = 7;
tree.remove(removeKey);
cout << "\nB+ Tree after removing " << removeKey << ":" <<
endl;
tree.printTree();
return 0;
}
Following is the B+ tree -
B+ Tree after insertions: 5 1 2 7 9 11 Searching for key 9: Found Range query [1, 11]: 1 2 7 9 11 B+ Tree after removing 7: 5 1 2 9 11