Red-Black Tree Implementation in Python


Red-Black Tree Implementation in Python

A red-black tree is a self-balancing binary search tree where each node stores an extra bit representing “color” (“red” or “black”). The properties of red-black trees ensure that the tree remains approximately balanced, making the insertion, deletion, and search operations efficient with O(log n) time complexity.

Program Structure

The implementation of the red-black tree consists of the following components:

1. Node Class

This class represents a node in the red-black tree. It includes the following attributes:

  • key: The key stored in the node.
  • color: The color of the node (either “red” or “black”).
  • left, right, parent: Pointers to the left child, right child, and parent nodes, respectively.

2. RedBlackTree Class

This class encapsulates the functionality of the red-black tree and includes the following methods:

  • __init__(self): Initializes the red-black tree, including the root and the sentinel node.
  • insert(self, key): Inserts a new key into the red-black tree and balances the tree according to red-black properties.
  • insert_fixup(self, z): Fixes any violations of red-black properties after an insertion.
  • left_rotate(self, x): Performs a left rotation around a given node.
  • right_rotate(self, x): Performs a right rotation around a given node.
  • search(self, key): Searches for a key in the red-black tree.

Python Code Implementation


class Node:
def __init__(self, key, color="red"):
"""
Initialize a new node.
:param key: The key value of the node.
:param color: The color of the node (red or black).
"""
self.key = key
self.color = color # Node color, default to red.
self.left = None
self.right = None
self.parent = None

class RedBlackTree:
def __init__(self):
"""
Initialize the red-black tree.
"""
self.NIL = Node(key=None, color="black") # Sentinel node represents leaves.
self.root = self.NIL

def left_rotate(self, x):
"""
Perform a left rotation around the node x.
:param x: The node to rotate around.
"""
y = x.right
x.right = y.left
if y.left != self.NIL:
y.left.parent = x
y.parent = x.parent
if x.parent is None:
self.root = y
elif x == x.parent.left:
x.parent.left = y
else:
x.parent.right = y
y.left = x
x.parent = y

def right_rotate(self, x):
"""
Perform a right rotation around the node x.
:param x: The node to rotate around.
"""
y = x.left
x.left = y.right
if y.right != self.NIL:
y.right.parent = x
y.parent = x.parent
if x.parent is None:
self.root = y
elif x == x.parent.right:
x.parent.right = y
else:
x.parent.left = y
y.right = x
x.parent = y

def insert(self, key):
"""
Insert a new key into the red-black tree.
:param key: The key to insert.
"""
z = Node(key)
z.left = self.NIL
z.right = self.NIL
y = None
x = self.root
while x != self.NIL:
y = x
if z.key < x.key:
x = x.left
else:
x = x.right
z.parent = y
if y is None:
self.root = z
elif z.key < y.key:
y.left = z
else:
y.right = z
z.color = "red"
self.insert_fixup(z)

def insert_fixup(self, z):
"""
Fix the red-black tree after insertion to maintain properties.
:param z: The node that was inserted.
"""
while z.parent and z.parent.color == "red":
if z.parent == z.parent.parent.left:
y = z.parent.parent.right
if y.color == "red": # Case 1
z.parent.color = "black"
y.color = "black"
z.parent.parent.color = "red"
z = z.parent.parent
else:
if z == z.parent.right: # Case 2
z = z.parent
self.left_rotate(z)
z.parent.color = "black" # Case 3
z.parent.parent.color = "red"
self.right_rotate(z.parent.parent)
else:
y = z.parent.parent.left
if y.color == "red": # Case 1
z.parent.color = "black"
y.color = "black"
z.parent.parent.color = "red"
z = z.parent.parent
else:
if z == z.parent.left: # Case 2
z = z.parent
self.right_rotate(z)
z.parent.color = "black" # Case 3
z.parent.parent.color = "red"
self.left_rotate(z.parent.parent)
self.root.color = "black"

def search(self, key, node=None):
"""
Search for a key in the red-black tree.
:param key: The key to search for.
:param node: The node to start the search from (defaults to root).
:return: The node containing the key, or None if not found.
"""
if node is None:
node = self.root
while node != self.NIL and key != node.key:
if key < node.key:
node = node.left
else:
node = node.right
return node if node != self.NIL else None

Explanation

The red-black tree implementation is centered around two main classes: Node and RedBlackTree.

1. Node Class

The Node class represents a node in the red-black tree, containing:

  • key: The key stored in the node.
  • color: The color of the node, either “red” or “black”.
  • left, right, parent: Pointers to the left child, right child, and parent nodes, respectively.

2. RedBlackTree Class

The RedBlackTree class manages the red-black tree and provides the following key functionalities:

Insert Method

The insert method adds a new key to the tree. It starts by placing the new node as a red leaf and then calls insert_fixup to ensure that the red-black properties are maintained.

Insert Fixup

The insert_fixup method is crucial for maintaining the balance of the tree. It handles the three cases that arise during insertion:

  • Case 1: The new node’s parent and its uncle are both red. The solution is to recolor them and move the problem up the tree.
  • Case 2: The new node’s parent is red, but the uncle is black, and the new node is on the opposite side from its parent. A rotation is needed to transform the situation into Case 3.
  • Case 3: The new node’s parent is red, but the uncle is black, and the new node is on the same side as its parent. A rotation and recoloring fix the violation.

Rotations

The left_rotate and right_rotate methods perform rotations to adjust the tree structure. Rotations are necessary to maintain the balanced nature of the red-black tree during insertion and deletion operations.

Search Method

The search method allows searching for a key in the tree. It follows the binary search tree property to locate the key, returning the node if found, or None if the key is not present.

Usage Example


# Example usage of the RedBlackTree

# Create a red-black tree
rbt = RedBlackTree()

# Insert some keys into the tree
keys = [20, 15, 25, 10, 5, 1]
for key in keys:
rbt.insert(key)

# Search for a key in the tree
result = rbt.search(15)
if result:
print(f"Key 15 found with color: {result.color}")
else:
print("Key 15 not found in the tree.")

# Output: Key 15 found with color: black

This example demonstrates how to create a red-black tree, insert keys into it, and search for a specific key. The output shows whether the key is found and its color, indicating the balanced nature of the tree.


By Aditya Bhuyan

I work as a cloud specialist. In addition to being an architect and SRE specialist, I work as a cloud engineer and developer. I have assisted my clients in converting their antiquated programmes into contemporary microservices that operate on various cloud computing platforms such as AWS, GCP, Azure, or VMware Tanzu, as well as orchestration systems such as Docker Swarm or Kubernetes. For over twenty years, I have been employed in the IT sector as a Java developer, J2EE architect, scrum master, and instructor. I write about Cloud Native and Cloud often. Bangalore, India is where my family and I call home. I maintain my physical and mental fitness by doing a lot of yoga and meditation.

Leave a Reply

Your email address will not be published. Required fields are marked *

error

Enjoy this blog? Please spread the word :)