B + tree is a variation of the B-tree data structure. In a B + tree, data pointers are stored only at the leaf nodes of the tree. In a B+ tree structure of a leaf node differs from the structure of internal nodes.
When compared to the B tree, B+ Tree offers more effective insertion, deletion, and other operations.
Deleting in B+ Tree:
Three operations—Searching, Deleting, and Balancing—are involved in deleting an element from the B+ tree. As the last step, we will balance the tree after first looking for the node that has to be deleted and deleting it from the tree.
A key-value pair is deleted from a B+ tree using this method. In order to keep the attributes of the tree’s internal nodes after performing the deletion operation, the appropriate leaf node and its key-value pair must be removed.
Illustration:
Consider the following B+ Tree:
[ 11 14 20 ]
/ | \
[ 1 3 5 ] [ 11 12 ] [ 14 15 17 ]
/ | \ / \ / | \
[ 1 2 ] [ 3 4 ] [ 5 7 ] [ 11 ] [ 12 ] [ 14 15 ]Let’s delete key 5 from the tree.
The deletion strategy for the B+ tree is as follows:
- Look to locate the deleted key in the leaf nodes.
- Delete the key and its associated value if the key is discovered in a leaf node.
- One of the following steps should be taken if the node underflows (number of keys is less than half the maximum allowed):
- Get a key by borrowing it from a sibling node if it contains more keys than the required minimum.
- If the minimal number of keys is met by all of the sibling nodes, merge the underflow node with one of its siblings and modify the parent node as necessary.
- Remove all references to the deleted leaf node from the internal nodes of the tree.
- Remove the old root node and update the new one if the root node is empty.
The following describes how to remove Key 5 from the B+ tree:
- Look in the leaf nodes for the key number 5. The node [1 3 5] contains the key.
- Remove the value associated with the key 5, creating the node [1 3].
- The node [1 3] underflows because it contains fewer keys than the maximum number permitted. From its sibling node, we can obtain a key [3 4]. We borrow key 4 in this instance, resulting in the nodes [1 3 4] and [5 7].
- Remove all references to the deleted leaf node from the internal nodes of the tree. We must delete the reference to the node [1 3 5] from its parent node [1 3 4 11] because it was merged with the node [5 7]. The node [1 3 4 11] is the consequence of this.
- The deletion is finished since the root node [11 14 20] is not empty.
Below is the implementation of the above illustration:
Python3
# Python Implementation class Node: # Creating structure of tree def __init__( self , leaf = False ): self .keys = [] self .values = [] self .leaf = leaf self . next = None # B + Tree class BPlusTree: def __init__( self , degree): self .root = Node(leaf = True ) self .degree = degree # Search for key which # has to be deleted def search( self , key): curr = self .root while not curr.leaf: i = 0 while i < len (curr.keys): if key < curr.keys[i]: break i + = 1 curr = curr.values[i] i = 0 while i < len (curr.keys): if curr.keys[i] = = key: return True i + = 1 return False # Insert key value pairs def insert( self , key): curr = self .root if len (curr.keys) = = 2 * self .degree: new_root = Node() self .root = new_root new_root.values.append(curr) self .split(new_root, 0 , curr) self .insert_non_full(new_root, key) else : self .insert_non_full(curr, key) def insert_non_full( self , curr, key): i = 0 while i < len (curr.keys): if key < curr.keys[i]: break i + = 1 if curr.leaf: curr.keys.insert(i, key) else : if len (curr.values[i].keys) = = 2 * self .degree: self .split(curr, i, curr.values[i]) if key > curr.keys[i]: i + = 1 self .insert_non_full(curr.values[i], key) def split( self , parent, i, node): new_node = Node(leaf = node.leaf) parent.values.insert(i + 1 , new_node) parent.keys.insert(i, node.keys[ self .degree - 1 ]) new_node.keys = node.keys[ self .degree:] node.keys = node.keys[: self .degree - 1 ] if not new_node.leaf: new_node.values = node.values[ self .degree:] node.values = node.values[: self .degree] def steal_from_left( self , parent, i): node = parent.values[i] left_sibling = parent.values[i - 1 ] node.keys.insert( 0 , parent.keys[i - 1 ]) parent.keys[i - 1 ] = left_sibling.keys.pop( - 1 ) if not node.leaf: node.values.insert( 0 , left_sibling.values.pop( - 1 )) def steal_from_right( self , parent, i): node = parent.values[i] right_sibling = parent.values[i + 1 ] node.keys.append(parent.keys[i]) parent.keys[i] = right_sibling.keys.pop( 0 ) if not node.leaf: node.values.append(right_sibling.values.pop( 0 )) # Del the given key def delete( self , key): curr = self .root found = False i = 0 while i < len (curr.keys): if key = = curr.keys[i]: found = True break elif key < curr.keys[i]: break i + = 1 if found: if curr.leaf: curr.keys.pop(i) else : pred = curr.values[i] if len (pred.keys) > = self .degree: pred_key = self .get_max_key(pred) curr.keys[i] = pred_key self .delete_from_leaf(pred_key, pred) else : succ = curr.values[i + 1 ] if len (succ.keys) > = self .degree: succ_key = self .get_min_key(succ) curr.keys[i] = succ_key self .delete_from_leaf(succ_key, succ) else : self .merge(curr, i, pred, succ) self .delete_from_leaf(key, pred) if curr = = self .root and not curr.keys: self .root = curr.values[ 0 ] else : if curr.leaf: return False else : if len (curr.values[i].keys) < self .degree: if i ! = 0 and len (curr.values[i - 1 ].keys) > = self .degree: self .steal_from_left(curr, i) elif i ! = len (curr.keys) and len (curr.values[i + 1 ].keys) > = self .degree: self .steal_from_right(curr, i) else : if i = = len (curr.keys): i - = 1 self .merge(curr, i, curr.values[i], curr.values[i + 1 ]) self .delete(key) def delete_from_leaf( self , key, leaf): leaf.keys.remove(key) if leaf = = self .root or len (leaf.keys) > = self .degree / / 2 : return parent = self .find_parent(leaf) i = parent.values.index(leaf) if i > 0 and len (parent.values[i - 1 ].keys) > self .degree / / 2 : self .rotate_right(parent, i) elif i < len (parent.keys) and len (parent.values[i + 1 ].keys) > self .degree / / 2 : self .rotate_left(parent, i) else : if i = = len (parent.keys): i - = 1 self .merge(parent, i, parent.values[i], parent.values[i + 1 ]) def get_min_key( self , node): while not node.leaf: node = node.values[ 0 ] return node.keys[ 0 ] def get_max_key( self , node): while not node.leaf: node = node.values[ - 1 ] return node.keys[ - 1 ] def get_min_key( self , node): while not node.leaf: node = node.values[ 0 ] return node.keys[ 0 ] def merge( self , parent, i, pred, succ): pred.keys + = succ.keys pred.values + = succ.values parent.values.pop(i + 1 ) parent.keys.pop(i) if parent = = self .root and not parent.keys: self .root = pred def fix( self , parent, i): node = parent.values[i] if i > 0 and len (parent.values[i - 1 ].keys) > = self .degree: self .rotate_right(parent, i) elif i < len (parent.keys) and len (parent.values[i + 1 ].keys) > = self .degree: self .rotate_left(parent, i) else : if i = = len (parent.keys): i - = 1 self .merge(parent, i, node, parent.values[i + 1 ]) # Balance the tree after deletion def rotate_right( self , parent, i): node = parent.values[i] prev = parent.values[i - 1 ] node.keys.insert( 0 , parent.keys[i - 1 ]) parent.keys[i - 1 ] = prev.keys.pop( - 1 ) if not node.leaf: node.values.insert( 0 , prev.values.pop( - 1 )) def rotate_left( self , parent, i): node = parent.values[i] next = parent.values[i + 1 ] node.keys.append(parent.keys[i]) parent.keys[i] = next .keys.pop( 0 ) if not node.leaf: node.values.append( next .values.pop( 0 )) # Function to print Tree def print_tree( self ): curr_level = [ self .root] while curr_level: next_level = [] for node in curr_level: print ( str (node.keys), end = ' ' ) if not node.leaf: next_level + = node.values print () curr_level = next_level # create a B + tree with degree 3 tree = BPlusTree( 3 ) # insert some keys tree.insert( 1 ) tree.insert( 2 ) tree.insert( 3 ) tree.insert( 4 ) tree.insert( 5 ) tree.insert( 6 ) tree.insert( 7 ) tree.insert( 8 ) tree.insert( 9 ) # print the tree tree.print_tree() # [4] [2, 3] [6, 7, 8, 9] [1] [5] # delete a key tree.delete( 3 ) # print the tree tree.print_tree() # [4] [2] [6, 7, 8, 9] [1] [5] |
[3] [1, 2] [4, 5, 6, 7, 8, 9] [4] [1, 2] [5, 6, 7, 8, 9]
Time Complexity: O(log N)
Auxiliary Space: O(N)
Ready to dive in? Explore our Free Demo Content and join our DSA course, trusted by over 100,000 neveropen!