B-Trees were introduced by Rudolf Bayer and Edward M. McCreight at Boeing Research Labs in 1970 with the name Height Balanced M-way Search Tree. Later it was named as B-Tree. A B-tree is a self-balancing tree where all the leaf nodes are at the same level which allows for efficient searching, insertion and deletion of records. Because of all the leaf nodes being on the same level, the access time of data is fixed regardless of the size of the data set.
In binary search tree, AVL Tree, Red-Black tree, etc., every node contains only one value(key) and a maximum of two children. B-Tree contains more than one value and more than two children. The number of values(keys) in a node and number of childrens for a node is called as order of B-Tree.
function search(node, key):
i = 1
while i <= node.n and key > node.keys[i]:
i = i + 1
if i <= node.n and key == node.keys[i]:
return (node, i) // Key found
else if node.leaf:
return null // Key not found and reached leaf
else:
return search(node.children[i], key) // Go to the appropriate child
Here's a step-by-step explanation of the insertion process in a B-tree:
function insert(tree, key):
root = tree.root
if root.n == m-1:
newNode = createNode()
tree.root = newNode
newNode.leaf = false
newNode.n = 0
newNode.children[1] = root
splitChild(newNode, 1)
insertNonFull(newNode, key)
else:
insertNonFull(root, key)
function insertNonFull(node, key):
i = node.n
if node.leaf:
while i >= 1 and key < node.keys[i]:
node.keys[i + 1] = node.keys[i]
i = i - 1
node.keys[i + 1] = key
node.n = node.n + 1
else:
while i >= 1 and key < node.keys[i]:
i = i - 1
i = i + 1
if node.children[i].n == m-1:
splitChild(node, i)
if key > node.keys[i]:
i = i + 1
insertNonFull(node.children[i], key)
function splitChild(node, i):
newNode = createNode()
newNode.leaf = node.children[i].leaf
newNode.n = t-1
for j = 1 to t-1:
newNode.keys[j] = node.children[i].keys[j+t]
if not node.children[i].leaf:
for j = 1 to t:
newNode.children[j] = node.children[i].children[j+t]
node.children[i].n = t-1
for j = node.n + 1 downto i+1:
node.children[j+1] = node.children[j]
node.children[i+1] = newNode
for j = node.n downto i:
node.keys[j+1] = node.keys[j]
node.keys[i] = node.children[i].keys[t]
node.n = node.n + 1
When deleting, you have to make sure that the B tree properties are preserved. We consider 3 cases in deletion of B trees.
Case 1: Key is in a Leaf Node(with Sufficient Keys)
Case 2: Key is in a Leaf Node(with Minimum Keys would violate the B-tree property)
Case 3: Key is in an Internal Node
Note: If the Root is Left with No Keys, delete the old root and make its only child the new root of the tree. This decreases the height of the tree.
function delete(tree, key):
if tree.root is None:
print("Tree is empty.")
return
// Call the delete procedure for the root
deleteNode(tree.root, key)
// If the root node has 0 keys, make its first child the new root
// if it has a child, otherwise set the root to None.
if tree.root.n == 0:
if tree.root.leaf:
tree.root = None
else:
tree.root = tree.root.children[0]
function deleteNode(node, key):
i = findKey(node, key)
// Case 1: The key to be removed is present in this node
if i <= node.n and node.keys[i] == key:
if node.leaf:
// The node is a leaf node: Remove the key directly
removeFromLeaf(node, i)
else:
// The node is an internal node
removeFromNonLeaf(node, i)
else:
if node.leaf:
// The key is not present in tree
print("The key", key, "is does not exist in the tree")
return
// The key might be present in the subtree rooted at this node
// The flag indicates whether the key is present in the subtree
// rooted at the last child of this node
flag = (i == node.n)
// If the child where the key is supposed to exist has less than t keys,
// we fill that child
if node.children[i].n < t:
fill(node, i)
// If the last child has been merged, it must have merged with the previous
// child and so we recurse on the (i-1)th child. Else, we recurse on the
// (i)th child which now has atleast t keys
if flag and i > node.n:
deleteNode(node.children[i-1], key)
else:
deleteNode(node.children[i], key)
// A utility function to remove the ith key from this node - which is a leaf node
function removeFromLeaf(node, i):
// Move all the keys after the ith position one place backward
for j in range(i+1, node.n):
node.keys[j-1] = node.keys[j]
// Reduce the count of keys
node.n -= 1
// A utility function to remove the ith key from this node - which is a non-leaf node
function removeFromNonLeaf(node, i):
key = node.keys[i]
// If the child that precedes key in this node has atleast t keys,
// find the predecessor 'pred' of key in the subtree rooted at
// child[i]. Replace key by pred. Recursively delete pred
// in children[i]
if node.children[i].n >= t:
pred = getPred(node, i)
node.keys[i] = pred
deleteNode(node.children[i], pred)
// If the child[i] has less that t keys, examine child[i+1].
// If child[i+1] has atleast t keys, find the successor 'succ' of key in
// the subtree rooted at child[i+1]
// Replace key by succ
// Recursively delete succ in child[i+1]
else if node.children[i+1].n >= t:
succ = getSucc(node, i)
node.keys[i] = succ
deleteNode(node.children[i+1], succ)
// If both child[i] and child[i+1] has less than t keys,merge key and all of child[i+1]
// into child[i]
// Now child[i] contains 2t-1 keys
// Free child[i+1] and recursively delete key from child[i]
else:
merge(node, i)
deleteNode(node.children[i], key)
// A function to get predecessor of keys[i]
function getPred(node, i):
// Keep moving to the right most node until we reach a leaf
current = node.children[i]
while not current.leaf:
current = current.children[current.n]
// Return the last key of the leaf
return current.keys[current.n-1]
// A function to get successor of keys[i]
function getSucc(node, i):
// Keep moving the left most node starting from child[i+1] until we reach a leaf
current = node.children