Skip to Content
Course content

158: Implementing a Red-Black Tree Concept

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was reviewing code for a developer who was building a custom event scheduler for a high-frequency trading app. He had implemented a standard Binary Search Tree (BST) to keep track of event timestamps. During our first load test, the system absolutely tanked. The problem wasn't the logic; it was the data. Because the events were arriving in almost perfectly chronological order, his BST didn't "branch" at all—it just grew in one long, straight line. He had accidentally built a very expensive linked list. His $O(\log n)$ lookups had become $O(n)$, and the latency spiked into the milliseconds.

That's the "degenerate tree" problem. In the real world, data isn't always random. If you're inserting sorted or semi-sorted data into a basic BST, you lose every single performance benefit of using a tree. This is why we use Red-Black Trees. They are self-balancing, meaning they use a set of rules to ensure the tree never gets too skewed, regardless of the order in which you insert the keys.

The Rules of the Game

To keep the tree balanced, we assign a "color" (Red or Black) to every node. I like to think of these colors as constraints that force the tree to restructure itself. You don't actually need a string or a complex enum; a simple int or a bit-field in your struct will do. For a tree to be a valid Red-Black Tree, it has to follow these rules strictly:

  • Every node is either red or black.
  • The root is always black.
  • Red nodes cannot have red children (no two reds in a row).
  • Every path from a node to its descendant NIL leaves must contain the same number of black nodes.

If you're wondering why we care about these specific rules, it's because they mathematically guarantee that the longest path from the root to a leaf is no more than twice as long as the shortest path. This keeps our time complexity locked at $O(\log n)$, saving us from the disaster my former colleague encountered.

Rotating the Architecture

When you insert a new node, you always start by coloring it red. But doing that often violates the "no two reds" rule. To fix this without breaking the BST property (where left is smaller and right is larger), we perform rotations. This is where the C pointer manipulation gets interesting.

A left rotation takes a node x and its right child y, and essentially "lifts" y up to take x's place. x then becomes the left child of y. If y already had a left child, that child gets handed off to x as its new right child. It's a bit of a dance with pointers, but it's the only way to move nodes around while keeping the sorted order intact.

typedef enum { RED, BLACK } NodeColor;

struct Node {
    int data;
    NodeColor color;
    struct Node *left, *right, *parent;
};

void rotate_left(struct Node **root, struct Node *x) {
    struct Node *y = x->right;
    x->right = y->left;
    
    if (y->left != NULL) {
        y->left->parent = x;
    }
    
    y->parent = x->parent;
    
    if (x->parent == NULL) {
        *root = y;
    } else if (x == x->parent->left) {
        x->parent->left = y;
    } else {
        x->parent->right = y;
    }
    
    y->left = x;
    x->parent = y;
}

I've seen people struggle with rotations because they try to visualize them in 2D on a whiteboard. Instead, focus on the "hand-off." The right child is moving up, and the right child's left subtree is being handed down to the old parent. Once you master the rotation, the rest of the implementation is just a series of if/else checks to see which "case" of red-violation you've encountered and applying the correct rotation or recoloring to fix it.




📋 Practical Task

Implementing the Left-Rotation Mechanism for Red-Black Trees

In this exercise, you will implement the rotate_left function for a Red-Black Tree. You are provided with a basic Node structure. Your goal is to correctly manipulate the pointers so that the tree remains a valid Binary Search Tree after the rotation, and the parent-child relationships are updated correctly.

Requirements:

  • Update the root pointer if the rotated node was the original root.
  • Correctly reassign the left child of the pivot node to the right child of the rotating node.
  • Ensure all parent pointers are updated to reflect the new hierarchy.
  • Do not change the data values within the nodes; only move the pointers.

#include <stdio.h>
#include <stdlib.h>

typedef enum { RED, BLACK } NodeColor;

struct Node {
    int data;
    NodeColor color;
    struct Node *left, *right, *parent;
};

// TODO: Implement this function
void rotate_left(struct Node **root, struct Node *x) {
    // Your code here
}

// Helper function to create a new node
struct Node* create_node(int data) {
    struct Node* node = (struct Node*)malloc(sizeof(struct Node));
    node->data = data;
    node->color = RED;
    node->left = node->right = node->parent = NULL;
    return node;
}

int main() {
    struct Node *root = create_node(10);
    root->color = BLACK;
    root->right = create_node(20);
    root->right->parent = root;
    
    printf("Before rotation: Root is %d, Root-Right is %d\n", root->data, root->right->data);
    
    rotate_left(&root, root);
    
    printf("After rotation: Root is %d, Root-Left is %d\n", root->data, root->left->data);
    
    return 0;
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.