A B-tree is a self-balancing tree data structure that maintains sorted data and allows searches, sequential access, insertions, and deletions in logarithmic time.
B Tree in Python
Characteristics of B-Trees:
- Multiple Keys: Unlike traditional binary search trees, each node in a B-Tree can contain multiple keys, which allows the tree to have a larger branching factor and thus a shallower height.
- Balanced Tree: B-Trees maintain balance by ensuring that each node has a minimum number of keys, so the tree is always balanced.
- Efficiency: This balance guarantees that the time complexity for operations such as insertion, deletion, and searching is always O(log n), regardless of the initial shape of the tree.
- Properties: All leaves are at the same level. The number of children of a node is equal to the number of keys in it plus. All keys of a node are sorted in increasing order.
- Applications: B-Trees are particularly well suited for storage systems that have slow, bulky data access such as hard drives, flash memory, and CD-ROMs.
Traversal of B-Tree in Python:
Let us see how we can define a class for the B tree in Python. Let us see the individual parts of code used for the implementation of the B tree.
Step-by-step algorithm:
- Create a node with the name 'BTreeNode' to create a default node with a list of keys and a list of children nodes.
- The b-tree class will be created which has two attributes 'root' and 't', root represents the root node, and 't' represents the minimum degree of the B-tree.
- The display function in class 'Btree' will be used to print to nodes of the tree in level-wise format.
- The final tree is created in the main function by inserting keys in the B-tree.
Below is the implementation of the above idea:
Python3
class BTreeNode:
def __init__(self, leaf=True):
self.leaf = leaf
self.keys = []
self.children = []
def display(self, level=0):
print(f"Level {level}: {self.keys}")
if not self.leaf:
for child in self.children:
child.display(level + 1)
class BTree:
def __init__(self, t):
self.root = BTreeNode(True)
self.t = t
def display(self):
self.root.display()
def insert(self, k):
root = self.root
if len(root.keys) == (2 * self.t) - 1:
temp = BTreeNode()
self.root = temp
temp.children.append(root)
self.split_child(temp, 0)
self.insert_non_full(temp, k)
else:
self.insert_non_full(root, k)
def insert_non_full(self, x, k):
i = len(x.keys) - 1
if x.leaf:
x.keys.append(None) # Make space for the new key
while i >= 0 and k < x.keys[i]:
x.keys[i + 1] = x.keys[i]
i -= 1
x.keys[i + 1] = k
else:
while i >= 0 and k < x.keys[i]:
i -= 1
i += 1
if len(x.children[i].keys) == (2 * self.t) - 1:
self.split_child(x, i)
if k > x.keys[i]:
i += 1
self.insert_non_full(x.children[i], k)
def split_child(self, x, i):
t = self.t
y = x.children[i]
z = BTreeNode(leaf=y.leaf)
x.keys.insert(i, y.keys[t - 1])
z.keys = y.keys[t: (2 * t) - 1]
y.keys = y.keys[0: t - 1]
if not y.leaf:
z.children = y.children[t: 2 * t]
y.children = y.children[0: t - 1]
x.children.insert(i + 1, z)
def main():
B = BTree(3)
keys = [10, 20, 5, 6, 12, 30, 7, 17]
for key in keys:
B.insert(key)
print("B-tree structure:")
B.display()
if __name__ == '__main__':
main()
OutputB-tree structure:
Level 0: [7, 10, 17, 30]
Search operation in B Tree in Python:
B tree makes it convenient for users to search for an element in it like searching for an element in any other binary tree. Let us see how it searches for an element 'm' in the tree.
- The search begins from the root node and compares them with the key values of the root node. If the value of the key is similar to m then it simply returns the index corresponding to that node where the key matched.
- If the key is not found, it goes down. It compares the value of the current key of the node to m. If the current element is smaller than m, then it searches for the right child of the node again and again.
- These steps are repeated until it finds the element or the leaf node is reached.
Step-by-step algorithm:
- m is not found in the root so we will compare it with the key. i.e. compare 10 and 100.
- Since 10<100, we will search in the left part of the tree.
- Now we will compare m with all the elements in the current node i.e. we will compare m with 35 and 65 in order.
- Since m is smaller than both 35 and 65, we will go to the left side of the tree to find the element.
- Now we will compare m with all the keys of the current node i.e. 10 and 20 but the first key of this node is equal to m so we found the element.
Below is the implementation of the approach:
Python3
# Searching a key on a B-tree in Python
# Create a node
class BTreeNode:
def __init__(self, leaf=False):
self.leaf = leaf
self.keys = []
self.child = []
# Tree
class BTree:
def __init__(self, t):
self.root = BTreeNode(True)
self.t = t
# Insert node
def insert(self, k):
root = self.root
if len(root.keys) == (2 * self.t) - 1:
temp = BTreeNode()
self.root = temp
temp.child.insert(0, root)
self.split_child(temp, 0)
self.insert_non_full(temp, k)
else:
self.insert_non_full(root, k)
# Insert nonfull
def insert_non_full(self, x, k):
i = len(x.keys) - 1
if x.leaf:
x.keys.append((None, None))
while i >= 0 and k[0] < x.keys[i][0]:
x.keys[i + 1] = x.keys[i]
i -= 1
x.keys[i + 1] = k
else:
while i >= 0 and k[0] < x.keys[i][0]:
i -= 1
i += 1
if len(x.child[i].keys) == (2 * self.t) - 1:
self.split_child(x, i)
if k[0] > x.keys[i][0]:
i += 1
self.insert_non_full(x.child[i], k)
# Split the child
def split_child(self, x, i):
t = self.t
y = x.child[i]
z = BTreeNode(y.leaf)
x.child.insert(i + 1, z)
x.keys.insert(i, y.keys[t - 1])
z.keys = y.keys[t: (2 * t) - 1]
y.keys = y.keys[0: t - 1]
if not y.leaf:
z.child = y.child[t: 2 * t]
y.child = y.child[0: t - 1]
# Print the tree
def print_tree(self, x, l=0):
print("Level ", l, " ", len(x.keys), end=":")
for i in x.keys:
print(i, end=" ")
print()
l += 1
if len(x.child) > 0:
for i in x.child:
self.print_tree(i, l)
# Search key in the tree
def search_key(self, k, x=None):
if x is not None:
i = 0
while i < len(x.keys) and k > x.keys[i][0]:
i += 1
if i < len(x.keys) and k == x.keys[i][0]:
return (x, i)
elif x.leaf:
return None
else:
return self.search_key(k, x.child[i])
else:
return self.search_key(k, self.root)
def main():
B = BTree(3)
for i in range(10):
B.insert((i, 2 * i))
B.print_tree(B.root)
if B.search_key(8) is not None:
print("\nFound")
else:
print("\nNot Found")
if __name__ == '__main__':
main()
OutputLevel 0 2:(2, 4) (5, 10)
Level 1 2:(0, 0) (1, 2)
Level 1 2:(3, 6) (4, 8)
Level 1 4:(6, 12) (7, 14) (8, 16) (9, 18)
Found
Time Complexity : O(log n)
Auxiliary Space: O(n)
Insert operation in B Tree in Python:
Inserting an element refers to adding the element at a certain position in the tree. Let us see how the B tree allows the insertion of an element.
Step-by-step algorithm:
- For an empty tree, the root node is declared and the element m is directly inserted.
- Use the search operation explained above to find the location where the new element is to be inserted.
- Case 1: If the node has already fulfilled its capacity of several keys
- then the node needs to be split. For this, firstly insert the key normally and then split the keys from the median.
- This middle key will move with the parent to separate the left and right nodes.
- Case 2: If the node has not fulfilled its capacity of several keys
- Simply insert the key at the required position.
- After the element is inserted, ensure the number of keys is updated.
Below is the implementation of the approach:
Python3
# Inserting a key on a B-tree in Python
# Create a node
class BTreeNode:
def __init__(self, leaf=False):
self.leaf = leaf
self.keys = []
self.child = []
# Tree
class BTree:
def __init__(self, t):
self.root = BTreeNode(True)
self.t = t
# Insert node
def insert(self, k):
root = self.root
if len(root.keys) == (2 * self.t) - 1:
temp = BTreeNode()
self.root = temp
temp.child.insert(0, root)
self.split_child(temp, 0)
self.insert_non_full(temp, k)
else:
self.insert_non_full(root, k)
# Insert nonfull
def insert_non_full(self, x, k):
i = len(x.keys) - 1
if x.leaf:
x.keys.append((None, None))
while i >= 0 and k[0] < x.keys[i][0]:
x.keys[i + 1] = x.keys[i]
i -= 1
x.keys[i + 1] = k
else:
while i >= 0 and k[0] < x.keys[i][0]:
i -= 1
i += 1
if len(x.child[i].keys) == (2 * self.t) - 1:
self.split_child(x, i)
if k[0] > x.keys[i][0]:
i += 1
self.insert_non_full(x.child[i], k)
# Split the child
def split_child(self, x, i):
t = self.t
y = x.child[i]
z = BTreeNode(y.leaf)
x.child.insert(i + 1, z)
x.keys.insert(i, y.keys[t - 1])
z.keys = y.keys[t: (2 * t) - 1]
y.keys = y.keys[0: t - 1]
if not y.leaf:
z.child = y.child[t: 2 * t]
y.child = y.child[0: t - 1]
# Print the tree
def print_tree(self, x, l=0):
print("Level ", l, " ", len(x.keys), end=":")
for i in x.keys:
print(i, end=" ")
print()
l += 1
if len(x.child) > 0:
for i in x.child:
self.print_tree(i, l)
def main():
B = BTree(3)
for i in range(10):
B.insert((i, 2 * i))
B.print_tree(B.root)
if __name__ == '__main__':
main()
OutputLevel 0 2:(2, 4) (5, 10)
Level 1 2:(0, 0) (1, 2)
Level 1 2:(3, 6) (4, 8)
Level 1 4:(6, 12) (7, 14) (8, 16) (9, 18)
Time Complexity : O(log n)
Auxiliary Space: O(n), where n is order of the tree
Delete operation in B Tree in Python:
Deleting elements refers to removing the element from a certain position in the tree. Let us see how the B tree allows the deletion of an element. We need to study 3 different cases for deleting an element.
Case 1: Deletion of a leaf node
- We search for the particular element to be deleted. If it is a leaf node then we need to check that it doesn't break the rule of a minimum number of keys of a node.
- If it does reduce the number of keys than the minimum number of keys, then we will take a key from the neighboring sibling from left to right checking for the key that has more than the minimum number of required keys.
- If it doesn't violate the number of minimum keys then we can simply join the parent node to the left or right sibling to delete that key.
Case 2: Deletion of an internal node
- We search for the particular element to be deleted. This node will be deleted and be replaced by its left child which comes just before it in the inorder predecessor. Note that it is only possible if the left child has more than the minimum number of keys.
- In case the left node doesn't have more than the minimum number of keys. then the deleted node is replaced by its right child which comes just after it in the inorder successor. Note that it is only possible if the left child has more than the minimum number of keys.
- If both children have a case where they only have the minimum number of keys, they are merged to replace that deleted node.
Case 3: If the height of the tree reduces
- If we witness case 2 repeatedly, then we will have to merge the left and right nodes several times which can shrink the tree.
- In this case, children are re-arranged in increasing order to maintain the height of the tree.
Below is the implementation of the approach:
Python3
# Deleting a key on a B-tree in Python
# Btree node
class BTreeNode:
def __init__(self, leaf=False):
self.leaf = leaf
self.keys = []
self.child = []
class BTree:
def __init__(self, t):
self.root = BTreeNode(True)
self.t = t
# Insert a key
def insert(self, k):
root = self.root
if len(root.keys) == (2 * self.t) - 1:
temp = BTreeNode()
self.root = temp
temp.child.insert(0, root)
self.split_child(temp, 0)
self.insert_non_full(temp, k)
else:
self.insert_non_full(root, k)
# Insert non full
def insert_non_full(self, x, k):
i = len(x.keys) - 1
if x.leaf:
x.keys.append((None, None))
while i >= 0 and k[0] < x.keys[i][0]:
x.keys[i + 1] = x.keys[i]
i -= 1
x.keys[i + 1] = k
else:
while i >= 0 and k[0] < x.keys[i][0]:
i -= 1
i += 1
if len(x.child[i].keys) == (2 * self.t) - 1:
self.split_child(x, i)
if k[0] > x.keys[i][0]:
i += 1
self.insert_non_full(x.child[i], k)
# Split the child
def split_child(self, x, i):
t = self.t
y = x.child[i]
z = BTreeNode(y.leaf)
x.child.insert(i + 1, z)
x.keys.insert(i, y.keys[t - 1])
z.keys = y.keys[t: (2 * t) - 1]
y.keys = y.keys[0: t - 1]
if not y.leaf:
z.child = y.child[t: 2 * t]
y.child = y.child[0: t - 1]
# Delete a node
def delete(self, x, k):
t = self.t
i = 0
while i < len(x.keys) and k[0] > x.keys[i][0]:
i += 1
if x.leaf:
if i < len(x.keys) and x.keys[i][0] == k[0]:
x.keys.pop(i)
return
return
if i < len(x.keys) and x.keys[i][0] == k[0]:
return self.delete_internal_node(x, k, i)
elif len(x.child[i].keys) >= t:
self.delete(x.child[i], k)
else:
if i != 0 and i + 2 < len(x.child):
if len(x.child[i - 1].keys) >= t:
self.delete_sibling(x, i, i - 1)
elif len(x.child[i + 1].keys) >= t:
self.delete_sibling(x, i, i + 1)
else:
self.delete_merge(x, i, i + 1)
elif i == 0:
if len(x.child[i + 1].keys) >= t:
self.delete_sibling(x, i, i + 1)
else:
self.delete_merge(x, i, i + 1)
elif i + 1 == len(x.child):
if len(x.child[i - 1].keys) >= t:
self.delete_sibling(x, i, i - 1)
else:
self.delete_merge(x, i, i - 1)
self.delete(x.child[i], k)
# Delete internal node
def delete_internal_node(self, x, k, i):
t = self.t
if x.leaf:
if x.keys[i][0] == k[0]:
x.keys.pop(i)
return
return
if len(x.child[i].keys) >= t:
x.keys[i] = self.delete_predecessor(x.child[i])
return
elif len(x.child[i + 1].keys) >= t:
x.keys[i] = self.delete_successor(x.child[i + 1])
return
else:
self.delete_merge(x, i, i + 1)
self.delete_internal_node(x.child[i], k, self.t - 1)
# Delete the predecessor
def delete_predecessor(self, x):
if x.leaf:
return x.pop()
n = len(x.keys) - 1
if len(x.child[n].keys) >= self.t:
self.delete_sibling(x, n + 1, n)
else:
self.delete_merge(x, n, n + 1)
self.delete_predecessor(x.child[n])
# Delete the successor
def delete_successor(self, x):
if x.leaf:
return x.keys.pop(0)
if len(x.child[1].keys) >= self.t:
self.delete_sibling(x, 0, 1)
else:
self.delete_merge(x, 0, 1)
self.delete_successor(x.child[0])
# Delete resolution
def delete_merge(self, x, i, j):
cnode = x.child[i]
if j > i:
rsnode = x.child[j]
cnode.keys.append(x.keys[i])
for k in range(len(rsnode.keys)):
cnode.keys.append(rsnode.keys[k])
if len(rsnode.child) > 0:
cnode.child.append(rsnode.child[k])
if len(rsnode.child) > 0:
cnode.child.append(rsnode.child.pop())
new = cnode
x.keys.pop(i)
x.child.pop(j)
else:
lsnode = x.child[j]
lsnode.keys.append(x.keys[j])
for i in range(len(cnode.keys)):
lsnode.keys.append(cnode.keys[i])
if len(lsnode.child) > 0:
lsnode.child.append(cnode.child[i])
if len(lsnode.child) > 0:
lsnode.child.append(cnode.child.pop())
new = lsnode
x.keys.pop(j)
x.child.pop(i)
if x == self.root and len(x.keys) == 0:
self.root = new
# Delete the sibling
def delete_sibling(self, x, i, j):
cnode = x.child[i]
if i < j:
rsnode = x.child[j]
cnode.keys.append(x.keys[i])
x.keys[i] = rsnode.keys[0]
if len(rsnode.child) > 0:
cnode.child.append(rsnode.child[0])
rsnode.child.pop(0)
rsnode.keys.pop(0)
else:
lsnode = x.child[j]
cnode.keys.insert(0, x.keys[i - 1])
x.keys[i - 1] = lsnode.keys.pop()
if len(lsnode.child) > 0:
cnode.child.insert(0, lsnode.child.pop())
# Print the tree
def print_tree(self, x, l=0):
print("Level ", l, " ", len(x.keys), end=":")
for i in x.keys:
print(i, end=" ")
print()
l += 1
if len(x.child) > 0:
for i in x.child:
self.print_tree(i, l)
B = BTree(3)
for i in range(10):
B.insert((i, 2 * i))
B.print_tree(B.root)
B.delete(B.root, (8,))
print("\n")
B.print_tree(B.root)
OutputLevel 0 2:(2, 4) (5, 10)
Level 1 2:(0, 0) (1, 2)
Level 1 2:(3, 6) (4, 8)
Level 1 4:(6, 12) (7, 14) (8, 16) (9, 18)
Level 0 2:(2, 4) (5, 10)
Level 1 2:(0, 0) (1, 2)
Level 1 ...
Time Complexity : O(log n)
Auxiliary Space: O(n), where n is order of the tree
Similar Reads
B+ Tree in Python
In computer science, data structures are crucial in efficiently managing and organizing data. Among these, the B+ tree is a powerful and important data structure, widely used in databases and file systems. In this article, we will discuss the concept of B+ trees, exploring their structure, operation
12 min read
Alphabet range in Python
When working with strings and characters in Python, you may need to create a sequence of letters, such as the alphabet from 'a' to 'z' or 'A' to 'Z'. Python offers various options for accomplishing this, taking advantage of its rich string handling features. This article will go over numerous ways t
3 min read
Update List in Python
In Python Programming, a list is a sequence of a data structure that is mutable. This means that the elements of a list can be modified to add, delete, or update the values. In this article we will explore various ways to update the list. Let us see a simple example of updating a list in Python. [GF
3 min read
Nested-if statement in Python
For more complex decision trees, Python allows for nested if statements where one if statement is placed inside another. This article will explore the concept of nested if statements in Python, providing clarity on how to use them effectively. Python Nested if StatementA nested if statement in Pytho
2 min read
Best way to learn python
Python is a versatile and beginner-friendly programming language that has become immensely popular for its readability and wide range of applications. Whether you're aiming to start a career in programming or just want to expand your skill set, learning Python is a valuable investment of your time.
11 min read
Python Boolean
Python Boolean type is one of the built-in data types provided by Python, which represents one of the two values i.e. True or False. Generally, it is used to represent the truth values of the expressions. Python Boolean TypeBoolean value can be of two types only i.e. either True or False. The output
7 min read
Python Variables
In Python, variables are used to store data that can be referenced and manipulated during program execution. A variable is essentially a name that is assigned to a value. Unlike many other programming languages, Python variables do not require explicit declaration of type. The type of the variable i
7 min read
Python Functions
Python Functions is a block of statements that return the specific task. The idea is to put some commonly or repeatedly done tasks together and make a function so that instead of writing the same code again and again for different inputs, we can do the function calls to reuse code contained in it ov
11 min read
Python Set discard() Function
Python discard() is a built-in method to remove elements from the set. The discard() method takes exactly one argument. This method does not return any value. Example: In this example, we are removing the integer 3 from the set with discard() in Python. C/C++ Code my_set = {1, 2, 3, 4, 5} my_set.dis
3 min read
Python vs Cpython
Python is a high-level, interpreted programming language favored for its readability and versatility. It's widely used in web development, data science, machine learning, scripting, and more. However, Cpython is the default and most widely used implementation of the Python language. It's written in
4 min read