Deletion From a Red-Black Tree (Data Structures & Algorithms)
Learn Deletion From a Red-Black Tree (Data Structures & Algorithms) step by step with clear examples and exercises.
Why This Matters
Deleting a node from a Red-Black Tree is crucial for maintaining its balance and efficiency. This operation ensures that the tree remains a self-balancing binary search tree, with an average time complexity of O(log n) for insertion, deletion, and search operations. Understanding how to delete a node from a Red-Black Tree is essential for mastering data structures and algorithms, as it helps you solve real-world problems and prepare for technical interviews.
Prerequisites
Before diving into deletion in Red-Black Trees, you should be familiar with the following:
- Basic Python concepts (variables, functions, loops, conditionals)
- Binary Search Trees (BST) and their operations (insertion, search, traversal)
- Understanding of recursion
- Familiarity with Red-Black Tree properties and insertion process
- Knowledge of Python classes and object-oriented programming concepts
- Comprehension of the
__init__,parent, and other helper functions used in the example code - Basic understanding of tree traversal (inorder, preorder, postorder)
- Understanding of Python's built-in data structures like lists and dictionaries
- Familiarity with Big O notation and time complexity analysis
- Knowledge of Python's error handling mechanisms (try/except blocks)
Core Concept
A Red-Black Tree is a binary search tree with the following additional properties:
- Every node has a color (either red or black)
- The root, empty tree, and all leaves are black
- Each node with two red children is black
- For each node, all paths from the node to the leaf descendants have the same number of black nodes
Deleting a node in a Red-Black Tree involves four cases:
- Deleting a leaf (black or red)
- Deleting a node with one red child
- Deleting a node with two black children
- Deleting a node with two red children
Case 1: Deleting a Leaf (Black or Red)
- A black leaf can be deleted directly, and its parent's color changes to red if it has become empty.
- A red leaf can be replaced by its sibling, maintaining the tree's balance.
Case 2: Deleting a Node with One Red Child
- The red child is promoted to black, and the parent becomes red.
- If the parent is now violating any property, we recursively apply the appropriate balancing operation (case 3 or case 4).
Case 3: Deleting a Node with Two Black Children
- Perform a left rotation on the node to make one of its children the new root.
- If the new root's sibling is red, perform a right rotation and color changes to balance the tree (case 2).
Case 4: Deleting a Node with Two Red Children
- Perform a left rotation on the node to make one of its children the new root.
- If the new root's sibling is black, change both siblings and their parent to red, making the parent's parent the problematic node (case 3 or case 4).
Worked Example
Let's delete the value 17 from the following Red-Black Tree:
class Node:
def __init__(self, key, color):
self.key = key
self.left = None
self.right = None
self.color = color
self.parent = None
def insert(root, key):
if root is None:
return Node(key, 'red')
else:
if key < root.key:
root.left = root.left.insert(root.left, key)
root.left.parent = root
elif key > root.key:
root.right = root.right.insert(root.right, key)
root.right.parent = root
if is_red(root.left) and is_red(root.right):
return balance(root)
return root
def balance(node):
if node is None:
return None
if is_black(node.right.left) and is_black(node.right.right):
node = rotate_left(node)
if is_red(node.left):
node.left = rotate_left(node.left)
node = rotate_right(node)
if is_black(node.left) and is_black(node.right):
node.color = 'red'
return node
if height(node.left) > height(node.right):
if height(node.left.left) > height(node.left.right):
node = rotate_right(node)
node = rotate_left(node)
else:
if height(node.right.left) > height(node.right.right):
node = rotate_left(node)
node = rotate_right(node)
else:
if height(node.right.left) > height(node.right.right):
node = rotate_left(node)
node = rotate_right(node)
return node
def rotate_left(self, node):
y = node.right
T3 = y.left
y.left = node
node.parent = y
node.right = T3
if T3 is not None:
T3.parent = node
return y
def rotate_right(self, node):
y = node.left
T2 = y.right
y.right = node
node.parent = y
node.left = T2
if T2 is not None:
T2.parent = node
return y
def is_red(node):
return node and node.color == 'red'
def is_black(node):
return node and node.color == 'black'
def height(node):
if node is None:
return 0
else:
left_height = height(node.left)
right_height = height(node.right)
return max(left_height, right_height) + 1
def delete(root, key):
if root is None:
return root
if key < root.key:
root.left = delete(root.left, key)
elif key > root.key:
root.right = delete(root.right, key)
if root.right is None:
return root.left
if is_black(root.left) and is_red(root.right):
root.color = 'red'
root.right = balance(root.right)
if root.right is not None and (is_black(root.right.left) or is_black(root.right.right)):
root = move_down(root)
return balance(root)
def move_down(self, node):
if is_red(node.left) and is_black(node.right):
node.left, node.right = node.right, node.left
node.color = 'red'
return balance(node)
if is_black(node.left) and is_red(node.right):
if is_black(node.right.left):
node.right = rotate_left(node.right)
node.color = 'red'
node.right = balance(node.right)
if is_black(node.left) and is_black(node.right):
if is_black(node.left.left) and is_black(node.left.right):
node = rotate_right(node)
node = rotate_left(node)
else:
if is_black(node.right.left) and is_black(node.right.right):
node = rotate_left(node)
node = rotate_right(node)
else:
if is_black(node.right.left) and is_black(node.right.right):
node = rotate_left(node)
node = rotate_right(node)
return node
def inorder_traversal(node):
if node is not None:
inorder_traversal(node.left)
print(node.key, end=' ')
inorder_traversal(node.right)
tree = Node(10, 'black')
insert(tree, 2)
insert(tree, 4)
insert(tree, 6)
insert(tree, 8)
insert(tree, 12)
insert(tree, 14)
insert(tree, 17)
print("Original Tree:")
inorder_traversal(tree)
delete(tree, 17)
print("\nTree after deleting 17:")
inorder_traversal(tree)
Common Mistakes
- Forgetting to check if the node being deleted is a leaf or has one red child.
- Not properly handling cases when the deleted node has two black children (case 3 and case 4).
- Failing to update the parent of the replaced sibling in case 2.
- Forgetting to balance the tree after performing rotations (cases 3 and 4).
- Not returning the new root after deleting a node.
- Neglecting to handle cases when the deleted node is the root or has only one child.
- Misunderstanding the order of operations during balancing, leading to incorrect results.
- Failing to maintain the Red-Black Tree properties throughout the deletion process.
- Using an inefficient implementation that does not take advantage of the tree's self-balancing property.
- Not properly implementing the helper functions (
rotate_left,rotate_right,move_down, etc.) required for the deletion algorithm. - Failing to handle edge cases like a single node or an empty tree during insertion and deletion operations.
- Neglecting to update the height of nodes after rotations or deletions.
- Implementing inefficient search algorithms that do not take advantage of the Red-Black Tree's properties.
Practice Questions
- Write a function to search for a given key in a Red-Black Tree using recursive and iterative approaches.
- Implement the
inorder_traversalfunction using preorder, postorder, and breadth-first traversals. - Given the following Red-Black Tree, delete the nodes with keys 7 and 15:
class Node:
def __init__(self, key, color):
self.key = key
self.left = None
self.right = None
self.color = color
self.parent = None
... (insert, balance, height, delete functions)
tree = Node(5, 'black')
insert(tree, 3)
insert(tree, 7)
insert(tree, 10)
insert(tree, 15)
insert(tree, 20)
insert(tree, 25)
print("Original Tree:")
inorder_traversal(tree)
delete(tree, 7)
delete(tree, 15)
print("\nTree after deleting 7 and 15:")
inorder_traversal(tree)
FAQ
What happens if we delete a root node in a Red-Black Tree?
- If the root is red, it can be deleted directly. If it's black, its successor or predecessor will replace it, and the new root may need balancing operations.
Can a Red-Black Tree have two red siblings?
- No, because having two red siblings would violate the property that every path from the root to a leaf has the same number of black nodes.
Why do we need to balance the tree after performing rotations in case 3 or case 4?
- Balancing ensures that the Red-Black Tree properties are maintained, which guarantees efficient insertion, deletion, and search operations.
Can a leaf node be red in a Red-Black Tree?
- Yes, a leaf node can be either black or red.
What is the time complexity of deleting a node from a Red-Black Tree?
- The average time complexity for deletion in a Red-Black Tree is O(log n). However, in the worst case, it could take O(n) due to an unbalanced tree structure or improper implementation.