Day 59: Advanced Tree Structures
Day 59: Advanced Tree Structures #
Welcome to Day 59 of our 60 Days of Coding Algorithm Challenge! Today, we’ll delve deep into advanced tree structures, building upon the foundational tree concepts we’ve explored in previous lessons. We’ll focus on three powerful tree structures: AVL Trees, Red-Black Trees, and B-Trees.
1. AVL Trees #
AVL trees, named after their inventors Adelson-Velsky and Landis, are self-balancing binary search trees. They maintain their balance by ensuring that the height difference between left and right subtrees (balance factor) for every node is at most 1.
Key Properties: #
- For each node, the heights of its left and right subtrees differ by at most 1.
- Every subtree is an AVL tree.
- The height of an AVL tree with n nodes is O(log n).
Implementation: #
1class AVLNode:
2 def __init__(self, key):
3 self.key = key
4 self.left = None
5 self.right = None
6 self.height = 1
7
8class AVLTree:
9 def __init__(self):
10 self.root = None
11
12 def height(self, node):
13 if not node:
14 return 0
15 return node.height
16
17 def balance_factor( …
Day 59: Advanced Tree Structures #
Welcome to Day 59 of our 60 Days of Coding Algorithm Challenge! Today, we’ll delve deep into advanced tree structures, building upon the foundational tree concepts we’ve explored in previous lessons. We’ll focus on three powerful tree structures: AVL Trees, Red-Black Trees, and B-Trees.
1. AVL Trees #
AVL trees, named after their inventors Adelson-Velsky and Landis, are self-balancing binary search trees. They maintain their balance by ensuring that the height difference between left and right subtrees (balance factor) for every node is at most 1.
Key Properties: #
- For each node, the heights of its left and right subtrees differ by at most 1.
- Every subtree is an AVL tree.
- The height of an AVL tree with n nodes is O(log n).
Implementation: #
1class AVLNode:
2 def __init__(self, key):
3 self.key = key
4 self.left = None
5 self.right = None
6 self.height = 1
7
8class AVLTree:
9 def __init__(self):
10 self.root = None
11
12 def height(self, node):
13 if not node:
14 return 0
15 return node.height
16
17 def balance_factor(self, node):
18 if not node:
19 return 0
20 return self.height(node.left) - self.height(node.right)
21
22 def update_height(self, node):
23 if not node:
24 return
25 node.height = 1 + max(self.height(node.left), self.height(node.right))
26
27 def right_rotate(self, y):
28 x = y.left
29 T2 = x.right
30 x.right = y
31 y.left = T2
32 self.update_height(y)
33 self.update_height(x)
34 return x
35
36 def left_rotate(self, x):
37 y = x.right
38 T2 = y.left
39 y.left = x
40 x.right = T2
41 self.update_height(x)
42 self.update_height(y)
43 return y
44
45 def insert(self, root, key):
46 if not root:
47 return AVLNode(key)
48 if key < root.key:
49 root.left = self.insert(root.left, key)
50 else:
51 root.right = self.insert(root.right, key)
52
53 self.update_height(root)
54 balance = self.balance_factor(root)
55
56 # Left Left Case
57 if balance > 1 and key < root.left.key:
58 return self.right_rotate(root)
59 # Right Right Case
60 if balance < -1 and key > root.right.key:
61 return self.left_rotate(root)
62 # Left Right Case
63 if balance > 1 and key > root.left.key:
64 root.left = self.left_rotate(root.left)
65 return self.right_rotate(root)
66 # Right Left Case
67 if balance < -1 and key < root.right.key:
68 root.right = self.right_rotate(root.right)
69 return self.left_rotate(root)
70
71 return root
72
73 def insert_key(self, key):
74 self.root = self.insert(self.root, key)
75
76 def inorder_traversal(self, root):
77 if not root:
78 return
79 self.inorder_traversal(root.left)
80 print(root.key, end=" ")
81 self.inorder_traversal(root.right)
82
83# Example usage
84avl_tree = AVLTree()
85keys = [10, 20, 30, 40, 50, 25]
86for key in keys:
87 avl_tree.insert_key(key)
88
89print("Inorder traversal of the AVL tree:")
90avl_tree.inorder_traversal(avl_tree.root)
Time Complexity: #
- Insertion: O(log n)
- Deletion: O(log n)
- Search: O(log n)
Space Complexity: #
O(n) for storing n nodes
2. Red-Black Trees #
Red-Black trees are another type of self-balancing binary search tree. They maintain balance through a set of properties that ensure the tree remains approximately balanced during insertions and deletions.
Key Properties: #
- Every node is either red or black.
- The root is always black.
- Every leaf (NIL) is black.
- If a node is red, then both its children are black.
- For each node, all paths from the node to descendant leaves contain the same number of black nodes.
Implementation: #
1class Color:
2 RED = 1
3 BLACK = 2
4
5class RBNode:
6 def __init__(self, key):
7 self.key = key
8 self.left = None
9 self.right = None
10 self.parent = None
11 self.color = Color.RED
12
13class RedBlackTree:
14 def __init__(self):
15 self.NIL = RBNode(0)
16 self.NIL.color = Color.BLACK
17 self.root = self.NIL
18
19 def left_rotate(self, x):
20 y = x.right
21 x.right = y.left
22 if y.left != self.NIL:
23 y.left.parent = x
24 y.parent = x.parent
25 if x.parent == None:
26 self.root = y
27 elif x == x.parent.left:
28 x.parent.left = y
29 else:
30 x.parent.right = y
31 y.left = x
32 x.parent = y
33
34 def right_rotate(self, x):
35 y = x.left
36 x.left = y.right
37 if y.right != self.NIL:
38 y.right.parent = x
39 y.parent = x.parent
40 if x.parent == None:
41 self.root = y
42 elif x == x.parent.right:
43 x.parent.right = y
44 else:
45 x.parent.left = y
46 y.right = x
47 x.parent = y
48
49 def insert(self, key):
50 node = RBNode(key)
51 node.left = self.NIL
52 node.right = self.NIL
53 y = None
54 x = self.root
55
56 while x != self.NIL:
57 y = x
58 if node.key < x.key:
59 x = x.left
60 else:
61 x = x.right
62
63 node.parent = y
64 if y == None:
65 self.root = node
66 elif node.key < y.key:
67 y.left = node
68 else:
69 y.right = node
70
71 self.insert_fixup(node)
72
73 def insert_fixup(self, k):
74 while k.parent and k.parent.color == Color.RED:
75 if k.parent == k.parent.parent.left:
76 u = k.parent.parent.right
77 if u.color == Color.RED:
78 k.parent.color = Color.BLACK
79 u.color = Color.BLACK
80 k.parent.parent.color = Color.RED
81 k = k.parent.parent
82 else:
83 if k == k.parent.right:
84 k = k.parent
85 self.left_rotate(k)
86 k.parent.color = Color.BLACK
87 k.parent.parent.color = Color.RED
88 self.right_rotate(k.parent.parent)
89 else:
90 u = k.parent.parent.left
91 if u.color == Color.RED:
92 k.parent.color = Color.BLACK
93 u.color = Color.BLACK
94 k.parent.parent.color = Color.RED
95 k = k.parent.parent
96 else:
97 if k == k.parent.left:
98 k = k.parent
99 self.right_rotate(k)
100 k.parent.color = Color.BLACK
101 k.parent.parent.color = Color.RED
102 self.left_rotate(k.parent.parent)
103 if k == self.root:
104 break
105 self.root.color = Color.BLACK
106
107 def inorder_traversal(self, node):
108 if node != self.NIL:
109 self.inorder_traversal(node.left)
110 print(node.key, end=" ")
111 self.inorder_traversal(node.right)
112
113# Example usage
114rb_tree = RedBlackTree()
115keys = [7, 3, 18, 10, 22, 8, 11, 26, 2, 6, 13]
116for key in keys:
117 rb_tree.insert(key)
118
119print("Inorder traversal of the Red-Black tree:")
120rb_tree.inorder_traversal(rb_tree.root)
Time Complexity: #
- Insertion: O(log n)
- Deletion: O(log n)
- Search: O(log n)
Space Complexity: #
O(n) for storing n nodes
3. B-Trees #
B-Trees are self-balancing tree data structures that maintain sorted data and allow searches, insertions, and deletions in logarithmic time. They are optimized for systems that read and write large blocks of data, such as disk-based storage systems.
Key Properties: #
- All leaves are at the same depth.
- A non-leaf node with k children contains k-1 keys.
- Each node (except root) has at least t-1 keys and at most 2t-1 keys, where t is the minimum degree of the B-tree.
- The root has at least 2 children if it is not a leaf node.
- A non-leaf node with k keys has k+1 children.
Implementation: #
1class BTreeNode:
2 def __init__(self, leaf=False):
3 self.leaf = leaf
4 self.keys = []
5 self.children = []
6
7class BTree:
8 def __init__(self, t):
9 self.root = BTreeNode(True)
10 self.t = t # Minimum degree
11
12 def insert(self, k):
13 root = self.root
14 if len(root.keys) == (2 * self.t) - 1:
15 new_root = BTreeNode()
16 new_root.children.insert(0, root)
17 self.split_child(new_root, 0)
18 self.root = new_root
19 self.insert_non_full(new_root, k)
20 else:
21 self.insert_non_full(root, k)
22
23 def insert_non_full(self, x, k):
24 i = len(x.keys) - 1
25 if x.leaf:
26 x.keys.append(None)
27 while i >= 0 and k < x.keys[i]:
28 x.keys[i + 1] = x.keys[i]
29 i -= 1
30 x.keys[i + 1] = k
31 else:
32 while i >= 0 and k < x.keys[i]:
33 i -= 1
34 i += 1
35 if len(x.children[i].keys) == (2 * self.t) - 1:
36 self.split_child(x, i)
37 if k > x.keys[i]:
38 i += 1
39 self.insert_non_full(x.children[i], k)
40
41 def split_child(self, x, i):
42 t = self.t
43 y = x.children[i]
44 z = BTreeNode(y.leaf)
45 x.children.insert(i + 1, z)
46 x.keys.insert(i, y.keys[t - 1])
47 z.keys = y.keys[t: (2 * t) - 1]
48 y.keys = y.keys[0: t - 1]
49 if not y.leaf:
50 z.children = y.children[t: 2 * t]
51 y.children = y.children[0: t]
52
53 def traverse(self):
54 self._traverse(self.root)
55 print()
56
57 def _traverse(self, x):
58 i = 0
59 while i < len(x.keys):
60 if not x.leaf:
61 self._traverse(x.children[i])
62 print(x.keys[i], end=" ")
63 i += 1
64 if not x.leaf:
65 self._traverse(x.children[i])
66
67# Example usage
68b_tree = BTree(3) # A B-Tree with minimum degree 3
69keys = [10, 20, 5, 6, 12, 30, 7, 17]
70for key in keys:
71 b_tree.insert(key)
72
73print("B-Tree traversal:")
74b_tree.traverse()
Time Complexity: #
- Insertion: O(log n)
- Deletion: O(log n)
- Search: O(log n)
Space Complexity: #
O(n) for storing n keys
Comparison of Advanced Tree Structures #
AVL Trees:
- Pros: Strictly balanced, guaranteeing O(log n) operations
- Cons: More rotations during insertion and deletion
Red-Black Trees:
- Pros: Fewer rotations than AVL trees, good for frequent insertions/deletions
- Cons: Not as strictly balanced as AVL trees
B-Trees:
- Pros: Optimized for disk access, good for large datasets
- Cons: More complex implementation, higher memory usage for small datasets
Applications #
- AVL Trees: Used in databases for indexing, in-memory sorting
- Red-Black Trees: Implemented in many standard library map/set data structures
- B-Trees: Used in file systems and databases for efficient disk access
Exercise #
- Implement a delete operation for the AVL Tree.
- Add a search function to the Red-Black Tree implementation.
- Modify the B-Tree to support deletion of keys.
Summary #
Today, we explored three advanced tree structures: AVL Trees, Red-Black Trees, and B-Trees. Each of these structures offers unique benefits and trade-offs, making them suitable for different scenarios. Understanding these advanced tree structures is crucial for designing efficient algorithms and data structures for various applications, especially those dealing with large datasets or requiring balanced search trees.
As we near the end of our 60-day challenge, these advanced concepts demonstrate the depth and complexity of algorithmic thinking. Tomorrow, we’ll tackle our final topic, bringing together many of the concepts we’ve learned throughout this journey. As we near the 60 days, stay tuned for the exciting conclusion of our challenge!
Continue Reading
Sign up or log in to access the full lesson and all 60 days of algorithm content.